Files
ibapp-backend/core/apps/counterparty/views/counterparty.py
behruz-dev 3179dcc16c fix
2025-11-14 17:13:48 +05:00

403 lines
16 KiB
Python

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
}
}
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):
"""Kun boyicha guruhlash"""
daily_data = defaultdict(lambda: {
'parties': [],
'expences': [],
'incomes': [],
'day_kredit': Decimal('0'),
'day_debit': Decimal('0'),
'day_balance': Decimal('0'),
'balance_type': 'kredit'
})
# Partiyalarni kunga ajratish (close_date bo'yicha)
for party in parties:
date_key = party.closed_date.strftime('%Y-%m-%d')
daily_data[date_key]['parties'].append(party)
party_total = getattr(party, 'total_price', 0) or 0
daily_data[date_key]['day_kredit'] += Decimal(party_total)
# Kirimlarni kunga ajratish (created_at bo'yicha)
for income in incomes:
date_key = income.created_at.strftime('%Y-%m-%d')
daily_data[date_key]['incomes'].append(income)
daily_data[date_key]['day_kredit'] += Decimal(income.price or 0)
# Chiqimlarni kunga ajratish (created_at bo'yicha)
for expence in expences:
date_key = expence.created_at.strftime('%Y-%m-%d')
daily_data[date_key]['expences'].append(expence)
daily_data[date_key]['day_debit'] += Decimal(expence.price or 0)
# Har kun uchun balans hisobini yakuniy qilish
result = []
for date_key in sorted(daily_data.keys(), reverse=True):
data = daily_data[date_key]
day_balance = data['day_kredit'] - data['day_debit']
balance_type = 'debit' if day_balance < 0 else 'kredit'
result.append({
'date': date_key,
'parties': PartyAKTSerializer(data['parties'], many=True).data,
'expences': ExpenceListSerializer(data['expences'], many=True).data,
'incomes': IncomeListSerializer(data['incomes'], many=True).data,
'kredit': str(data['day_kredit']),
'debit': str(data['day_debit']),
'balance': {
'balance': str(abs(day_balance)),
'type': balance_type
}
})
return result