Files
ibapp-backend/core/apps/counterparty/views/counterparty.py
behruz-dev f4d1b0afbe fix
2025-11-18 16:02:55 +05:00

399 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 BOLMAYDI
"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