from decimal import Decimal from collections import defaultdict from django.db.models import Sum, Count, Q from django.shortcuts import get_object_or_404 from rest_framework import generics, views, filters from rest_framework.response import Response from django_filters.rest_framework.backends import DjangoFilterBackend # accounts from core.apps.accounts.permissions.permissions import HasRolePermission # shared from core.apps.shared.paginations.custom import CustomPageNumberPagination # counterparty from core.apps.counterparty.models import Counterparty, CounterpartyFolder, CounterpartyBalance from core.apps.counterparty.serializers import counterparty as serializers from core.apps.counterparty.filters.counterparty import CounterpartyFilter # finance from core.apps.finance.models import Expence, Income from core.apps.finance.serializers.income import IncomeListSerializer from core.apps.finance.serializers.expence import ExpenceListSerializer # orders from core.apps.orders.models import Party from core.apps.orders.serializers.party import PartyAKTSerializer class CounterpartyListApiView(generics.ListAPIView): serializer_class = serializers.CounterpartyListSerializer queryset = Counterparty.objects\ .select_related('balance')\ .exclude(is_archived=True)\ .exclude(folder__isnull=False)\ .order_by('-created_at') pagination_class = [HasRolePermission] pagination_class = CustomPageNumberPagination filter_backends = [DjangoFilterBackend, filters.SearchFilter] filterset_class = CounterpartyFilter search_fields = [ 'name', 'inn' ] class CounterpartyCreateApiView(generics.GenericAPIView): serializer_class = serializers.CounterpartyCreateSerializer queryset = Counterparty.objects.all() permission_classes = [HasRolePermission] def post(self, request): serializer = self.serializer_class(data=request.data) if serializer.is_valid(raise_exception=True): data = serializer.save() return Response( { 'success': True, 'message': 'Conterparty Created', 'data': serializers.CounterpartyListSerializer(data).data }, status=201 ) return Response( {'success': False, 'message': serializer.errors}, status=400 ) class ArchiveCounterpartyApiView(views.APIView): permission_classes = [HasRolePermission] def get(self, request, id): counterparty = get_object_or_404(Counterparty, id=id) counterparty.is_archived = True counterparty.save() return Response( {'success': True, 'message': 'counterparty archived'}, status=200 ) class ArchivedCounterpartyListApiView(generics.ListAPIView): serializer_class = serializers.CounterpartyListSerializer queryset = Counterparty.objects.exclude(is_archived=False).select_related('balance').order_by('-created_at') pagination_class = [HasRolePermission] pagination_class = CustomPageNumberPagination class CounterpartyDeleteApiView(views.APIView): permission_classes = [HasRolePermission] def delete(self, request, id): counterparty = get_object_or_404(Counterparty, id=id) counterparty.delete() return Response( {'success': True, 'message': 'counterparty deleted'}, status=204 ) class CounterpartyUpdateApiView(generics.UpdateAPIView): permission_classes = [HasRolePermission] lookup_field = 'id' serializer_class = serializers.CounterpartyUpdateSerializer queryset = Counterparty.objects.all() class FolderCounterpartyListApiView(generics.GenericAPIView): serializer_class = serializers.CounterpartyListSerializer queryset = Counterparty.objects.exclude(is_archived=True).select_related('balance').order_by('-created_at') permission_classes = [HasRolePermission] filter_backends = [filters.SearchFilter] search_fields = [ 'name', 'inn' ] def get(self, reuqest, folder_id): folder = get_object_or_404(CounterpartyFolder, id=folder_id) queryset = self.queryset.filter(folder=folder).exclude(folder__isnull=True) page = self.paginate_queryset(self.filter_queryset(queryset)) if page is not None: serializer = self.serializer_class(page, many=True) return self.get_paginated_response(serializer.data) class CounterpartyStatisticsApiView(views.APIView): permission_classes = [HasRolePermission] def get(self, request): counterparty_ids = request.query_params.getlist('counterparty') if counterparty_ids: queryset = Counterparty.objects.filter(id__in=counterparty_ids) else: queryset = Counterparty.objects.all() balance_qs = CounterpartyBalance.objects.filter(counterparty__in=queryset) stats = balance_qs.aggregate( total_balance_uzs=Sum('balance_uzs'), total_balance_usd=Sum('balance_usd'), total_debit_uzs=Sum('debit_uzs'), total_kredit_uzs=Sum('kredit_uzs'), total_debit_usd=Sum('debit_usd'), total_kredit_usd=Sum('kredit_usd'), ) counterparty_stats = queryset.aggregate( total_counterparties=Count('id'), total_creditors=Count('id', filter=Q(status='CREDITOR')), total_debtors=Count('id', filter=Q(status='DEBITOR')), ) result = { **counterparty_stats, **stats, } for key, value in result.items(): if value is None: result[key] = 0 result['total_debit_uzs'] = abs(Decimal(result['total_debit_uzs'])) result['total_debit_usd'] = abs(Decimal(result['total_debit_usd'])) return Response(result) class CounterpartiesApiView(generics.GenericAPIView): serializer_class = serializers.CounterpartyListSerializer queryset = Counterparty.objects.order_by('-created_at').select_related('balance') permission_classes = [HasRolePermission] filter_backends = [filters.SearchFilter] search_fields = [ 'name', 'inn' ] def get(self, request): page = self.paginate_queryset(self.filter_queryset(self.queryset)) if page is not None: ser = self.serializer_class(page, many=True) return self.get_paginated_response(ser.data) class CounterpartyDetailApiView(views.APIView): permission_classes = [HasRolePermission] def get(self, request, id): obj = get_object_or_404(Counterparty, id=id) serializer = serializers.CounterpartyListSerializer(obj) return Response(serializer.data, status=200) class UnArchiveCounterpartyApiView(views.APIView): permission_classes = [HasRolePermission] def get(self, request, id): obj = get_object_or_404(Counterparty, id=id) obj.is_archived = False obj.save() return Response( {'success': True, 'message': 'Conterparty unarchived'}, status=200 ) class CounterPartyIncomeExpenceStatisticsApiView(views.APIView): permission_classes = [HasRolePermission] def get(self, request, id): counterparty = get_object_or_404(Counterparty, id=id) incomes = Income.objects.filter(counterparty=counterparty, is_deleted=False) expences = Expence.objects.filter(counterparty=counterparty, is_deleted=False) income_by_currency = {'uzs': {'total': Decimal(0), 'count': 0, 'amount_uzs': Decimal(0)}, 'usd': {'total': Decimal(0), 'count': 0, 'amount_uzs': Decimal(0)}} for income in incomes: currency = income.currency amount = Decimal(income.price or 0) rate = Decimal(income.exchange_rate or 1) income_by_currency[currency]['total'] += amount income_by_currency[currency]['count'] += 1 income_by_currency[currency]['amount_uzs'] += amount * rate if currency == 'usd' else amount expence_by_currency = {'uzs': {'total': Decimal(0), 'count': 0, 'amount_uzs': Decimal(0)}, 'usd': {'total': Decimal(0), 'count': 0, 'amount_uzs': Decimal(0)}} for expence in expences: currency = expence.currency amount = Decimal(expence.price or 0) rate = Decimal(expence.exchange_rate or 1) expence_by_currency[currency]['total'] += amount expence_by_currency[currency]['count'] += 1 expence_by_currency[currency]['amount_uzs'] += amount * rate if currency == 'usd' else amount total_income_uzs = sum(v['amount_uzs'] for v in income_by_currency.values()) total_expence_uzs = sum(v['amount_uzs'] for v in expence_by_currency.values()) total_income_usd = income_by_currency['usd']['total'] total_expence_usd = expence_by_currency['usd']['total'] balance_uzs = counterparty.balance.total_balance_uzs balance_usd = counterparty.balance.total_balance_usd if balance_uzs > 0: status = 'positive' elif balance_uzs < 0: status = 'negative' else: status = 'zero' data = { 'counterparty': { 'id': counterparty.id, 'name': counterparty.name }, 'income': { 'by_currency': income_by_currency, 'total_uzs': total_income_uzs, 'total_usd': total_income_usd, 'total_count': sum(v['count'] for v in income_by_currency.values()) }, 'expence': { 'by_currency': expence_by_currency, 'total_uzs': total_expence_uzs, 'total_usd': total_expence_usd, 'total_count': sum(v['count'] for v in expence_by_currency.values()) }, 'balance': { 'uzs': balance_uzs, 'usd': balance_usd, 'status': status, 'total_balance': counterparty.balance.total_balance } } return Response(data, status=200) class CounterpartyAKTApiView(views.APIView): permission_classes = [HasRolePermission] def get(self, request, id): # TODO: filterlar date = request.query_params.get('date', None) end_date = request.query_params.get('end_date', None) project_folder = request.query_params.getlist('folder', None) project = request.query_params.getlist('project', None) currency = request.query_params.get('currency', 'uzs') counterparty = get_object_or_404(Counterparty, id=id) parties = Party.objects.prefetch_related('orders', 'orders__product').filter( orders__counterparty=counterparty, is_deleted=False, process=100, payment_percentage=100 ).distinct().order_by('-created_at') expences = Expence.objects.select_related().filter(counterparty=counterparty, is_deleted=False).distinct().order_by('-created_at') incomes = Income.objects.filter(counterparty=counterparty, is_deleted=False).distinct().order_by('-created_at') # TODO: date va end date boyicha filter if date: parties = parties.filter(close_date__gte=date) expences = expences.filter(created_at__gte=date) incomes = incomes.filter(created_at__gte=date) if end_date: parties = parties.filter(close_date__lte=end_date) expences = expences.filter(created_at__lte=end_date) incomes = incomes.filter(created_at__lte=end_date) # TODO: project folder va project boyicha filter if project_folder: parties = parties.filter(orders__project_folder=project_folder).distinct() expences = expences.filter(project_folder=project_folder) incomes = incomes.filter(project_folder=project_folder) if project: parties = parties.filter(orders__project=project).distinct() expences = expences.filter(project=project) incomes = incomes.filter(project=project) # TODO: currency boyicha filter if currency: parties = parties.filter(currency=currency) expences = expences.filter(currency=currency) incomes = incomes.filter(currency=currency) # TODO: date boyicha guruhlash daily_breakdown = self._group_by_date(parties, incomes, expences) # TODO: total kreditni hisoblash kerak: Sum(party total_price) + Sum(income total_price) parties_total_price = parties.aggregate(total_price=Sum('party_amount__total_price'))['total_price'] or 0 income_total_price = incomes.aggregate(total_price=Sum('price'))['total_price'] or 0 total_kredit = Decimal(parties_total_price) + Decimal(income_total_price) # TODO: total debitni hisoblash kerak: Sum(expence total_price) expence_total_balance = expences.aggregate(total_price=Sum('price'))['total_price'] or 0 total_debit = expence_total_balance # TODO: final balanceni hisoblash kerak: total_kredit - total_debit = final balance => negative or positive final_balance = total_kredit - total_debit # TODO: final balance typeni topish kerak -> debit or kredit: if negative == debit, positive == kredit type = 'debit' if final_balance < 0 else 'kredit' response = { "daily_breakdown": daily_breakdown, "total_kredit": str(total_kredit), "total_debit": str(total_debit), "final_balance": { "balance": str(final_balance) if not str(final_balance).startswith('-') else str(final_balance).replace('-', ''), "type": type, } } return Response(response, status=200) def _group_by_date(self, parties, incomes, expences): daily_data = defaultdict(list) # PARTIYALAR (kirim) for party in parties: date_key = party.closed_date.strftime('%Y-%m-%d') daily_data[date_key].append({ "type": "kirim", "price": str(getattr(party.party_amount, 'calculated_amount', 0) or 0), "partiya": PartyAKTSerializer(party).data, "kirim": None, "chiqim": None }) # INCOMES (kirim) for income in incomes: date_key = income.created_at.strftime('%Y-%m-%d') daily_data[date_key].append({ "type": "kirim", "price": str(income.price or 0), "partiya": None, # ❗ KIRIM ichida PARTIYA BO‘LMAYDI "kirim": IncomeListSerializer(income).data, "chiqim": None }) # EXPENCES (chiqim) for expence in expences: date_key = expence.created_at.strftime('%Y-%m-%d') daily_data[date_key].append({ "type": "chiqim", "price": str(expence.price or 0), "partiya": None, "kirim": None, "chiqim": ExpenceListSerializer(expence).data }) # Yakuniy format result = [] for date_key in sorted(daily_data.keys(), reverse=True): result.append({ "date": date_key, "items": daily_data[date_key] }) return result