From e98e40a3c7e534b25bb44e6a703e01279c2dcbde Mon Sep 17 00:00:00 2001 From: behruz-dev Date: Tue, 9 Sep 2025 15:07:58 +0500 Subject: [PATCH] add: add expence api --- .../finance/migrations/0012_expence_file.py | 18 +++ .../migrations/0013_alter_expence_file.py | 18 +++ .../0014_alter_expence_exchange_rate.py | 18 +++ core/apps/finance/models/expence.py | 3 +- .../finance/serializers/cash_transaction.py | 4 +- core/apps/finance/serializers/expence.py | 104 ++++++++++++++++++ core/apps/finance/urls.py | 7 ++ core/apps/finance/views/expence.py | 52 +++++++++ 8 files changed, 222 insertions(+), 2 deletions(-) create mode 100644 core/apps/finance/migrations/0012_expence_file.py create mode 100644 core/apps/finance/migrations/0013_alter_expence_file.py create mode 100644 core/apps/finance/migrations/0014_alter_expence_exchange_rate.py create mode 100644 core/apps/finance/views/expence.py diff --git a/core/apps/finance/migrations/0012_expence_file.py b/core/apps/finance/migrations/0012_expence_file.py new file mode 100644 index 0000000..25c527d --- /dev/null +++ b/core/apps/finance/migrations/0012_expence_file.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.4 on 2025-09-09 14:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('finance', '0011_paymenttype_total_usd_paymenttype_total_uzs_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='expence', + name='file', + field=models.FileField(blank=True, null=True, unique='finance/expence/files/', upload_to=''), + ), + ] diff --git a/core/apps/finance/migrations/0013_alter_expence_file.py b/core/apps/finance/migrations/0013_alter_expence_file.py new file mode 100644 index 0000000..80525d7 --- /dev/null +++ b/core/apps/finance/migrations/0013_alter_expence_file.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.4 on 2025-09-09 15:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('finance', '0012_expence_file'), + ] + + operations = [ + migrations.AlterField( + model_name='expence', + name='file', + field=models.FileField(blank=True, null=True, upload_to='finance/expence/files/'), + ), + ] diff --git a/core/apps/finance/migrations/0014_alter_expence_exchange_rate.py b/core/apps/finance/migrations/0014_alter_expence_exchange_rate.py new file mode 100644 index 0000000..feac499 --- /dev/null +++ b/core/apps/finance/migrations/0014_alter_expence_exchange_rate.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.4 on 2025-09-09 15:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('finance', '0013_alter_expence_file'), + ] + + operations = [ + migrations.AlterField( + model_name='expence', + name='exchange_rate', + field=models.PositiveBigIntegerField(blank=True, default=0, null=True), + ), + ] diff --git a/core/apps/finance/models/expence.py b/core/apps/finance/models/expence.py index 52ac9c2..93b194d 100644 --- a/core/apps/finance/models/expence.py +++ b/core/apps/finance/models/expence.py @@ -22,13 +22,14 @@ class Expence(BaseModel): ) price = models.PositiveBigIntegerField() - exchange_rate = models.PositiveBigIntegerField(default=0) + exchange_rate = models.PositiveBigIntegerField(default=0, null=True, blank=True) currency = models.CharField( max_length=3, choices=[('usd','usd'), ('uzs', 'uzs')] ) date = models.DateField(null=True, blank=True) comment = models.TextField(null=True, blank=True) audit = models.CharField(max_length=200, null=True, blank=True) + file = models.FileField(null=True, blank=True, upload_to='finance/expence/files/') def __str__(self): return f'{self.cash_transaction} kassa uchun chiqim {self.price}' diff --git a/core/apps/finance/serializers/cash_transaction.py b/core/apps/finance/serializers/cash_transaction.py index 72b9e4c..a364366 100644 --- a/core/apps/finance/serializers/cash_transaction.py +++ b/core/apps/finance/serializers/cash_transaction.py @@ -23,7 +23,9 @@ class CashTransactionListSerializer(serializers.ModelSerializer): class Meta: model = CashTransaction fields = [ - 'id', 'name', 'payment_type', 'employees', 'status' + 'id', 'name', 'payment_type', 'employees', 'status', 'total_balance_usd', + 'income_balance_usd', 'expence_balance_usd', 'total_balance_uzs', + 'income_balance_uzs', 'expence_balance_uzs' ] diff --git a/core/apps/finance/serializers/expence.py b/core/apps/finance/serializers/expence.py index e69de29..8290ca8 100644 --- a/core/apps/finance/serializers/expence.py +++ b/core/apps/finance/serializers/expence.py @@ -0,0 +1,104 @@ +from django.db import transaction + +from rest_framework import serializers + +from core.apps.finance.models import Expence + + +class ExpenceCreateSerializer(serializers.ModelSerializer): + class Meta: + model = Expence + fields = [ + 'cash_transaction', 'payment_type', 'project_folder', 'project', 'expence_type', + 'counterparty', 'price', 'exchange_rate', 'currency', 'date', 'comment', 'audit', 'file' + ] + + def create(self, validated_data): + with transaction.atomic(): + expence = Expence.objects.create( + cash_transaction=validated_data.get('cash_transaction'), + payment_type=validated_data.get('payment_type'), + project_folder=validated_data.get('project_folder'), + project=validated_data.get('project'), + expence_type=validated_data.get('expence_type'), + counterparty=validated_data.get('counterparty'), + price=validated_data.get('price') * validated_data.get('exchange_rate') if validated_data.get('exchange_rate') else validated_data.get('price'), + exchange_rate=validated_data.get('exchange_rate'), + currency=validated_data.get('currency'), + date=validated_data.get('date'), + comment=validated_data.get('comment'), + audit=validated_data.get('audit'), + file=validated_data.get('file'), + ) + cash_transaction = expence.cash_transaction + payment_type = expence.payment_type + + if validated_data.get('currency') == 'uzs': + cash_transaction.expence_balance_uzs += expence.price + cash_transaction.total_balance_uzs = cash_transaction.income_balance_uzs - cash_transaction.expence_balance_uzs + if payment_type.total_uzs > expence.price: + payment_type.total_uzs -= expence.price + elif validated_data.get('currency') == 'usd': + cash_transaction.expence_balance_usd += expence.price + if cash_transaction.income_balance_usd > cash_transaction.expence_balance_usd: + cash_transaction.total_balance_usd = cash_transaction.income_balance_usd - cash_transaction.expence_balance_usd + else: + cash_transaction.total_balance_usd = 0 + if payment_type.total_usd > expence.price: + payment_type.total_usd -= expence.price + + cash_transaction.save() + payment_type.save() + return expence + + +class ExpenceListSerializer(serializers.ModelSerializer): + cash_transaction = serializers.SerializerMethodField(method_name='get_cash_transaction') + payment_type = serializers.SerializerMethodField(method_name='get_payment_type') + project_folder = serializers.SerializerMethodField(method_name='get_project_folder') + project = serializers.SerializerMethodField(method_name='get_project') + counterparty = serializers.SerializerMethodField(method_name='get_counterparty') + expence_type = serializers.SerializerMethodField(method_name='get_expence_type') + + class Meta: + model = Expence + fields = [ + 'id', 'cash_transaction', 'payment_type', 'project_folder', 'project', 'expence_type', + 'counterparty', 'price', 'exchange_rate', 'currency', 'date', 'comment', 'audit', 'file' + ] + + def get_cash_transaction(self, obj): + return { + 'id': obj.cash_transaction.id, + 'name': obj.cash_transaction.name + } + + def get_payment_type(self, obj): + return { + 'id': obj.payment_type.id, + 'name': obj.payment_type.name + } + + def get_project_folder(self, obj): + return { + 'id': obj.project_folder.id, + 'name': obj.project_folder.name + } + + def get_project(self, obj): + return { + 'id': obj.project.id, + 'name': obj.project.name + } if obj.project else None + + def get_counterparty(self, obj): + return { + 'id': obj.counterparty.id, + 'name': obj.counterparty.name + } if obj.counterparty else None + + def get_expence_type(self, obj): + return { + 'id': obj.expence_type.id, + 'name': obj.expence_type.name + } if obj.expence_type else None diff --git a/core/apps/finance/urls.py b/core/apps/finance/urls.py index 5501783..f5f8d6d 100644 --- a/core/apps/finance/urls.py +++ b/core/apps/finance/urls.py @@ -6,6 +6,7 @@ from core.apps.finance.views import payment_type as pt_views from core.apps.finance.views import type_income as ti_views from core.apps.finance.views import income as income_views from core.apps.finance.views import expence_type as ex_views +from core.apps.finance.views import expence as expence_views urlpatterns = [ @@ -55,4 +56,10 @@ urlpatterns = [ path('/delete/', ex_views.ExpenceTypeDeleteApiView.as_view()), ] )), + path('expence/', include( + [ + path('list/', expence_views.ExpenceListApiView.as_view()), + path('create/', expence_views.ExpenceCreateApiView.as_view()), + ] + )) ] \ No newline at end of file diff --git a/core/apps/finance/views/expence.py b/core/apps/finance/views/expence.py new file mode 100644 index 0000000..714b08f --- /dev/null +++ b/core/apps/finance/views/expence.py @@ -0,0 +1,52 @@ +from rest_framework import generics, views, parsers +from rest_framework.response import Response + +from core.apps.accounts.permissions.permissions import HasRolePermission +from core.apps.finance.models import Expence +from core.apps.finance.serializers import expence as serializers + + +class ExpenceCreateApiView(generics.GenericAPIView): + serializer_class = serializers.ExpenceCreateSerializer + queryset = Expence.objects.select_related( + 'cash_transaction', 'payment_type', 'project_folder', 'project', + 'counterparty', 'expence_type', + ) + permission_classes = [HasRolePermission] + parser_classes = [parsers.FormParser, parsers.MultiPartParser] + + def post(self, request): + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(raise_exception=True): + serializer.save() + return Response( + { + 'success': True, + 'message': 'Expence created' + }, + status=201 + ) + return Response( + { + 'success': False, + 'message': 'Expence create failed', + 'error': serializer.errors + }, + status=400 + ) + + +class ExpenceListApiView(generics.GenericAPIView): + serializer_class = serializers.ExpenceListSerializer + queryset = Expence.objects.select_related( + 'cash_transaction', 'payment_type', 'project_folder', 'project', + 'counterparty', 'expence_type', + ) + permission_classes = [HasRolePermission] + + def get(self, request): + page = self.paginate_queryset(self.queryset) + if page is not None: + serializer = self.serializer_class(page, many=True) + return self.get_paginated_response(serializer.data) + \ No newline at end of file