behruz #149

Merged
xoliqberdiyev merged 3 commits from behruz into main 2026-05-06 12:58:03 +00:00
13 changed files with 402 additions and 27 deletions

View File

@@ -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",
},
),
]

View File

@@ -0,0 +1 @@
from .executor import ExecutorInfoModel # noqa

View File

@@ -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")

View File

@@ -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"]

View File

@@ -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/<int:pk>/', ValuationReportPDFView.as_view(), name='generate_contract_pdf'),
path('executor-info/', ExecutorInfoView.as_view(), name='executor_info'),
]

View File

@@ -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": "",

View File

@@ -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)

View File

@@ -0,0 +1,56 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("evaluation", "0048_autoevaluation_customer_fields"),
]
operations = [
migrations.AddField(
model_name="mechanicautoevaluationmodel",
name="customer_type",
field=models.IntegerField(
blank=True,
choices=[(1, "Jismoniy shaxs"), (2, "Yuridik shaxs")],
null=True,
verbose_name="customer type",
),
),
migrations.AddField(
model_name="mechanicautoevaluationmodel",
name="customer_individual_person_f_name",
field=models.CharField(blank=True, max_length=100, null=True, verbose_name="customer first name"),
),
migrations.AddField(
model_name="mechanicautoevaluationmodel",
name="customer_individual_person_l_name",
field=models.CharField(blank=True, max_length=100, null=True, verbose_name="customer last name"),
),
migrations.AddField(
model_name="mechanicautoevaluationmodel",
name="customer_individual_person_p_name",
field=models.CharField(blank=True, max_length=100, null=True, verbose_name="customer patronymic"),
),
migrations.AddField(
model_name="mechanicautoevaluationmodel",
name="customer_individual_person_passport_num",
field=models.CharField(blank=True, max_length=20, null=True, verbose_name="customer passport number"),
),
migrations.AddField(
model_name="mechanicautoevaluationmodel",
name="customer_legal_entity",
field=models.CharField(blank=True, max_length=255, null=True, verbose_name="customer legal entity name"),
),
migrations.AddField(
model_name="mechanicautoevaluationmodel",
name="customer_legal_inn",
field=models.CharField(blank=True, max_length=20, null=True, verbose_name="customer legal entity INN"),
),
migrations.AddField(
model_name="mechanicautoevaluationmodel",
name="customer_residence",
field=models.CharField(blank=True, max_length=255, null=True, verbose_name="customer residence"),
),
]

View File

