feat: add new api and fix some problems
This commit is contained in:
37
core/apps/documents/migrations/0001_executor_info.py
Normal file
37
core/apps/documents/migrations/0001_executor_info.py
Normal 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",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
1
core/apps/documents/models/__init__.py
Normal file
1
core/apps/documents/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .executor import ExecutorInfoModel # noqa
|
||||||
88
core/apps/documents/models/executor.py
Normal file
88
core/apps/documents/models/executor.py
Normal 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")
|
||||||
27
core/apps/documents/serializers/executor.py
Normal file
27
core/apps/documents/serializers/executor.py
Normal 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"]
|
||||||
@@ -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'),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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": "",
|
||||||
|
|||||||
47
core/apps/documents/views/executor.py
Normal file
47
core/apps/documents/views/executor.py
Normal 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)
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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'])
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user