From feecb580c1d51d76a3cc950890ae5ffd94f74702 Mon Sep 17 00:00:00 2001 From: Shaxobff Date: Fri, 1 May 2026 16:54:38 +0500 Subject: [PATCH 1/4] update --- core/apps/evaluation/choices/bonus.py | 8 +++ core/apps/evaluation/migrations/0039_bonus.py | 31 ++++++++++ ...us_bonustype_employeebonus_delete_bonus.py | 59 +++++++++++++++++++ core/apps/evaluation/models/auto.py | 5 -- core/apps/evaluation/models/bonus.py | 38 ++++++++++++ core/apps/evaluation/models/movable.py | 4 +- .../evaluation/serializers/bonus/Bonus.py | 44 ++++++++++++++ .../evaluation/serializers/bonus/__init__.py | 0 core/apps/evaluation/urls.py | 2 + core/apps/evaluation/views/__init__.py | 1 + core/apps/evaluation/views/bonus.py | 55 +++++++++++++++++ 11 files changed, 239 insertions(+), 8 deletions(-) create mode 100644 core/apps/evaluation/choices/bonus.py create mode 100644 core/apps/evaluation/migrations/0039_bonus.py create mode 100644 core/apps/evaluation/migrations/0040_basevaluebonus_bonustype_employeebonus_delete_bonus.py create mode 100644 core/apps/evaluation/models/bonus.py create mode 100644 core/apps/evaluation/serializers/bonus/Bonus.py create mode 100644 core/apps/evaluation/serializers/bonus/__init__.py create mode 100644 core/apps/evaluation/views/bonus.py diff --git a/core/apps/evaluation/choices/bonus.py b/core/apps/evaluation/choices/bonus.py new file mode 100644 index 0000000..7c13ce2 --- /dev/null +++ b/core/apps/evaluation/choices/bonus.py @@ -0,0 +1,8 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + + +class EvaluationCategory(models.TextChoices): + AUTO = "auto_transport", _("Avtotransport") + REAL_ESTATE = "real estate", _("ko'chmas mulk") + EQUIPMENT = "equipment", _("uskuna va jihozlar") diff --git a/core/apps/evaluation/migrations/0039_bonus.py b/core/apps/evaluation/migrations/0039_bonus.py new file mode 100644 index 0000000..138a813 --- /dev/null +++ b/core/apps/evaluation/migrations/0039_bonus.py @@ -0,0 +1,31 @@ +# Generated by Django 5.2.7 on 2026-05-01 06:45 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('evaluation', '0038_evaluationrequestmodel_distance_covered_and_more'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Bonus', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('updated_at', models.DateTimeField(auto_now=True)), + ('bonus_type', models.CharField(choices=[('lightweight_auto', 'Yengil automobil'), ('truck_car', 'Yuk automobil'), ('special_tech', 'Maxsus texnika')], max_length=50)), + ('percentage', models.FloatField()), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('price', models.FloatField()), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bonuses', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/core/apps/evaluation/migrations/0040_basevaluebonus_bonustype_employeebonus_delete_bonus.py b/core/apps/evaluation/migrations/0040_basevaluebonus_bonustype_employeebonus_delete_bonus.py new file mode 100644 index 0000000..8d43c63 --- /dev/null +++ b/core/apps/evaluation/migrations/0040_basevaluebonus_bonustype_employeebonus_delete_bonus.py @@ -0,0 +1,59 @@ +# Generated by Django 5.2.7 on 2026-05-01 11:43 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('evaluation', '0039_bonus'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='BaseValueBonus', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('base_price', models.DecimalField(decimal_places=2, max_digits=12)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='BonusType', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('name', models.CharField(max_length=255)), + ('category', models.CharField(choices=[('auto_transport', 'Avtotransport'), ('real estate', "ko'chmas mulk"), ('equipment', 'uskuna va jihozlar')], max_length=50)), + ('percentage', models.PositiveIntegerField()), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='EmployeeBonus', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('percentage', models.PositiveIntegerField()), + ('bonus_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='evaluation.bonustype')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bonuses', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'unique_together': {('user', 'bonus_type')}, + }, + ), + migrations.DeleteModel( + name='Bonus', + ), + ] diff --git a/core/apps/evaluation/models/auto.py b/core/apps/evaluation/models/auto.py index 799fe53..9a406f0 100644 --- a/core/apps/evaluation/models/auto.py +++ b/core/apps/evaluation/models/auto.py @@ -9,14 +9,11 @@ from core.apps.evaluation.choices.auto import ( AutoEvaluationStatus, AutoObjectType, # FormOwnership, - LocationConvenience, - LocationHighways, ObjectOwnerType, # PropertyRights, # RateType, # ValueDetermined, ) - from .valuation import ValuationModel from .vehicle import VehicleModel @@ -244,8 +241,6 @@ class AutoEvaluationModel(AbstractBaseModel): default=False, ) - - def __str__(self): return f"Auto Evaluation {self.registration_number or self.pk}" diff --git a/core/apps/evaluation/models/bonus.py b/core/apps/evaluation/models/bonus.py new file mode 100644 index 0000000..9c18840 --- /dev/null +++ b/core/apps/evaluation/models/bonus.py @@ -0,0 +1,38 @@ +from django.db import models +from django.db.models.fields import PositiveIntegerField +from django_core.models import AbstractBaseModel + +from core.apps.evaluation.choices.bonus import EvaluationCategory + + +class BaseValueBonus(AbstractBaseModel): + base_price = models.DecimalField(max_digits=12, decimal_places=2) + + def __str__(self): + return f"Base: {self.base_price}" + + def save(self, *args, **kwargs): + if not self.pk and BaseValueBonus.objects.exists(): + raise ValueError("Value Bonus already exists") + return super().save(*args, **kwargs) + + +class BonusType(AbstractBaseModel): + name = models.CharField(max_length=255) + category = models.CharField( + max_length=50, + choices=EvaluationCategory.choices + ) + percentage = PositiveIntegerField() + + def __str__(self): + return self.name + + +class EmployeeBonus(AbstractBaseModel): + user = models.ForeignKey("accounts.User", on_delete=models.CASCADE, related_name="bonuses", ) + bonus_type = models.ForeignKey(BonusType, on_delete=models.CASCADE) + percentage = models.PositiveIntegerField() + + class Meta: + unique_together = ("user", "bonus_type") diff --git a/core/apps/evaluation/models/movable.py b/core/apps/evaluation/models/movable.py index c6075db..c762f4a 100644 --- a/core/apps/evaluation/models/movable.py +++ b/core/apps/evaluation/models/movable.py @@ -3,12 +3,11 @@ from django.utils.translation import gettext_lazy as _ from django_core.models import AbstractBaseModel from model_bakery import baker - -from .valuation import ValuationModel from core.apps.evaluation.choices.movable import ( MovablePropertyCategory, MovablePropertyCondition, ) +from .valuation import ValuationModel class MovablePropertyEvaluationModel(AbstractBaseModel): @@ -51,4 +50,3 @@ class MovablePropertyEvaluationModel(AbstractBaseModel): db_table = "MovablePropertyEvaluation" verbose_name = _("Movable Property Evaluation") verbose_name_plural = _("Movable Property Evaluations") - diff --git a/core/apps/evaluation/serializers/bonus/Bonus.py b/core/apps/evaluation/serializers/bonus/Bonus.py new file mode 100644 index 0000000..51482cd --- /dev/null +++ b/core/apps/evaluation/serializers/bonus/Bonus.py @@ -0,0 +1,44 @@ +from rest_framework import serializers + +from core.apps.evaluation.models.bonus import BonusType, EmployeeBonus, BaseValueBonus + + +class BonusTypeCreateSerializer(serializers.ModelSerializer): + class Meta: + model = BonusType + fields = 'name', 'category', 'percentage' + + +class BonusTypeListSerializer(serializers.ModelSerializer): + price = serializers.SerializerMethodField() + + class Meta: + model = BonusType + fields = 'name', 'category', 'percentage' + + def get_price(self, obj): + base_obj = BaseValueBonus.objects.first() + if not base_obj: + return 0 + + return (base_obj.base_price * obj.percentage) / 100 + + +class BonusEmployeeBonusSerializer(serializers.ModelSerializer): + class Meta: + model = EmployeeBonus + fields = 'user' , 'bonus_type' , 'percentage' + + +class EmployeeBonusSerializer(serializers.ModelSerializer): + price = serializers.SerializerMethodField() + + class Meta: + model = EmployeeBonus + fields = 'user', 'bonus_type', 'percentage' + + def get_price(self, obj): + base_obj = BaseValueBonus.objects.first() + if not base_obj: + return 0 + return (base_obj.base_price * obj.percentage) / 100 diff --git a/core/apps/evaluation/serializers/bonus/__init__.py b/core/apps/evaluation/serializers/bonus/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/apps/evaluation/urls.py b/core/apps/evaluation/urls.py index 59e1718..dea563e 100644 --- a/core/apps/evaluation/urls.py +++ b/core/apps/evaluation/urls.py @@ -27,6 +27,8 @@ router.register("valuation", views.ValuationView, basename="valuation") router.register("property-owner", views.PropertyOwnerView, basename="property-owner") router.register("customer", views.CustomerView, basename="customer") router.register("certificate", views.CertificateView, basename="certificate") +router.register("bonus-type", views.BonusTypeView, basename="bonus-type") +router.register("bonus-employee", views.BonusEmployeeViewSet, basename="bonus-employee") urlpatterns = [ path("", include(router.urls)), diff --git a/core/apps/evaluation/views/__init__.py b/core/apps/evaluation/views/__init__.py index 64a46d9..5f9ff27 100644 --- a/core/apps/evaluation/views/__init__.py +++ b/core/apps/evaluation/views/__init__.py @@ -15,3 +15,4 @@ from .didox import * # noqa from .tech_passport import * # noqa from .certificate import * # noqa from .avg_cost import * +from .bonus import * \ No newline at end of file diff --git a/core/apps/evaluation/views/bonus.py b/core/apps/evaluation/views/bonus.py new file mode 100644 index 0000000..4c3d312 --- /dev/null +++ b/core/apps/evaluation/views/bonus.py @@ -0,0 +1,55 @@ +from django_core.mixins import BaseViewSetMixin +from drf_spectacular.utils import extend_schema +from rest_framework.permissions import AllowAny, IsAdminUser +from rest_framework.viewsets import ModelViewSet + +# core +from core.apps.evaluation.models.bonus import BonusType, EmployeeBonus +from core.apps.evaluation.serializers.bonus.Bonus import BonusTypeCreateSerializer, \ + BonusTypeListSerializer, EmployeeBonusSerializer, BonusEmployeeBonusSerializer + + +@extend_schema(tags=["Bonus"]) +class BonusTypeView(BaseViewSetMixin, ModelViewSet): + queryset = BonusType.objects.all() + + serializer_class = BonusTypeCreateSerializer + + action_serializer_class = { + 'create': BonusTypeCreateSerializer, + 'update': BonusTypeCreateSerializer, + 'partial_update': BonusTypeCreateSerializer, + 'list': BonusTypeListSerializer, + 'retrieve': BonusTypeListSerializer, + } + + action_permission_classes = { + 'create': [IsAdminUser], + 'update': [IsAdminUser], + 'partial_update': [IsAdminUser], + 'destroy': [IsAdminUser], + 'list': [IsAdminUser], + } + + +class BonusEmployeeViewSet(BaseViewSetMixin, ModelViewSet): + queryset = EmployeeBonus.objects.all() + serializer_class = EmployeeBonusSerializer + + + + action_serializer_class = { + 'create': BonusEmployeeBonusSerializer, + 'update': BonusEmployeeBonusSerializer, + 'partial_update': BonusEmployeeBonusSerializer, + 'list': EmployeeBonusSerializer, + 'retrieve': EmployeeBonusSerializer, + } + + action_permission_classes = { + 'create': [IsAdminUser], + 'update': [IsAdminUser], + 'partial_update': [IsAdminUser], + 'destroy': [IsAdminUser], + 'list': [IsAdminUser], + } \ No newline at end of file From 1ff23af8bff8c3baf42446381294c9f1010078f2 Mon Sep 17 00:00:00 2001 From: Shaxobff Date: Fri, 1 May 2026 17:15:01 +0500 Subject: [PATCH 2/4] update --- .../0041_rename_bonustype_bonuscategory.py | 17 ++++++++ core/apps/evaluation/models/bonus.py | 9 +--- .../evaluation/serializers/bonus/Bonus.py | 34 ++++++++++----- core/apps/evaluation/urls.py | 1 + core/apps/evaluation/views/bonus.py | 41 +++++++++++-------- 5 files changed, 66 insertions(+), 36 deletions(-) create mode 100644 core/apps/evaluation/migrations/0041_rename_bonustype_bonuscategory.py diff --git a/core/apps/evaluation/migrations/0041_rename_bonustype_bonuscategory.py b/core/apps/evaluation/migrations/0041_rename_bonustype_bonuscategory.py new file mode 100644 index 0000000..2d56cf0 --- /dev/null +++ b/core/apps/evaluation/migrations/0041_rename_bonustype_bonuscategory.py @@ -0,0 +1,17 @@ +# Generated by Django 5.2.7 on 2026-05-01 12:06 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('evaluation', '0040_basevaluebonus_bonustype_employeebonus_delete_bonus'), + ] + + operations = [ + migrations.RenameModel( + old_name='BonusType', + new_name='BonusCategory', + ), + ] diff --git a/core/apps/evaluation/models/bonus.py b/core/apps/evaluation/models/bonus.py index 9c18840..784c2da 100644 --- a/core/apps/evaluation/models/bonus.py +++ b/core/apps/evaluation/models/bonus.py @@ -11,13 +11,8 @@ class BaseValueBonus(AbstractBaseModel): def __str__(self): return f"Base: {self.base_price}" - def save(self, *args, **kwargs): - if not self.pk and BaseValueBonus.objects.exists(): - raise ValueError("Value Bonus already exists") - return super().save(*args, **kwargs) - -class BonusType(AbstractBaseModel): +class BonusCategory(AbstractBaseModel): name = models.CharField(max_length=255) category = models.CharField( max_length=50, @@ -31,7 +26,7 @@ class BonusType(AbstractBaseModel): class EmployeeBonus(AbstractBaseModel): user = models.ForeignKey("accounts.User", on_delete=models.CASCADE, related_name="bonuses", ) - bonus_type = models.ForeignKey(BonusType, on_delete=models.CASCADE) + bonus_type = models.ForeignKey(BonusCategory, on_delete=models.CASCADE) percentage = models.PositiveIntegerField() class Meta: diff --git a/core/apps/evaluation/serializers/bonus/Bonus.py b/core/apps/evaluation/serializers/bonus/Bonus.py index 51482cd..501c09a 100644 --- a/core/apps/evaluation/serializers/bonus/Bonus.py +++ b/core/apps/evaluation/serializers/bonus/Bonus.py @@ -1,20 +1,32 @@ from rest_framework import serializers -from core.apps.evaluation.models.bonus import BonusType, EmployeeBonus, BaseValueBonus +from core.apps.evaluation.models.bonus import BonusCategory, EmployeeBonus, BaseValueBonus -class BonusTypeCreateSerializer(serializers.ModelSerializer): +class BaseBonusSerializer(serializers.ModelSerializer): class Meta: - model = BonusType + model = BaseValueBonus + fields = 'id', 'base_price' + + def create(self, validated_data): + if BaseValueBonus.objects.exists(): + raise serializers.ValidationError("Base bonus already exists") + + return super().create(validated_data) + + +class BonusCategorySerializer(serializers.ModelSerializer): + class Meta: + model = BonusCategory fields = 'name', 'category', 'percentage' -class BonusTypeListSerializer(serializers.ModelSerializer): - price = serializers.SerializerMethodField() +class BonusCategoryListSerializer(serializers.ModelSerializer): + price = serializers.DecimalField(max_digits=12, decimal_places=2) class Meta: - model = BonusType - fields = 'name', 'category', 'percentage' + model = BonusCategory + fields = 'id', 'name', 'category', 'percentage' , 'price' def get_price(self, obj): base_obj = BaseValueBonus.objects.first() @@ -27,15 +39,15 @@ class BonusTypeListSerializer(serializers.ModelSerializer): class BonusEmployeeBonusSerializer(serializers.ModelSerializer): class Meta: model = EmployeeBonus - fields = 'user' , 'bonus_type' , 'percentage' + fields = 'user', 'bonus_type', 'percentage' -class EmployeeBonusSerializer(serializers.ModelSerializer): - price = serializers.SerializerMethodField() +class EmployeeBonusListSerializer(serializers.ModelSerializer): + price = serializers.DecimalField(max_digits=12, decimal_places=2) class Meta: model = EmployeeBonus - fields = 'user', 'bonus_type', 'percentage' + fields = 'id', 'user', 'bonus_type', 'percentage' , 'price' def get_price(self, obj): base_obj = BaseValueBonus.objects.first() diff --git a/core/apps/evaluation/urls.py b/core/apps/evaluation/urls.py index dea563e..a9d7247 100644 --- a/core/apps/evaluation/urls.py +++ b/core/apps/evaluation/urls.py @@ -29,6 +29,7 @@ router.register("customer", views.CustomerView, basename="customer") router.register("certificate", views.CertificateView, basename="certificate") router.register("bonus-type", views.BonusTypeView, basename="bonus-type") router.register("bonus-employee", views.BonusEmployeeViewSet, basename="bonus-employee") +router.register("bonus-base", views.BaseBonusViewSet, basename="bonus-base") urlpatterns = [ path("", include(router.urls)), diff --git a/core/apps/evaluation/views/bonus.py b/core/apps/evaluation/views/bonus.py index 4c3d312..3e294a8 100644 --- a/core/apps/evaluation/views/bonus.py +++ b/core/apps/evaluation/views/bonus.py @@ -1,26 +1,33 @@ from django_core.mixins import BaseViewSetMixin from drf_spectacular.utils import extend_schema -from rest_framework.permissions import AllowAny, IsAdminUser +from rest_framework import viewsets +from rest_framework.permissions import IsAdminUser from rest_framework.viewsets import ModelViewSet # core -from core.apps.evaluation.models.bonus import BonusType, EmployeeBonus -from core.apps.evaluation.serializers.bonus.Bonus import BonusTypeCreateSerializer, \ - BonusTypeListSerializer, EmployeeBonusSerializer, BonusEmployeeBonusSerializer +from core.apps.evaluation.models.bonus import BonusCategory, EmployeeBonus, BaseValueBonus +from core.apps.evaluation.serializers.bonus.Bonus import BonusCategorySerializer, \ + BonusCategoryListSerializer, EmployeeBonusListSerializer, BonusEmployeeBonusSerializer, BaseBonusSerializer -@extend_schema(tags=["Bonus"]) +@extend_schema(tags=["BaseBonus"]) +class BaseBonusViewSet(BaseViewSetMixin, viewsets.ModelViewSet): + queryset = BaseValueBonus.objects.all() + serializer_class = BaseBonusSerializer + + +@extend_schema(tags=["Bonus-Category"]) class BonusTypeView(BaseViewSetMixin, ModelViewSet): - queryset = BonusType.objects.all() + queryset = BonusCategory.objects.all() - serializer_class = BonusTypeCreateSerializer + serializer_class = BonusCategorySerializer action_serializer_class = { - 'create': BonusTypeCreateSerializer, - 'update': BonusTypeCreateSerializer, - 'partial_update': BonusTypeCreateSerializer, - 'list': BonusTypeListSerializer, - 'retrieve': BonusTypeListSerializer, + 'create': BonusCategorySerializer, + 'update': BonusCategorySerializer, + 'partial_update': BonusCategorySerializer, + 'list': BonusCategoryListSerializer, + 'retrieve': BonusCategoryListSerializer, } action_permission_classes = { @@ -34,16 +41,14 @@ class BonusTypeView(BaseViewSetMixin, ModelViewSet): class BonusEmployeeViewSet(BaseViewSetMixin, ModelViewSet): queryset = EmployeeBonus.objects.all() - serializer_class = EmployeeBonusSerializer - - + serializer_class = BonusEmployeeBonusSerializer action_serializer_class = { 'create': BonusEmployeeBonusSerializer, 'update': BonusEmployeeBonusSerializer, 'partial_update': BonusEmployeeBonusSerializer, - 'list': EmployeeBonusSerializer, - 'retrieve': EmployeeBonusSerializer, + 'list': EmployeeBonusListSerializer, + 'retrieve': EmployeeBonusListSerializer, } action_permission_classes = { @@ -52,4 +57,4 @@ class BonusEmployeeViewSet(BaseViewSetMixin, ModelViewSet): 'partial_update': [IsAdminUser], 'destroy': [IsAdminUser], 'list': [IsAdminUser], - } \ No newline at end of file + } From 0c9c7267567976e4bddc8f33ed92151a5d17a956 Mon Sep 17 00:00:00 2001 From: Shaxobff Date: Mon, 4 May 2026 16:15:12 +0500 Subject: [PATCH 3/4] add generation_pdf pdf , fix 500 error , install reportlab --- .../evaluation/serializers/vehicle/Vehicle.py | 26 ++++ core/apps/evaluation/urls.py | 1 + core/apps/evaluation/views/vehicle.py | 25 +++- core/apps/tasks/urls.py | 2 +- core/apps/tasks/views/task.py | 9 +- core/utils/generation_pdf.py | 138 ++++++++++++++++++ requirements.txt | 4 +- 7 files changed, 195 insertions(+), 10 deletions(-) create mode 100644 core/utils/generation_pdf.py diff --git a/core/apps/evaluation/serializers/vehicle/Vehicle.py b/core/apps/evaluation/serializers/vehicle/Vehicle.py index 7593e1a..67d99e7 100644 --- a/core/apps/evaluation/serializers/vehicle/Vehicle.py +++ b/core/apps/evaluation/serializers/vehicle/Vehicle.py @@ -87,3 +87,29 @@ class CreateVehicleSerializer(serializers.ModelSerializer): "condition", "position", ] + + +class VehicleApplicationSerializer(serializers.Serializer): + person_name = serializers.CharField() + property_owner = serializers.CharField(max_length=100) + address = serializers.CharField(max_length=255) + marka = serializers.CharField(max_length=100) + model = serializers.CharField(max_length=100) + configuration = serializers.CharField(max_length=100) + auto_number = serializers.CharField(max_length=100) + date_created = serializers.DateTimeField() + mileage = serializers.IntegerField() + vehicle_identification = serializers.CharField(max_length=100) + engine_number = serializers.CharField(max_length=100) + colour = serializers.CharField(max_length=100) + registration_certificate_series = serializers.CharField(max_length=100) + tec_passport_number = serializers.CharField(max_length=100) + tec_passport_date = serializers.DateTimeField() + tec_passport_place = serializers.CharField(max_length=255) + body_type = serializers.CharField(max_length=100) + chassis = serializers.CharField(max_length=100) + plate = serializers.CharField(max_length=100) + value_type = serializers.CharField(max_length=100) + evaluation_purpose = serializers.CharField(max_length=100) + personal_id_number = serializers.IntegerField() + id_number = serializers.CharField(max_length=9) diff --git a/core/apps/evaluation/urls.py b/core/apps/evaluation/urls.py index a9d7247..7e64e0b 100644 --- a/core/apps/evaluation/urls.py +++ b/core/apps/evaluation/urls.py @@ -88,4 +88,5 @@ urlpatterns = [ )), path("calculate_avg_cost/", views.AvgCostAPIView.as_view()), + path("vehicle_document/", views.GeneratePDFView.as_view()), ] diff --git a/core/apps/evaluation/views/vehicle.py b/core/apps/evaluation/views/vehicle.py index b250b7a..f8b956e 100644 --- a/core/apps/evaluation/views/vehicle.py +++ b/core/apps/evaluation/views/vehicle.py @@ -1,16 +1,19 @@ # django core +from django.http import HttpResponse from django_core.mixins import BaseViewSetMixin - # swagger from drf_spectacular.utils import extend_schema - # rest framework from rest_framework.permissions import AllowAny +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework.views import APIView from rest_framework.viewsets import ReadOnlyModelViewSet # core apps from core.apps.evaluation.models import VehicleModel -from core.apps.evaluation.serializers import vehicle as serialziers +from core.apps.evaluation.serializers import vehicle as serialziers, VehicleApplicationSerializer +from core.utils.generation_pdf import PDFService @extend_schema(tags=["Vehicle"]) @@ -27,3 +30,19 @@ class VehicleView(BaseViewSetMixin, ReadOnlyModelViewSet): "retrieve": serialziers.RetrieveVehicleSerializer, "create": serialziers.CreateVehicleSerializer, } + + +@extend_schema(tags=['GenerationDocument'], request=VehicleApplicationSerializer) +class GeneratePDFView(APIView): + permission_classes = [IsAuthenticated] + + def post(self, request): + serializer = VehicleApplicationSerializer(data=request.data) + if not serializer.is_valid(): + return Response(serializer.errors, status=400) + + pdf_buffer = PDFService.generate_vehicle_pdf(serializer.validated_data) + + response = HttpResponse(pdf_buffer, content_type='application/pdf') + response['Content-Disposition'] = 'attachment; filename="ariza.pdf"' + return response diff --git a/core/apps/tasks/urls.py b/core/apps/tasks/urls.py index 29d59af..662b7ac 100644 --- a/core/apps/tasks/urls.py +++ b/core/apps/tasks/urls.py @@ -20,7 +20,7 @@ urlpatterns = [ path('task/', include( [ path('list/', task.TaskListView.as_view()), - path('/', task.TaskDetailView.as_view()), + path('/', task.TaskDetailView.as_view()), path('create/', task.TaskCreateView.as_view()), ] )), diff --git a/core/apps/tasks/views/task.py b/core/apps/tasks/views/task.py index 22dc730..ef79b2a 100644 --- a/core/apps/tasks/views/task.py +++ b/core/apps/tasks/views/task.py @@ -1,9 +1,7 @@ from django.db import transaction - -from rest_framework import permissions, generics -from rest_framework.response import Response - from drf_spectacular.utils import extend_schema +from rest_framework import permissions, generics, status +from rest_framework.response import Response from core.apps.tasks.models.task import Task from core.apps.tasks.serializers.task import TaskSerializer, TaskCreateSerializer @@ -18,7 +16,8 @@ class TaskCreateView(generics.GenericAPIView): @transaction.atomic def post(self, request): serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) + if not serializer.is_valid(raise_exception=True): + return Response(serializer.validated_data, status=status.HTTP_400_BAD_REQUEST) serializer.save() return Response(serializer.data) diff --git a/core/utils/generation_pdf.py b/core/utils/generation_pdf.py new file mode 100644 index 0000000..546719b --- /dev/null +++ b/core/utils/generation_pdf.py @@ -0,0 +1,138 @@ +# services.py +from io import BytesIO + +from reportlab.lib import colors +from reportlab.lib.enums import TA_RIGHT, TA_CENTER +from reportlab.lib.pagesizes import A4 +from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle +from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer + + +class PDFService: + @staticmethod + def generate_vehicle_pdf(data): + buffer = BytesIO() + doc = SimpleDocTemplate( + buffer, pagesize=A4, + rightMargin=40, leftMargin=40, + topMargin=40, bottomMargin=40 + ) + styles = getSampleStyleSheet() + + cell_style = ParagraphStyle( + 'CellStyle', parent=styles['Normal'], + fontSize=9, leading=11 + ) + cell_style_bold = ParagraphStyle( + 'CellStyleBold', parent=cell_style, + fontName='Helvetica-Bold', + textColor=colors.red + ) + header_style = ParagraphStyle( + 'HeaderStyle', parent=styles['Normal'], + alignment=TA_RIGHT, fontSize=10, leading=12 + ) + title_style = ParagraphStyle( + 'TitleStyle', parent=styles['Normal'], + alignment=TA_CENTER, fontSize=14, leading=16 + ) + + elements = [] + + header_text = ( + f'"Sifat baholash" MChJ direktori
' + f"T.R.To'rayevga


