add: add income model and income create and list apig
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
from .cash_transaction import *
|
from .cash_transaction import *
|
||||||
from .payment_type import *
|
from .payment_type import *
|
||||||
from .type_income import *
|
from .type_income import *
|
||||||
|
from .income import *
|
||||||
9
core/apps/finance/admin/income.py
Normal file
9
core/apps/finance/admin/income.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from core.apps.finance.models import Income
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Income)
|
||||||
|
class IncomeAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ['id', 'price', 'cash_transaction']
|
||||||
|
list_filter = ['cash_transaction', 'payment_type']
|
||||||
42
core/apps/finance/migrations/0007_income.py
Normal file
42
core/apps/finance/migrations/0007_income.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# Generated by Django 5.2.4 on 2025-09-08 16:18
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('counterparty', '0004_counterparty_status'),
|
||||||
|
('finance', '0006_typeincome'),
|
||||||
|
('projects', '0016_estimatework_employee_estimatework_end_date_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Income',
|
||||||
|
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)),
|
||||||
|
('currency', models.CharField(choices=[('uzs', 'uzs'), ('usd', 'usd')], max_length=3)),
|
||||||
|
('price', models.PositiveBigIntegerField()),
|
||||||
|
('exchange_rate', models.PositiveBigIntegerField(default=0)),
|
||||||
|
('date', models.DateField()),
|
||||||
|
('comment', models.TextField(blank=True, null=True)),
|
||||||
|
('file', models.FileField(blank=True, null=True, upload_to='finance/income/file/')),
|
||||||
|
('audit', models.CharField(max_length=200)),
|
||||||
|
('cash_transaction', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='incomes', to='finance.cashtransaction')),
|
||||||
|
('counterparty', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='incomes', to='counterparty.counterparty')),
|
||||||
|
('payment_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='incomes', to='finance.paymenttype')),
|
||||||
|
('project', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='incomes', to='projects.project')),
|
||||||
|
('project_folder', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='incomes', to='projects.projectfolder')),
|
||||||
|
('type_income', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='incomes', to='finance.typeincome')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'kirim',
|
||||||
|
'verbose_name_plural': 'kirimlar',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# Generated by Django 5.2.4 on 2025-09-08 16:19
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('counterparty', '0004_counterparty_status'),
|
||||||
|
('finance', '0007_income'),
|
||||||
|
('projects', '0016_estimatework_employee_estimatework_end_date_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='income',
|
||||||
|
name='audit',
|
||||||
|
field=models.CharField(blank=True, max_length=200, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='income',
|
||||||
|
name='counterparty',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='incomes', to='counterparty.counterparty'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='income',
|
||||||
|
name='project',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='incomes', to='projects.project'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='income',
|
||||||
|
name='type_income',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='incomes', to='finance.typeincome'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -14,13 +14,13 @@ class Income(BaseModel):
|
|||||||
'projects.ProjectFolder', on_delete=models.CASCADE, related_name='incomes'
|
'projects.ProjectFolder', on_delete=models.CASCADE, related_name='incomes'
|
||||||
)
|
)
|
||||||
project = models.ForeignKey(
|
project = models.ForeignKey(
|
||||||
'projects.Project', on_delete=models.SET_NULL, related_name='incomes', null=True
|
'projects.Project', on_delete=models.SET_NULL, related_name='incomes', null=True, blank=True
|
||||||
)
|
)
|
||||||
counterparty = models.ForeignKey(
|
counterparty = models.ForeignKey(
|
||||||
Counterparty, on_delete=models.SET_NULL, related_name='incomes', null=True
|
Counterparty, on_delete=models.SET_NULL, related_name='incomes', null=True, blank=True
|
||||||
)
|
)
|
||||||
type_income = models.ForeignKey(
|
type_income = models.ForeignKey(
|
||||||
TypeIncome, on_delete=models.SET_NULL, related_name='incomes', null=True
|
TypeIncome, on_delete=models.SET_NULL, related_name='incomes', null=True, blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
currency = models.CharField(choices=[('uzs', 'uzs'),('usd', 'usd')], max_length=3)
|
currency = models.CharField(choices=[('uzs', 'uzs'),('usd', 'usd')], max_length=3)
|
||||||
@@ -29,7 +29,7 @@ class Income(BaseModel):
|
|||||||
date = models.DateField()
|
date = models.DateField()
|
||||||
comment = models.TextField(null=True, blank=True)
|
comment = models.TextField(null=True, blank=True)
|
||||||
file = models.FileField(upload_to='finance/income/file/', null=True, blank=True)
|
file = models.FileField(upload_to='finance/income/file/', null=True, blank=True)
|
||||||
audit = models.CharField(max_length=200)
|
audit = models.CharField(max_length=200, null=True, blank=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.cash_transaction} kassa uchun kirim {self.price}'
|
return f'{self.cash_transaction} kassa uchun kirim {self.price}'
|
||||||
|
|||||||
@@ -39,16 +39,22 @@ class CashTransactionUpdateSerializer(serializers.ModelSerializer):
|
|||||||
'name', 'payment_type', 'employees', 'status', 'folder',
|
'name', 'payment_type', 'employees', 'status', 'folder',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class CashTransactionCreateSerializer(serializers.Serializer):
|
class CashTransactionCreateSerializer(serializers.Serializer):
|
||||||
payment_type_id = serializers.UUIDField()
|
payment_type_id = serializers.UUIDField()
|
||||||
employee_ids = serializers.ListSerializer(child=serializers.UUIDField())
|
employee_ids = serializers.ListSerializer(child=serializers.UUIDField(), write_only=True)
|
||||||
name = serializers.CharField()
|
name = serializers.CharField()
|
||||||
status = serializers.BooleanField()
|
status = serializers.BooleanField()
|
||||||
folder_id = serializers.UUIDField(required=False)
|
folder_id = serializers.UUIDField(required=False)
|
||||||
|
|
||||||
|
def validate_name(self, value):
|
||||||
|
if CashTransaction.objects.filter(name=value).exists():
|
||||||
|
raise serializers.ValidationError('cash transaction with this name already exists')
|
||||||
|
return value
|
||||||
|
|
||||||
def validate(self, data):
|
def validate(self, data):
|
||||||
payment_type = PaymentType.objects.filter(id=data['id']).first()
|
payment_type = PaymentType.objects.filter(id=data['payment_type_id']).first()
|
||||||
if payment_type:
|
if not payment_type:
|
||||||
raise serializers.ValidationError("Payment Type not found")
|
raise serializers.ValidationError("Payment Type not found")
|
||||||
if data.get('folder_id'):
|
if data.get('folder_id'):
|
||||||
folder = CashTransactionFolder.objects.filter(id=data.get('folder_id')).first()
|
folder = CashTransactionFolder.objects.filter(id=data.get('folder_id')).first()
|
||||||
@@ -60,7 +66,7 @@ class CashTransactionCreateSerializer(serializers.Serializer):
|
|||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
employee_ids = validated_data.pop('employee_ids')
|
employee_ids = validated_data.pop('employee_ids', [])
|
||||||
cash_transaction = CashTransaction.objects.create(
|
cash_transaction = CashTransaction.objects.create(
|
||||||
name=validated_data.get('name'),
|
name=validated_data.get('name'),
|
||||||
payment_type=validated_data.get('payment_type'),
|
payment_type=validated_data.get('payment_type'),
|
||||||
|
|||||||
97
core/apps/finance/serializers/income.py
Normal file
97
core/apps/finance/serializers/income.py
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
from django.db import transaction
|
||||||
|
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from core.apps.finance.models import Income
|
||||||
|
|
||||||
|
|
||||||
|
class IncomeListSerializer(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')
|
||||||
|
type_income = serializers.SerializerMethodField(method_name='get_type_income')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Income
|
||||||
|
fields = [
|
||||||
|
'id', 'cash_transaction', 'payment_type', 'project_folder', 'project',
|
||||||
|
'counterparty', 'type_income', 'currency', 'price', 'exchange_rate', 'date',
|
||||||
|
'comment', 'file', 'audit'
|
||||||
|
]
|
||||||
|
|
||||||
|
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_type_income(self, obj):
|
||||||
|
return {
|
||||||
|
'id': obj.type_income.id,
|
||||||
|
'name': obj.type_income.name
|
||||||
|
} if obj.type_income else None
|
||||||
|
|
||||||
|
|
||||||
|
class IncomeCreateSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Income
|
||||||
|
fields = [
|
||||||
|
'cash_transaction', 'payment_type', 'project_folder', 'project',
|
||||||
|
'counterparty', 'type_income', 'currency', 'price', 'exchange_rate', 'date',
|
||||||
|
'comment', 'file', 'audit'
|
||||||
|
]
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
with transaction.atomic():
|
||||||
|
income = Income.objects.create(
|
||||||
|
cash_transaction=validated_data['cash_transaction'],
|
||||||
|
payment_type=validated_data['payment_type'],
|
||||||
|
project_folder=validated_data['project_folder'],
|
||||||
|
project=validated_data.get('project'),
|
||||||
|
counterparty=validated_data.get('counterparty'),
|
||||||
|
type_income=validated_data.get('type_income'),
|
||||||
|
currency=validated_data.get('currency'),
|
||||||
|
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'),
|
||||||
|
date=validated_data.get('date'),
|
||||||
|
comment=validated_data.get('comment'),
|
||||||
|
file=validated_data.get('file'),
|
||||||
|
audit=validated_data.get('audit')
|
||||||
|
)
|
||||||
|
cash_transaction = income.cash_transaction
|
||||||
|
|
||||||
|
if validated_data.get('currency') == 'uzs':
|
||||||
|
cash_transaction.income_balance_uzs += income.price
|
||||||
|
cash_transaction.total_balance_uzs = cash_transaction.income_balance_uzs - cash_transaction.expence_balance_uzs
|
||||||
|
elif validated_data.get('currency') == 'usd':
|
||||||
|
cash_transaction.income_balance_usd += income.price
|
||||||
|
cash_transaction.total_balance_usd = cash_transaction.income_balance_usd - cash_transaction.expence_balance_usd
|
||||||
|
|
||||||
|
cash_transaction.save()
|
||||||
|
return income
|
||||||
@@ -4,6 +4,7 @@ from core.apps.finance.views import cash_transaction as cash_views
|
|||||||
from core.apps.finance.views import cash_transaction_folder as folder_views
|
from core.apps.finance.views import cash_transaction_folder as folder_views
|
||||||
from core.apps.finance.views import payment_type as pt_views
|
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 type_income as ti_views
|
||||||
|
from core.apps.finance.views import income as income_views
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
@@ -38,5 +39,11 @@ urlpatterns = [
|
|||||||
path('<uuid:id>/update/', ti_views.TypeIncomeUpdateApiView.as_view()),
|
path('<uuid:id>/update/', ti_views.TypeIncomeUpdateApiView.as_view()),
|
||||||
path('<uuid:id>/delete/', ti_views.TypeIncomeDeleteApiView.as_view()),
|
path('<uuid:id>/delete/', ti_views.TypeIncomeDeleteApiView.as_view()),
|
||||||
]
|
]
|
||||||
|
)),
|
||||||
|
path('income/', include(
|
||||||
|
[
|
||||||
|
path('list/', income_views.IncomeListApiView.as_view()),
|
||||||
|
path('create/', income_views.IncomeCreateApiView.as_view()),
|
||||||
|
]
|
||||||
))
|
))
|
||||||
]
|
]
|
||||||
49
core/apps/finance/views/income.py
Normal file
49
core/apps/finance/views/income.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
|
from rest_framework import generics, views, parsers
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
from core.apps.finance.models import Income
|
||||||
|
from core.apps.finance.serializers import income as serializers
|
||||||
|
from core.apps.accounts.permissions.permissions import HasRolePermission
|
||||||
|
|
||||||
|
|
||||||
|
class IncomeListApiView(generics.GenericAPIView):
|
||||||
|
serializer_class = serializers.IncomeListSerializer
|
||||||
|
queryset = Income.objects.select_related(
|
||||||
|
'cash_transaction', 'payment_type', 'project_folder', 'project', 'counterparty', 'type_income'
|
||||||
|
)
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
class IncomeCreateApiView(generics.GenericAPIView):
|
||||||
|
serializer_class = serializers.IncomeCreateSerializer
|
||||||
|
queryset = Income.objects.all()
|
||||||
|
permission_classes = [HasRolePermission]
|
||||||
|
parser_classes = [parsers.FormParser, parsers.MultiPartParser]
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
ser = self.serializer_class(data=request.data)
|
||||||
|
if ser.is_valid(raise_exception=True):
|
||||||
|
ser.save()
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
'success': True,
|
||||||
|
'message': 'income created'
|
||||||
|
},
|
||||||
|
status=201
|
||||||
|
)
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
'success': False,
|
||||||
|
'message': 'income create failed',
|
||||||
|
'error': ser.errors,
|
||||||
|
},
|
||||||
|
status=400
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user