From 7ee8531aee67facfdd15620d6a32483006c08a32 Mon Sep 17 00:00:00 2001 From: behruz-dev Date: Thu, 25 Sep 2025 16:37:41 +0500 Subject: [PATCH] add: income-expence delete api --- core/apps/finance/admin/expence.py | 7 +++- core/apps/finance/admin/income.py | 8 +++- .../finance/migrations/0025_deletedexpence.py | 29 ++++++++++++++ .../migrations/0026_expence_is_deleted.py | 18 +++++++++ .../finance/migrations/0027_deletedincome.py | 29 ++++++++++++++ .../migrations/0028_income_is_deleted.py | 18 +++++++++ core/apps/finance/models/expence.py | 12 ++++++ core/apps/finance/models/income.py | 11 ++++++ core/apps/finance/serializers/expence.py | 4 ++ core/apps/finance/serializers/income.py | 6 ++- core/apps/finance/urls.py | 2 + core/apps/finance/views/expence.py | 39 ++++++++++++++++++- core/apps/finance/views/income.py | 38 +++++++++++++++++- 13 files changed, 214 insertions(+), 7 deletions(-) create mode 100644 core/apps/finance/migrations/0025_deletedexpence.py create mode 100644 core/apps/finance/migrations/0026_expence_is_deleted.py create mode 100644 core/apps/finance/migrations/0027_deletedincome.py create mode 100644 core/apps/finance/migrations/0028_income_is_deleted.py diff --git a/core/apps/finance/admin/expence.py b/core/apps/finance/admin/expence.py index 13317c9..68a48c9 100644 --- a/core/apps/finance/admin/expence.py +++ b/core/apps/finance/admin/expence.py @@ -1,9 +1,14 @@ from django.contrib import admin -from core.apps.finance.models import Expence +from core.apps.finance.models import Expence, DeletedExpence @admin.register(Expence) class ExpenceAdmin(admin.ModelAdmin): list_display = ['id', 'price', 'cash_transaction', 'status'] + + +@admin.register(DeletedExpence) +class DeletedExpenceAdmin(admin.ModelAdmin): + list_display = ['id', 'comment', 'expence'] \ No newline at end of file diff --git a/core/apps/finance/admin/income.py b/core/apps/finance/admin/income.py index b392c2c..70d46a2 100644 --- a/core/apps/finance/admin/income.py +++ b/core/apps/finance/admin/income.py @@ -1,9 +1,15 @@ from django.contrib import admin -from core.apps.finance.models import Income +from core.apps.finance.models import Income, DeletedIncome @admin.register(Income) class IncomeAdmin(admin.ModelAdmin): list_display = ['id', 'price', 'cash_transaction'] list_filter = ['cash_transaction', 'payment_type'] + + +@admin.register(DeletedIncome) +class DeletedIncomeAdmin(admin.ModelAdmin): + list_display = ['id', 'comment', 'income'] + \ No newline at end of file diff --git a/core/apps/finance/migrations/0025_deletedexpence.py b/core/apps/finance/migrations/0025_deletedexpence.py new file mode 100644 index 0000000..0e34462 --- /dev/null +++ b/core/apps/finance/migrations/0025_deletedexpence.py @@ -0,0 +1,29 @@ +# Generated by Django 5.2.4 on 2025-09-25 16:10 + +import django.db.models.deletion +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('finance', '0024_expence_status'), + ] + + operations = [ + migrations.CreateModel( + name='DeletedExpence', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('comment', models.CharField(max_length=200)), + ('expence', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='deleted_expences', to='finance.expence')), + ], + options={ + 'verbose_name': "O'chirilgan chiqim", + 'verbose_name_plural': "O'chirilgan chiqim", + }, + ), + ] diff --git a/core/apps/finance/migrations/0026_expence_is_deleted.py b/core/apps/finance/migrations/0026_expence_is_deleted.py new file mode 100644 index 0000000..77883e7 --- /dev/null +++ b/core/apps/finance/migrations/0026_expence_is_deleted.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.4 on 2025-09-25 16:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('finance', '0025_deletedexpence'), + ] + + operations = [ + migrations.AddField( + model_name='expence', + name='is_deleted', + field=models.BooleanField(default=False), + ), + ] diff --git a/core/apps/finance/migrations/0027_deletedincome.py b/core/apps/finance/migrations/0027_deletedincome.py new file mode 100644 index 0000000..d615ff5 --- /dev/null +++ b/core/apps/finance/migrations/0027_deletedincome.py @@ -0,0 +1,29 @@ +# Generated by Django 5.2.4 on 2025-09-25 16:30 + +import django.db.models.deletion +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('finance', '0026_expence_is_deleted'), + ] + + operations = [ + migrations.CreateModel( + name='DeletedIncome', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('comment', models.CharField(max_length=200)), + ('income', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='deleted_incomes', to='finance.income')), + ], + options={ + 'verbose_name': "O'chirilgan kirim", + 'verbose_name_plural': "O'chirilgan kirimlar", + }, + ), + ] diff --git a/core/apps/finance/migrations/0028_income_is_deleted.py b/core/apps/finance/migrations/0028_income_is_deleted.py new file mode 100644 index 0000000..f7d7985 --- /dev/null +++ b/core/apps/finance/migrations/0028_income_is_deleted.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.4 on 2025-09-25 16:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('finance', '0027_deletedincome'), + ] + + operations = [ + migrations.AddField( + model_name='income', + name='is_deleted', + field=models.BooleanField(default=False), + ), + ] diff --git a/core/apps/finance/models/expence.py b/core/apps/finance/models/expence.py index dd387cf..ecf3ec8 100644 --- a/core/apps/finance/models/expence.py +++ b/core/apps/finance/models/expence.py @@ -41,6 +41,7 @@ class Expence(BaseModel): audit = models.CharField(max_length=200, null=True, blank=True) file = models.FileField(null=True, blank=True, upload_to='finance/expence/files/') status = models.CharField(max_length=20, choices=STATUS, default='PENDING', null=True, blank=True) + is_deleted = models.BooleanField(default=False) def __str__(self): return f'{self.cash_transaction} kassa uchun chiqim {self.price}' @@ -49,3 +50,14 @@ class Expence(BaseModel): verbose_name = 'chiqim' verbose_name_plural = 'chiqimlar' + +class DeletedExpence(BaseModel): + expence = models.ForeignKey(Expence, on_delete=models.CASCADE, related_name='deleted_expences') + comment = models.CharField(max_length=200) + + def __str__(self): + return f'{self.expence} is deleted' + + class Meta: + verbose_name = "O'chirilgan chiqim" + verbose_name_plural = "O'chirilgan chiqim" \ No newline at end of file diff --git a/core/apps/finance/models/income.py b/core/apps/finance/models/income.py index 19527ed..d1902a9 100644 --- a/core/apps/finance/models/income.py +++ b/core/apps/finance/models/income.py @@ -32,6 +32,7 @@ class Income(BaseModel): comment = models.TextField(null=True, blank=True) file = models.FileField(upload_to='finance/income/file/', null=True, blank=True) audit = models.CharField(max_length=200, null=True, blank=True) + is_deleted = models.BooleanField(default=False) def __str__(self): return f'{self.cash_transaction} kassa uchun kirim {self.price}' @@ -41,3 +42,13 @@ class Income(BaseModel): verbose_name_plural = 'kirimlar' +class DeletedIncome(BaseModel): + income = models.ForeignKey(Income, on_delete=models.CASCADE, related_name='deleted_incomes') + comment = models.CharField(max_length=200) + + def __str__(self): + return f'{self.income} is deleted' + + class Meta: + verbose_name = "O'chirilgan kirim" + verbose_name_plural = "O'chirilgan kirimlar" \ No newline at end of file diff --git a/core/apps/finance/serializers/expence.py b/core/apps/finance/serializers/expence.py index 175e5e6..accecc5 100644 --- a/core/apps/finance/serializers/expence.py +++ b/core/apps/finance/serializers/expence.py @@ -133,3 +133,7 @@ class ExpenceListSerializer(serializers.ModelSerializer): 'id': obj.expence_type.id, 'name': obj.expence_type.name } if obj.expence_type else None + + +class ExpenceDeleteSerializer(serializers.Serializer): + comment = serializers.CharField() \ No newline at end of file diff --git a/core/apps/finance/serializers/income.py b/core/apps/finance/serializers/income.py index 706cb17..8ae71f6 100644 --- a/core/apps/finance/serializers/income.py +++ b/core/apps/finance/serializers/income.py @@ -130,4 +130,8 @@ class IncomeCreateSerializer(serializers.ModelSerializer): cash_transaction.save() payment_type.save() - return income \ No newline at end of file + return income + + +class IncomeDeleteSerializer(serializers.Serializer): + comment = serializers.CharField() \ No newline at end of file diff --git a/core/apps/finance/urls.py b/core/apps/finance/urls.py index d723b6b..a6e1452 100644 --- a/core/apps/finance/urls.py +++ b/core/apps/finance/urls.py @@ -50,6 +50,7 @@ urlpatterns = [ path('list/', income_views.IncomeListApiView.as_view()), path('create/', income_views.IncomeCreateApiView.as_view()), path('/list/', income_views.CounterpartyIncomeListApiView.as_view()), + path('/delete/', income_views.IncomeDeleteApiView.as_view()), ] )), path('expence_type/', include( @@ -66,6 +67,7 @@ urlpatterns = [ path('create/', expence_views.ExpenceCreateApiView.as_view()), path('/list/', expence_views.CounterpartyExpenceListApiView.as_view()), path('/change-status/', expence_views.ChangeExpenceStatusApiView.as_view()), + path('/delete/', expence_views.ExpenceDeleteApiView.as_view()), ] )), path('income_contract/', include( diff --git a/core/apps/finance/views/expence.py b/core/apps/finance/views/expence.py index 8ae0d31..9319827 100644 --- a/core/apps/finance/views/expence.py +++ b/core/apps/finance/views/expence.py @@ -6,7 +6,7 @@ from rest_framework.response import Response from django_filters.rest_framework.backends import DjangoFilterBackend from core.apps.accounts.permissions.permissions import HasRolePermission -from core.apps.finance.models import Expence +from core.apps.finance.models import Expence, DeletedExpence from core.apps.finance.serializers import expence as serializers from core.apps.finance.filters.expence import ExpenceFilter from core.apps.counterparty.models import Counterparty @@ -107,4 +107,39 @@ class ChangeExpenceStatusApiView(views.APIView): 'message': 'expence status successfully updated', }, status=200 - ) \ No newline at end of file + ) + + +class ExpenceDeleteApiView(generics.GenericAPIView): + serializer_class = serializers.ExpenceDeleteSerializer + queryset = Expence.objects.all() + permission_classes = [HasRolePermission] + + def post(self, request, id): + expence = get_object_or_404(Expence, id=id) + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(raise_exception=True): + comment = serializer.validated_data.get('comment') + DeletedExpence.objects.create( + expence=expence, + comment=comment + ) + expence.is_deleted = True + if expence.currency == 'uzs': + expence.cash_transaction.expence_balance_uzs += expence.price + expence.cash_transaction.total_balance_uzs += expence.price + expence.payment_type.total_uzs += expence.price + else: + expence.cash_transaction.expence_balance_usd += expence.price + expence.cash_transaction.total_balance_usd += expence.price + expence.payment_type.total_usd += expence.price + + expence.cash_transaction.save() + expence.payment_type.save() + expence.save() + return Response( + { + 'success': True, + 'message': 'Expence deleted', + }, status=200 + ) \ No newline at end of file diff --git a/core/apps/finance/views/income.py b/core/apps/finance/views/income.py index 6a8356d..b3cc9ff 100644 --- a/core/apps/finance/views/income.py +++ b/core/apps/finance/views/income.py @@ -5,7 +5,7 @@ from rest_framework.response import Response from django_filters.rest_framework.backends import DjangoFilterBackend -from core.apps.finance.models import Income +from core.apps.finance.models import Income, DeletedIncome from core.apps.finance.serializers import income as serializers from core.apps.accounts.permissions.permissions import HasRolePermission from core.apps.finance.filters.income import IncomeFilter @@ -76,4 +76,38 @@ class CounterpartyIncomeListApiView(generics.GenericAPIView): if page is not None: ser = self.serializer_class(page, many=True) return self.get_paginated_response(ser.data) - \ No newline at end of file + + +class IncomeDeleteApiView(generics.GenericAPIView): + serializer_class = serializers.IncomeDeleteSerializer + queryset = Income.objects.all() + permission_classes = [HasRolePermission] + + def post(self, request, id): + income = get_object_or_404(Income, id=id) + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(raise_exception=True): + comment = serializer.validated_data.get('comment') + DeletedIncome.objects.create( + income=income, + comment=comment + ) + income.is_deleted = True + if income.currency == 'uzs': + income.cash_transaction.expence_balance_uzs -= income.price + income.cash_transaction.total_balance_uzs -= income.price + income.payment_type.total_uzs -= income.price + else: + income.cash_transaction.expence_balance_usd -= income.price + income.cash_transaction.total_balance_usd -= income.price + income.payment_type.total_usd -= income.price + + income.cash_transaction.save() + income.payment_type.save() + income.save() + return Response( + { + 'success': True, + 'message': 'Income deleted', + }, status=200 + ) \ No newline at end of file