" + f"{data.get('address', '')}
" + f"ro'yxatda turuvchi fuqaro
" + f"{data.get('person_name', '')} tomonidan
" + f"Avtotransport vositasini baholash uchun" + ) + elements.append(Paragraph(header_text, header_style)) + elements.append(Spacer(1, 25)) + + elements.append(Paragraph("A R I Z A", title_style)) + elements.append(Spacer(1, 10)) + elements.append(Paragraph( + "Ushbu orqali quyidagi avtotransport vositasini baholab berishingizni so'rayman:", + cell_style + )) + elements.append(Spacer(1, 10)) + + date_created = data.get('date_created') + year_str = str(date_created.year) + " yil" if date_created else "" + + tec_date = data.get('tec_passport_date') + tec_date_str = tec_date.strftime('%d.%m.%Y yil') if tec_date else "" + + raw_data = [ + ["Mulk egasi", data.get('property_owner', '')], + ["Manzil", data.get('address', '')], + ["Marka", data.get('marka', '')], + ["Model", data.get('model', '')], + ["Komplektatsiya", data.get('configuration', '')], + ["Davlat raqami", data.get('auto_number', '')], + ["Ishlab chiqarilgan yili", year_str], + ["Bosib o'tgan masofasi", f"{data.get('mileage', 0):,}".replace(',', ' ')], + ["№ kuzov (VIN)", data.get('vehicle_identification', '')], + ["№ dvigatel", data.get('engine_number', '')], + ["Rang", data.get('colour', '')], + ["Texnik passport seriyasi", data.get('registration_certificate_series', '')], + ["Texnik passport raqami", data.get('tec_passport_number', '')], + ["Texnik passport berilgan sanasi", tec_date_str], + ["Texnik passport berilgan joyi", data.get('tec_passport_place', '')], + ["Kuzov turi", data.get('body_type', '')], + ["Shassi", data.get('chassis', '')], + ["Davlat belgisi (plate)", data.get('plate', '')], # ← was missing + ] + + table_data = [ + [Paragraph(row[0], cell_style), Paragraph(str(row[1]), cell_style_bold)] + for row in raw_data + ] + table = Table(table_data, colWidths=[170, 345]) + table.setStyle(TableStyle([ + ('GRID', (0, 0), (-1, -1), 0.5, colors.black), + ('VALIGN', (0, 0), (-1, -1), 'TOP'), + ('LEFTPADDING', (0, 0), (-1, -1), 6), + ('TOPPADDING', (0, 0), (-1, -1), 4), + ('BOTTOMPADDING', (0, 0), (-1, -1), 4), + ])) + elements.append(table) + elements.append(Spacer(1, 15)) + + elements.append(Paragraph("Baholash maqsadi:", cell_style)) + purpose_raw = [ + ["Aniqlanayotgan qiymat turi", data.get('value_type', '')], + ["Baholash maqsadi", data.get('evaluation_purpose', '')], + ] + purpose_data = [ + [Paragraph(r[0], cell_style), Paragraph(str(r[1]), cell_style_bold)] + for r in purpose_raw + ] + pt = Table(purpose_data, colWidths=[170, 345]) + pt.setStyle(TableStyle([ + ('GRID', (0, 0), (-1, -1), 0.5, colors.black), + ('VALIGN', (0, 0), (-1, -1), 'TOP'), + ])) + elements.append(pt) + elements.append(Spacer(1, 15)) + + elements.append(Paragraph("Buyurtmachi rekvizitlari:", cell_style)) + footer_raw = [ + ["Shaxsiy raqam (PINFL)", str(data.get('personal_id_number', ''))], + ["ID karta raqami", data.get('id_number', '')], + ] + footer_data = [ + [Paragraph(f[0], cell_style), Paragraph(str(f[1]), cell_style_bold)] + for f in footer_raw + ] + ft = Table(footer_data, colWidths=[170, 345]) + ft.setStyle(TableStyle([ + ('GRID', (0, 0), (-1, -1), 0.5, colors.black), + ('VALIGN', (0, 0), (-1, -1), 'TOP'), + ])) + elements.append(ft) + + doc.build(elements) + buffer.seek(0) + return buffer \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 9c56fdb..97411b8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -49,4 +49,6 @@ boto3 grpcio>=1.62.0 grpcio-tools>=1.62.0 -protobuf>=4.25.0 \ No newline at end of file +protobuf>=4.25.0 + +reportlab \ No newline at end of file From 0c622759ccf05cc6820922bffa9b5bc1ba0ac8fd Mon Sep 17 00:00:00 2001 From: Shaxobff Date: Mon, 4 May 2026 17:41:50 +0500 Subject: [PATCH 4/4] update --- core/apps/evaluation/choices/bonus.py | 8 -------- .../0042_alter_bonuscategory_category.py | 18 ++++++++++++++++++ core/apps/evaluation/models/bonus.py | 4 ++-- .../apps/evaluation/serializers/bonus/Bonus.py | 10 +++++----- core/apps/tasks/serializers/comment.py | 15 +++++---------- 5 files changed, 30 insertions(+), 25 deletions(-) delete mode 100644 core/apps/evaluation/choices/bonus.py create mode 100644 core/apps/evaluation/migrations/0042_alter_bonuscategory_category.py diff --git a/core/apps/evaluation/choices/bonus.py b/core/apps/evaluation/choices/bonus.py deleted file mode 100644 index 7c13ce2..0000000 --- a/core/apps/evaluation/choices/bonus.py +++ /dev/null @@ -1,8 +0,0 @@ -from django.db import models -from django.utils.translation import gettext_lazy as _ - - -class EvaluationCategory(models.TextChoices): - AUTO = "auto_transport", _("Avtotransport") - REAL_ESTATE = "real estate", _("ko'chmas mulk") - EQUIPMENT = "equipment", _("uskuna va jihozlar") diff --git a/core/apps/evaluation/migrations/0042_alter_bonuscategory_category.py b/core/apps/evaluation/migrations/0042_alter_bonuscategory_category.py new file mode 100644 index 0000000..d773dda --- /dev/null +++ b/core/apps/evaluation/migrations/0042_alter_bonuscategory_category.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.7 on 2026-05-04 12:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('evaluation', '0041_rename_bonustype_bonuscategory'), + ] + + operations = [ + migrations.AlterField( + model_name='bonuscategory', + name='category', + field=models.CharField(choices=[('lightweight_auto', 'Yengil automobil'), ('truck_car', 'Yuk automobil'), ('special_tech', 'Maxsus texnika')], max_length=50), + ), + ] diff --git a/core/apps/evaluation/models/bonus.py b/core/apps/evaluation/models/bonus.py index 784c2da..8e51220 100644 --- a/core/apps/evaluation/models/bonus.py +++ b/core/apps/evaluation/models/bonus.py @@ -2,7 +2,7 @@ from django.db import models from django.db.models.fields import PositiveIntegerField from django_core.models import AbstractBaseModel -from core.apps.evaluation.choices.bonus import EvaluationCategory +from core.apps.evaluation.choices.auto import AutoObjectType class BaseValueBonus(AbstractBaseModel): @@ -16,7 +16,7 @@ class BonusCategory(AbstractBaseModel): name = models.CharField(max_length=255) category = models.CharField( max_length=50, - choices=EvaluationCategory.choices + choices=AutoObjectType.choices ) percentage = PositiveIntegerField() diff --git a/core/apps/evaluation/serializers/bonus/Bonus.py b/core/apps/evaluation/serializers/bonus/Bonus.py index 501c09a..3f181ef 100644 --- a/core/apps/evaluation/serializers/bonus/Bonus.py +++ b/core/apps/evaluation/serializers/bonus/Bonus.py @@ -6,7 +6,7 @@ from core.apps.evaluation.models.bonus import BonusCategory, EmployeeBonus, Base class BaseBonusSerializer(serializers.ModelSerializer): class Meta: model = BaseValueBonus - fields = 'id', 'base_price' + fields = ['id', 'base_price'] def create(self, validated_data): if BaseValueBonus.objects.exists(): @@ -18,7 +18,7 @@ class BaseBonusSerializer(serializers.ModelSerializer): class BonusCategorySerializer(serializers.ModelSerializer): class Meta: model = BonusCategory - fields = 'name', 'category', 'percentage' + fields = ['name', 'category', 'percentage'] class BonusCategoryListSerializer(serializers.ModelSerializer): @@ -26,7 +26,7 @@ class BonusCategoryListSerializer(serializers.ModelSerializer): class Meta: model = BonusCategory - fields = 'id', 'name', 'category', 'percentage' , 'price' + fields = ['id', 'name', 'category', 'percentage' , 'price'] def get_price(self, obj): base_obj = BaseValueBonus.objects.first() @@ -39,7 +39,7 @@ class BonusCategoryListSerializer(serializers.ModelSerializer): class BonusEmployeeBonusSerializer(serializers.ModelSerializer): class Meta: model = EmployeeBonus - fields = 'user', 'bonus_type', 'percentage' + fields = ['user', 'bonus_type', 'percentage'] class EmployeeBonusListSerializer(serializers.ModelSerializer): @@ -47,7 +47,7 @@ class EmployeeBonusListSerializer(serializers.ModelSerializer): class Meta: model = EmployeeBonus - fields = 'id', 'user', 'bonus_type', 'percentage' , 'price' + fields = ['id', 'user', 'bonus_type', 'percentage' , 'price'] def get_price(self, obj): base_obj = BaseValueBonus.objects.first() diff --git a/core/apps/tasks/serializers/comment.py b/core/apps/tasks/serializers/comment.py index 3c053af..0d9202d 100644 --- a/core/apps/tasks/serializers/comment.py +++ b/core/apps/tasks/serializers/comment.py @@ -30,15 +30,10 @@ class CommentCreateSerializer(serializers.ModelSerializer): 'id', 'message', 'file', 'type', 'task' ] - def validate(self, data): - task = Task.objects.filter(id=data['task']).first() - if not task: - raise serializers.ValidationError("Task not found") - data['task'] = task - return data - def create(self, validated_data): with transaction.atomic(): - task = validated_data.pop('task') - comment = Comment.objects.create(task=task, created_by=self.context['request'].user, **validated_data) - return comment + comment = Comment.objects.create( + created_by=self.context['request'].user, + **validated_data + ) + return comment \ No newline at end of file