feat: add new api and fix some problems

This commit is contained in:
xoliqberdiyev
2026-05-06 17:57:07 +05:00
parent 67558e77de
commit 6a9cf9fa1f
10 changed files with 265 additions and 28 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 django.urls import path
from core.apps.documents.views.contract import ValuationReportPDFView from core.apps.documents.views.contract import ValuationReportPDFView
from core.apps.documents.views.executor import ExecutorInfoView
urlpatterns = [ urlpatterns = [
path('generate-contract-pdf/<int:pk>/', ValuationReportPDFView.as_view(), name='generate_contract_pdf'), 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.models import AutoEvaluationModel
from core.apps.evaluation.choices.auto import ObjectOwnerType 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.apps.documents.serializers.contract import ContractPDFRequestSerializer
from core.services import CurrencyService from core.services import CurrencyService
@@ -194,6 +195,7 @@ class ValuationReportPDFView(APIView):
contract_ctx = self._contract_context(auto, report_date) contract_ctx = self._contract_context(auto, report_date)
inspection_ctx = self._inspection_context(payload) inspection_ctx = self._inspection_context(payload)
rates_ctx = self._rates_context(valuation_date) rates_ctx = self._rates_context(valuation_date)
executor_ctx = self._executor_context()
ctx = { ctx = {
"logo_url": "", "logo_url": "",
@@ -211,8 +213,9 @@ class ValuationReportPDFView(APIView):
"owner": owner_ctx, "owner": owner_ctx,
"contract": contract_ctx, "contract": contract_ctx,
"company": { "company": {
"director": "", "director": executor_ctx.get("evaluator_full_name") or "",
}, },
"executor": executor_ctx,
"rates": rates_ctx, "rates": rates_ctx,
"inspection": inspection_ctx, "inspection": inspection_ctx,
"cost": { "cost": {
@@ -372,6 +375,40 @@ class ValuationReportPDFView(APIView):
"eur": rates.get("EUR", ""), "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): def _empty_analog(self):
return { return {
"source": "", "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

@@ -46,6 +46,13 @@ class TaskCreateSerializer(serializers.ModelSerializer):
'labels', 'labels',
'assignees', 'assignees',
] ]
extra_kwargs = {
"description": {"required": False},
"from_date": {"required": False},
"labels": {"required": False},
"to_date": {"required": False},
}
def create(self, validated_data): def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user validated_data['created_by'] = self.context['request'].user

View File

@@ -16,10 +16,13 @@ class TaskCreateView(generics.GenericAPIView):
@transaction.atomic @transaction.atomic
def post(self, request): def post(self, request):
serializer = self.get_serializer(data=request.data) serializer = self.get_serializer(data=request.data)
if not serializer.is_valid(raise_exception=True): if not serializer.is_valid():
return Response(serializer.validated_data, status=status.HTTP_400_BAD_REQUEST) return Response({"status": False, "data": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
serializer.save() serializer.save()
return Response(serializer.data) return Response({
"status": True,
"data": serializer.data
})
@extend_schema(tags=['Tasks']) @extend_schema(tags=['Tasks'])

View File

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