@@ -216,6 +216,56 @@ class MechanicAutoEvaluationModel(AbstractBaseModel):
related_name='evaluation_mechanic_auto_rate_type'
)
# ── Step 3 — Buyurtmachi ma'lumotlari ────────────────────────────
customer_type = models.IntegerField(
verbose_name=_("customer type"),
choices=ObjectOwnerType.choices,
blank=True,
null=True,
)
customer_individual_person_f_name = models.CharField(
verbose_name=_("customer first name"),
max_length=100,
blank=True,
null=True,
)
customer_individual_person_l_name = models.CharField(
verbose_name=_("customer last name"),
max_length=100,
blank=True,
null=True,
)
customer_individual_person_p_name = models.CharField(
verbose_name=_("customer patronymic"),
max_length=100,
blank=True,
null=True,
)
customer_individual_person_passport_num = models.CharField(
verbose_name=_("customer passport number"),
max_length=20,
blank=True,
null=True,
)
customer_legal_entity = models.CharField(
verbose_name=_("customer legal entity name"),
max_length=255,
blank=True,
null=True,
)
customer_legal_inn = models.CharField(
verbose_name=_("customer legal entity INN"),
max_length=20,
blank=True,
null=True,
)
customer_residence = models.CharField(
verbose_name=_("customer residence"),
max_length=255,
blank=True,
null=True,
)
# ── Step 4 — Avtomobil ma'lumotlari ─────────────────────────────
tex_passport_serie_num = models.CharField(
verbose_name=_("tech passport series and number"),

View File

@@ -62,6 +62,14 @@ class BaseMechanicAutoevaluationSerializer(serializers.ModelSerializer):
"object_owner_legal_entity",
"object_owner_legal_inn",
"object_owner_residence",
"customer_type",
"customer_individual_person_f_name",
"customer_individual_person_l_name",
"customer_individual_person_p_name",
"customer_individual_person_passport_num",
"customer_legal_entity",
"customer_legal_inn",
"customer_residence",
"tex_passport_serie_num",
"tex_passport_files",
"rating_goal",
@@ -201,6 +209,14 @@ class UpdateMechanicAutoevaluationSerializer(serializers.ModelSerializer):
"object_owner_legal_entity",
"object_owner_legal_inn",
"object_owner_residence",
"customer_type",
"customer_individual_person_f_name",
"customer_individual_person_l_name",
"customer_individual_person_p_name",
"customer_individual_person_passport_num",
"customer_legal_entity",
"customer_legal_inn",
"customer_residence",
"value_determined",
"rate_type",
"assessment_task_type",
@@ -353,6 +369,14 @@ class CreateMechanicAutoevaluationSerializer(serializers.ModelSerializer):
"object_owner_legal_entity",
"object_owner_legal_inn",
"object_owner_residence",
"customer_type",
"customer_individual_person_f_name",
"customer_individual_person_l_name",
"customer_individual_person_p_name",
"customer_individual_person_passport_num",
"customer_legal_entity",
"customer_legal_inn",
"customer_residence",
"value_determined",
"rate_type",
"assessment_task_type",
@@ -473,6 +497,14 @@ class MechanicAutoEvaluationModelSerializer(serializers.ModelSerializer):
"object_owner_legal_entity",
"object_owner_legal_inn",
"object_owner_residence",
"customer_type",
"customer_individual_person_f_name",
"customer_individual_person_l_name",
"customer_individual_person_p_name",
"customer_individual_person_passport_num",
"customer_legal_entity",
"customer_legal_inn",
"customer_residence",
"value_determined",
"rate_type",
"assessment_task_type",

View File

@@ -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},
}
extra_kwargs = {
'labels' : {'required' : False},

View File

@@ -17,9 +17,12 @@ class TaskCreateView(generics.GenericAPIView):
def post(self, request):
serializer = self.get_serializer(data=request.data)
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
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'])

View File

@@ -1159,65 +1159,53 @@
<table class="info-table">
<tr>
<td class="label">Nomi:</td>
<td class="value">"SIFAT BAHOLASH" MCHJ</td>
<td class="value">{{ executor.name }}</td>
</tr>
<tr>
<td class="label">Baholovchi tashkilot:</td>
<td class="value">
O'zbekiston Respublikasi, Toshkent shahri, Chilonzor
tumani, 7-mavze, 45-uy, 27-xonadon
</td>
<td class="value">{{ executor.address }}</td>
</tr>
<tr>
<td class="label">Hisob raqami:</td>
<td class="value">20208 000 505 309 735 001</td>
<td class="value">{{ executor.account_number }}</td>
</tr>
<tr>
<td class="label">STIR:</td>
<td class="value">307 930 412</td>
<td class="value">{{ executor.tin }}</td>
</tr>
<tr>
<td class="label">Bank:</td>
<td class="value">AKIB "Ipoteka bank" Chilonzor filiali</td>
<td class="value">{{ executor.bank }}</td>
</tr>
<tr>
<td class="label">MFO:</td>
<td class="value">00997</td>
<td class="value">{{ executor.mfo }}</td>
</tr>
<tr>
<td class="label">OKED:</td>
<td class="value">74900</td>
<td class="value">{{ executor.oked }}</td>
</tr>
<tr>
<td class="label">E-mail:</td>
<td class="value">sifat.baholash@gmail.com</td>
<td class="value">{{ executor.email }}</td>
</tr>
<tr>
<td class="label">Telefon:</td>
<td class="value">(90) 535-99-99, (91) 585-77-77</td>
<td class="value">{{ executor.phone }}</td>
</tr>
<tr>
<td class="label">Baholovchi:</td>
<td class="value">
{{ company.director }} — Baholash bo'yicha mutaxassis
sertifikati № 0988, 17.11.2021 y.
{{ executor.evaluator_full_name }}{% if executor.evaluator_certificate %} — {{ executor.evaluator_certificate }}{% endif %}
</td>
</tr>
<tr>
<td class="label">Baholash o'tkazish huquqi:</td>
<td class="value">
Baholovchi Tashkilotlar Assotsiatsiyasi tomonidan
berilgan a'zolik sertifikati, ro'yxatdan o'tish raqami №
122, 01.06.2023 y.
</td>
<td class="value">{{ executor.license_info }}</td>
</tr>
<tr>
<td class="label">Sug'urta polisi:</td>
<td class="value">
"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
</td>
<td class="value">{{ executor.insurance_info }}</td>
</tr>
</table>