412 lines
16 KiB
Python
412 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
|
|
|
|
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):
|
|
daily_data = defaultdict(lambda: {
|
|
'parties': [],
|
|
'expences': [],
|
|
'incomes': [],
|
|
'parties_total': Decimal('0'),
|
|
'day_kredit': Decimal('0'),
|
|
'day_debit': Decimal('0'),
|
|
'day_balance': Decimal('0'),
|
|
'balance_type': 'kredit'
|
|
})
|
|
|
|
for party in parties:
|
|
date_key = party.closed_date.strftime('%Y-%m-%d')
|
|
daily_data[date_key]['parties'].append(party)
|
|
|
|
party_total = getattr(party.party_amount, 'calculated_amount', 0) or 0
|
|
daily_data[date_key]['parties_total'] = daily_data[date_key].get('parties_total', Decimal('0')) + Decimal(party_total)
|
|
|
|
for income in incomes:
|
|
date_key = income.created_at.strftime('%Y-%m-%d')
|
|
daily_data[date_key]['incomes'].append(income)
|
|
|
|
for expence in expences:
|
|
date_key = expence.created_at.strftime('%Y-%m-%d')
|
|
daily_data[date_key]['expences'].append(expence)
|
|
|
|
for date_key in sorted(daily_data.keys(), reverse=True):
|
|
parties_list = daily_data[date_key]['parties']
|
|
incomes_list = daily_data[date_key]['incomes']
|
|
expences_list = daily_data[date_key]['expences']
|
|
|
|
parties_total = daily_data[date_key].get('parties_total', Decimal('0'))
|
|
incomes_total = sum(
|
|
Decimal(i.price or 0)
|
|
for i in incomes_list
|
|
)
|
|
day_kredit = parties_total + incomes_total
|
|
|
|
day_debit = sum(
|
|
Decimal(e.price or 0)
|
|
for e in expences_list
|
|
)
|
|
|
|
daily_data[date_key].update({
|
|
'parties': PartyAKTSerializer(parties_list, many=True).data,
|
|
'expences': ExpenceListSerializer(expences_list, many=True).data,
|
|
'incomes': IncomeListSerializer(incomes_list, many=True).data,
|
|
'day_kredit': str(day_kredit),
|
|
'day_debit': str(day_debit),
|
|
})
|
|
|
|
return {
|
|
date: {
|
|
'date': date,
|
|
'parties': data['parties'],
|
|
'expences': data['expences'],
|
|
'incomes': data['incomes'],
|
|
'kredit': data['day_kredit'],
|
|
'debit': data['day_debit'],
|
|
}
|
|
for date, data in daily_data.items()
|
|
}
|