diff --git a/core/apps/documents/migrations/0001_executor_info.py b/core/apps/documents/migrations/0001_executor_info.py new file mode 100644 index 0000000..999139f --- /dev/null +++ b/core/apps/documents/migrations/0001_executor_info.py @@ -0,0 +1,37 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="ExecutorInfoModel", + fields=[ + ("id", models.AutoField(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(blank=True, max_length=255, null=True, verbose_name="name")), + ("address", models.TextField(blank=True, null=True, verbose_name="address")), + ("account_number", models.CharField(blank=True, max_length=50, null=True, verbose_name="account number")), + ("tin", models.CharField(blank=True, max_length=20, null=True, verbose_name="STIR / TIN")), + ("bank", models.CharField(blank=True, max_length=255, null=True, verbose_name="bank")), + ("mfo", models.CharField(blank=True, max_length=20, null=True, verbose_name="MFO")), + ("oked", models.CharField(blank=True, max_length=20, null=True, verbose_name="OKED")), + ("email", models.EmailField(blank=True, max_length=254, null=True, verbose_name="email")), + ("phone", models.CharField(blank=True, max_length=255, null=True, verbose_name="phone")), + ("evaluator_full_name", models.CharField(blank=True, max_length=255, null=True, verbose_name="evaluator full name")), + ("evaluator_certificate", models.CharField(blank=True, max_length=255, null=True, verbose_name="evaluator certificate")), + ("license_info", models.TextField(blank=True, null=True, verbose_name="license info")), + ("insurance_info", models.TextField(blank=True, null=True, verbose_name="insurance info")), + ], + options={ + "verbose_name": "Executor Info", + "verbose_name_plural": "Executor Info", + "db_table": "ExecutorInfo", + }, + ), + ] diff --git a/core/apps/documents/models/__init__.py b/core/apps/documents/models/__init__.py new file mode 100644 index 0000000..d6a1b27 --- /dev/null +++ b/core/apps/documents/models/__init__.py @@ -0,0 +1 @@ +from .executor import ExecutorInfoModel # noqa diff --git a/core/apps/documents/models/executor.py b/core/apps/documents/models/executor.py new file mode 100644 index 0000000..64c6c1f --- /dev/null +++ b/core/apps/documents/models/executor.py @@ -0,0 +1,88 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ +from django_core.models import AbstractBaseModel + + +class ExecutorInfoModel(AbstractBaseModel): + name = models.CharField( + verbose_name=_("name"), + max_length=255, + blank=True, + null=True, + ) + address = models.TextField( + verbose_name=_("address"), + blank=True, + null=True, + ) + account_number = models.CharField( + verbose_name=_("account number"), + max_length=50, + blank=True, + null=True, + ) + tin = models.CharField( + verbose_name=_("STIR / TIN"), + max_length=20, + blank=True, + null=True, + ) + bank = models.CharField( + verbose_name=_("bank"), + max_length=255, + blank=True, + null=True, + ) + mfo = models.CharField( + verbose_name=_("MFO"), + max_length=20, + blank=True, + null=True, + ) + oked = models.CharField( + verbose_name=_("OKED"), + max_length=20, + blank=True, + null=True, + ) + email = models.EmailField( + verbose_name=_("email"), + blank=True, + null=True, + ) + phone = models.CharField( + verbose_name=_("phone"), + max_length=255, + blank=True, + null=True, + ) + evaluator_full_name = models.CharField( + verbose_name=_("evaluator full name"), + max_length=255, + blank=True, + null=True, + ) + evaluator_certificate = models.CharField( + verbose_name=_("evaluator certificate"), + max_length=255, + blank=True, + null=True, + ) + license_info = models.TextField( + verbose_name=_("license info"), + blank=True, + null=True, + ) + insurance_info = models.TextField( + verbose_name=_("insurance info"), + blank=True, + null=True, + ) + + def __str__(self): + return self.name or f"ExecutorInfo #{self.pk}" + + class Meta: + db_table = "ExecutorInfo" + verbose_name = _("Executor Info") + verbose_name_plural = _("Executor Info") diff --git a/core/apps/documents/serializers/executor.py b/core/apps/documents/serializers/executor.py new file mode 100644 index 0000000..a496d92 --- /dev/null +++ b/core/apps/documents/serializers/executor.py @@ -0,0 +1,27 @@ +from rest_framework import serializers + +from core.apps.documents.models import ExecutorInfoModel + + +class ExecutorInfoSerializer(serializers.ModelSerializer): + class Meta: + model = ExecutorInfoModel + fields = [ + "id", + "name", + "address", + "account_number", + "tin", + "bank", + "mfo", + "oked", + "email", + "phone", + "evaluator_full_name", + "evaluator_certificate", + "license_info", + "insurance_info", + "created_at", + "updated_at", + ] + read_only_fields = ["id", "created_at", "updated_at"] diff --git a/core/apps/documents/urls.py b/core/apps/documents/urls.py index fb367e4..900d3e8 100644 --- a/core/apps/documents/urls.py +++ b/core/apps/documents/urls.py @@ -1,7 +1,9 @@ from django.urls import path from core.apps.documents.views.contract import ValuationReportPDFView +from core.apps.documents.views.executor import ExecutorInfoView urlpatterns = [ path('generate-contract-pdf//', ValuationReportPDFView.as_view(), name='generate_contract_pdf'), + path('executor-info/', ExecutorInfoView.as_view(), name='executor_info'), ] diff --git a/core/apps/documents/views/contract.py b/core/apps/documents/views/contract.py index 88d676e..8840d6e 100644 --- a/core/apps/documents/views/contract.py +++ b/core/apps/documents/views/contract.py @@ -11,6 +11,7 @@ from weasyprint import HTML from core.apps.evaluation.models import AutoEvaluationModel from core.apps.evaluation.choices.auto import ObjectOwnerType +from core.apps.documents.models import ExecutorInfoModel from core.apps.documents.serializers.contract import ContractPDFRequestSerializer from core.services import CurrencyService @@ -194,6 +195,7 @@ class ValuationReportPDFView(APIView): contract_ctx = self._contract_context(auto, report_date) inspection_ctx = self._inspection_context(payload) rates_ctx = self._rates_context(valuation_date) + executor_ctx = self._executor_context() ctx = { "logo_url": "", @@ -211,8 +213,9 @@ class ValuationReportPDFView(APIView): "owner": owner_ctx, "contract": contract_ctx, "company": { - "director": "—", + "director": executor_ctx.get("evaluator_full_name") or "—", }, + "executor": executor_ctx, "rates": rates_ctx, "inspection": inspection_ctx, "cost": { @@ -372,6 +375,40 @@ class ValuationReportPDFView(APIView): "eur": rates.get("EUR", ""), } + def _executor_context(self): + instance = ExecutorInfoModel.objects.order_by("-created_at").first() + if not instance: + return { + "name": "", + "address": "", + "account_number": "", + "tin": "", + "bank": "", + "mfo": "", + "oked": "", + "email": "", + "phone": "", + "evaluator_full_name": "", + "evaluator_certificate": "", + "license_info": "", + "insurance_info": "", + } + return { + "name": instance.name or "", + "address": instance.address or "", + "account_number": instance.account_number or "", + "tin": instance.tin or "", + "bank": instance.bank or "", + "mfo": instance.mfo or "", + "oked": instance.oked or "", + "email": instance.email or "", + "phone": instance.phone or "", + "evaluator_full_name": instance.evaluator_full_name or "", + "evaluator_certificate": instance.evaluator_certificate or "", + "license_info": instance.license_info or "", + "insurance_info": instance.insurance_info or "", + } + def _empty_analog(self): return { "source": "", diff --git a/core/apps/documents/views/executor.py b/core/apps/documents/views/executor.py new file mode 100644 index 0000000..a03dabe --- /dev/null +++ b/core/apps/documents/views/executor.py @@ -0,0 +1,47 @@ +from rest_framework import status +from rest_framework.response import Response +from rest_framework.views import APIView + +from core.apps.documents.models import ExecutorInfoModel +from core.apps.documents.serializers.executor import ExecutorInfoSerializer + + +class ExecutorInfoView(APIView): + """Singleton endpoint — faqat bitta ExecutorInfo yozuvi bo'ladi.""" + + def get(self, request, *args, **kwargs): + instance = ExecutorInfoModel.objects.order_by("created_at").first() + if not instance: + return Response({}, status=status.HTTP_200_OK) + return Response(ExecutorInfoSerializer(instance).data) + + def post(self, request, *args, **kwargs): + if ExecutorInfoModel.objects.exists(): + return Response( + {"detail": "ExecutorInfo allaqachon mavjud. Yangilash uchun PUT/PATCH ishlating."}, + status=status.HTTP_400_BAD_REQUEST, + ) + serializer = ExecutorInfoSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + + def put(self, request, *args, **kwargs): + return self._upsert(request, partial=False) + + def patch(self, request, *args, **kwargs): + return self._upsert(request, partial=True) + + def delete(self, request, *args, **kwargs): + instance = ExecutorInfoModel.objects.order_by("created_at").first() + if not instance: + return Response(status=status.HTTP_404_NOT_FOUND) + instance.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + + def _upsert(self, request, partial): + instance = ExecutorInfoModel.objects.order_by("created_at").first() + serializer = ExecutorInfoSerializer(instance, data=request.data, partial=partial) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(serializer.data) diff --git a/core/apps/tasks/serializers/task.py b/core/apps/tasks/serializers/task.py index bef96e7..478dc09 100644 --- a/core/apps/tasks/serializers/task.py +++ b/core/apps/tasks/serializers/task.py @@ -46,6 +46,13 @@ class TaskCreateSerializer(serializers.ModelSerializer): 'labels', 'assignees', ] + extra_kwargs = { + "description": {"required": False}, + "from_date": {"required": False}, + "labels": {"required": False}, + "to_date": {"required": False}, + } + def create(self, validated_data): validated_data['created_by'] = self.context['request'].user diff --git a/core/apps/tasks/views/task.py b/core/apps/tasks/views/task.py index ef79b2a..24c0c27 100644 --- a/core/apps/tasks/views/task.py +++ b/core/apps/tasks/views/task.py @@ -16,10 +16,13 @@ class TaskCreateView(generics.GenericAPIView): @transaction.atomic def post(self, request): serializer = self.get_serializer(data=request.data) - if not serializer.is_valid(raise_exception=True): - return Response(serializer.validated_data, status=status.HTTP_400_BAD_REQUEST) + if not serializer.is_valid(): + return Response({"status": False, "data": serializer.errors}, status=status.HTTP_400_BAD_REQUEST) serializer.save() - return Response(serializer.data) + return Response({ + "status": True, + "data": serializer.data + }) @extend_schema(tags=['Tasks']) diff --git a/resources/templates/documents/contract.html b/resources/templates/documents/contract.html index 4a2f239..0e0fcf8 100644 --- a/resources/templates/documents/contract.html +++ b/resources/templates/documents/contract.html @@ -1159,65 +1159,53 @@ - + - + - + - + - + - + - + - + - + - + - +
Nomi:"SIFAT BAHOLASH" MCHJ{{ executor.name }}
Baholovchi tashkilot: - O'zbekiston Respublikasi, Toshkent shahri, Chilonzor - tumani, 7-mavze, 45-uy, 27-xonadon - {{ executor.address }}
Hisob raqami:20208 000 505 309 735 001{{ executor.account_number }}
STIR:307 930 412{{ executor.tin }}
Bank:AKIB "Ipoteka bank" Chilonzor filiali{{ executor.bank }}
MFO:00997{{ executor.mfo }}
OKED:74900{{ executor.oked }}
E-mail:sifat.baholash@gmail.com{{ executor.email }}
Telefon:(90) 535-99-99, (91) 585-77-77{{ executor.phone }}
Baholovchi: - {{ company.director }} — Baholash bo'yicha mutaxassis - sertifikati № 0988, 17.11.2021 y. + {{ executor.evaluator_full_name }}{% if executor.evaluator_certificate %} — {{ executor.evaluator_certificate }}{% endif %}
Baholash o'tkazish huquqi: - Baholovchi Tashkilotlar Assotsiatsiyasi tomonidan - berilgan a'zolik sertifikati, ro'yxatdan o'tish raqami № - 122, 01.06.2023 y. - {{ executor.license_info }}
Sug'urta polisi: - "KAFIL-SUG'URTA" AJ baholovchining professional - javobgarligi sug'urtasi polisi: № 19-01-25/0000368-2025, - 30.05.2025 dan 29.05.2026 gacha amal qiladi - {{ executor.insurance_info }}