From 80a1f5ff17363279d1656d4dd417470ab668d59d Mon Sep 17 00:00:00 2001 From: xoliqberdiyev Date: Tue, 5 May 2026 18:51:24 +0500 Subject: [PATCH] feat: wire contract PDF context and align MechanicAuto with AutoEvaluation - contract PDF: map report/customer/owner/contract from AutoEvaluationModel fields, accept inspection via POST serializer, fetch CBU.uz currency rates - MechanicAutoEvaluation: add distance_covered, object_owner_residence and car_position/body_type/fuel_type/state_car/assessment_task_type FKs; drop car_type and single tex_passport_file in favour of multi-file FK model Co-Authored-By: Claude Opus 4.7 (1M context) --- core/apps/documents/serializers/__init__.py | 0 core/apps/documents/serializers/contract.py | 13 + core/apps/documents/services/__init__.py | 0 core/apps/documents/services/cbu_rates.py | 33 ++ core/apps/documents/views/contract.py | 281 +++++++++--------- ...hanicauto_fields_and_multi_tex_passport.py | 118 ++++++++ core/apps/evaluation/models/mechanic_auto.py | 82 ++++- .../auto/MechanicAutoEvaluation.py | 153 +++++++++- resources/templates/documents/contract.html | 2 +- task.txt | 42 ++- 10 files changed, 560 insertions(+), 164 deletions(-) create mode 100644 core/apps/documents/serializers/__init__.py create mode 100644 core/apps/documents/serializers/contract.py create mode 100644 core/apps/documents/services/__init__.py create mode 100644 core/apps/documents/services/cbu_rates.py create mode 100644 core/apps/evaluation/migrations/0046_mechanicauto_fields_and_multi_tex_passport.py diff --git a/core/apps/documents/serializers/__init__.py b/core/apps/documents/serializers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/apps/documents/serializers/contract.py b/core/apps/documents/serializers/contract.py new file mode 100644 index 0000000..aaecd28 --- /dev/null +++ b/core/apps/documents/serializers/contract.py @@ -0,0 +1,13 @@ +from rest_framework import serializers + + +class InspectionSerializer(serializers.Serializer): + tires = serializers.CharField(required=False, allow_blank=True, default="Qoniqarli") + engine = serializers.CharField(required=False, allow_blank=True, default="Qoniqarli") + chassis = serializers.CharField(required=False, allow_blank=True, default="Qoniqarli") + transmission = serializers.CharField(required=False, allow_blank=True, default="Qoniqarli") + body = serializers.CharField(required=False, allow_blank=True, default="Qoniqarli") + + +class ContractPDFRequestSerializer(serializers.Serializer): + inspection = InspectionSerializer(required=False) diff --git a/core/apps/documents/services/__init__.py b/core/apps/documents/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/apps/documents/services/cbu_rates.py b/core/apps/documents/services/cbu_rates.py new file mode 100644 index 0000000..5ef4ed6 --- /dev/null +++ b/core/apps/documents/services/cbu_rates.py @@ -0,0 +1,33 @@ +from datetime import date + +import requests + +CBU_URL = "https://cbu.uz/oz/arkhiv-kursov-valyut/json/{code}/{date}/" +TIMEOUT_SECONDS = 5 +CURRENCY_CODES = ("USD", "EUR", "RUB") + + +def fetch_rates(target_date): + """CBU.uz dan berilgan sanaga oid USD, EUR, RUB kurslarini olish. + + Tarmoq xatosi yoki notogri javob bolsa bosh dict qaytadi. + """ + if target_date is None: + target_date = date.today() + date_str = target_date.strftime("%Y-%m-%d") + rates = {} + for code in CURRENCY_CODES: + try: + resp = requests.get( + CBU_URL.format(code=code, date=date_str), + timeout=TIMEOUT_SECONDS, + ) + resp.raise_for_status() + data = resp.json() + if isinstance(data, list) and data: + rate_value = data[0].get("Rate") + if rate_value: + rates[code] = rate_value + except (requests.RequestException, ValueError): + continue + return rates diff --git a/core/apps/documents/views/contract.py b/core/apps/documents/views/contract.py index 4222f05..9a8aaa6 100644 --- a/core/apps/documents/views/contract.py +++ b/core/apps/documents/views/contract.py @@ -10,6 +10,9 @@ from rest_framework import status from weasyprint import HTML from core.apps.evaluation.models import AutoEvaluationModel +from core.apps.evaluation.choices.auto import ObjectOwnerType +from core.apps.documents.serializers.contract import ContractPDFRequestSerializer +from core.apps.documents.services.cbu_rates import fetch_rates UZ_MONTHS = { @@ -27,6 +30,14 @@ UZ_TENS = [ "oltmish", "yetmish", "sakson", "to'qson", ] +DEFAULT_INSPECTION = { + "tires": "Qoniqarli", + "engine": "Qoniqarli", + "chassis": "Qoniqarli", + "transmission": "Qoniqarli", + "body": "Qoniqarli", +} + def _format_currency(value): if value is None: @@ -97,33 +108,44 @@ class ValuationReportPDFView(APIView): """ Baholash hisobotini PDF formatida yuklab olish uchun API. - GET /api/documents/generate-contract-pdf// - pk — AutoEvaluationModel id si. + GET /api/documents/generate-contract-pdf// + POST /api/documents/generate-contract-pdf// + + POST so'rov tanasida inspection malumotlarini yuborish mumkin: + { + "inspection": { + "tires": "...", + "engine": "...", + "chassis": "...", + "transmission": "...", + "body": "..." + } + } """ def get(self, request, pk, *args, **kwargs): - return self._generate_pdf(request, pk) + return self._generate_pdf(request, pk, payload={}) def post(self, request, pk, *args, **kwargs): - return self._generate_pdf(request, pk) + serializer = ContractPDFRequestSerializer(data=request.data or {}) + serializer.is_valid(raise_exception=True) + return self._generate_pdf(request, pk, payload=serializer.validated_data) - def _generate_pdf(self, request, pk): + def _generate_pdf(self, request, pk, payload): auto_evaluation = get_object_or_404( AutoEvaluationModel.objects.select_related( + "user", "vehicle", "vehicle__brand", "vehicle__model", "vehicle__color", "vehicle__fuel_type", "vehicle__body_type", - "valuation", - "valuation__customer", - "valuation__property_owner", ), pk=pk, ) - context = self._build_context(auto_evaluation) + context = self._build_context(auto_evaluation, payload) html_string = render_to_string("documents/contract.html", context) base_url = request.build_absolute_uri("/") @@ -144,95 +166,34 @@ class ValuationReportPDFView(APIView): response["Content-Length"] = len(pdf_file) return response - def _build_context(self, auto): + def _build_context(self, auto, payload): vehicle = auto.vehicle - valuation = auto.valuation - customer = valuation.customer if valuation else None - owner = valuation.property_owner if valuation and valuation.property_owner else customer - report = getattr(valuation, "report", None) if valuation else None + user = auto.user - report_date = ( - auto.rate_report_date - or auto.contract_date - or (report.created_at.date() if report else None) - or date.today() - ) + report_date = auto.rate_report_date or auto.contract_date or date.today() valuation_date = auto.rate_date or report_date inspection_date = auto.object_inspection_date or report_date - report_number = ( - (report.report_number if report else None) - or auto.registration_number - or (valuation.conclusion_number if valuation else None) - or f"{auto.pk}/{report_date.year}" - ) + report_number = auto.registration_number or f"{auto.pk}/{report_date.year}" + # Bozor qiymati hozircha hisoblanmagan — default 0. final_value = None - if report and report.final_value is not None: - final_value = report.final_value - elif valuation and valuation.final_price is not None: - final_value = valuation.final_price - elif valuation and valuation.estimated_price is not None: - final_value = valuation.estimated_price - market_value_formatted = ( f"{_format_currency(final_value)} so'm" if final_value is not None else "0 so'm" ) market_value_words = ( - f"{_number_to_uzbek_words(final_value)} so'm" - if final_value is not None - else "" + f"{_number_to_uzbek_words(final_value)} so'm" if final_value is not None else "" ) cost_final = final_value comparative_final = final_value - brand_name = "" - model_name = "" - if vehicle: - brand_name = vehicle.brand.name if vehicle.brand else "" - model_name = vehicle.model.name if vehicle.model else "" - if not brand_name: - brand_name = auto.car_brand or "" - if not model_name: - model_name = auto.car_model or "" - full_brand = f"{brand_name} {model_name}".strip() - - plate_number = (vehicle.license_plate if vehicle else None) or auto.car_number or "" - manufacture_year = "" - if vehicle and vehicle.manufacture_year: - manufacture_year = str(vehicle.manufacture_year) - elif auto.manufacture_year: - manufacture_year = str(auto.manufacture_year) - - production_date = f"{manufacture_year}-yil" if manufacture_year else "" - engine_number = (vehicle.engine_number if vehicle else None) or auto.car_dvigatel_number or "" - body_number = vehicle.vin_number if vehicle and vehicle.vin_number else "" - color_value = "" - if vehicle and vehicle.color: - color_value = vehicle.color.name - elif auto.car_color: - color_value = auto.car_color - fuel_type_value = "" - if vehicle and vehicle.fuel_type: - fuel_type_value = vehicle.fuel_type.name - - tech_passport_value = "" - if vehicle and (vehicle.tech_passport_series or vehicle.tech_passport_number): - tech_passport_value = ( - f"{vehicle.tech_passport_series or ''} № {vehicle.tech_passport_number or ''}" - ).strip() - elif auto.tex_passport_serie_num: - tech_passport_value = auto.tex_passport_serie_num - - customer_ctx = self._customer_context(customer) - owner_ctx = self._owner_context(owner) - if not owner_ctx["name"]: - owner_ctx = customer_ctx - + vehicle_ctx = self._vehicle_context(auto, vehicle) + customer_ctx = self._customer_context(user) + owner_ctx = self._owner_context(auto) contract_ctx = self._contract_context(auto, report_date) - - director_name = customer.director_name if customer and customer.director_name else "—" + inspection_ctx = self._inspection_context(payload) + rates_ctx = self._rates_context(valuation_date) ctx = { "logo_url": "", @@ -241,44 +202,19 @@ class ValuationReportPDFView(APIView): "date": _format_date(report_date), "valuation_date": _format_date(valuation_date), "inspection_date": _format_date(inspection_date), - "year": str(report_date.year), + "year": str(report_date.year or date.today().year), "market_value_formatted": market_value_formatted, "market_value_words": market_value_words, }, - "vehicle": { - "brand": full_brand, - "plate_number": plate_number, - "production_date": production_date, - "production_year": manufacture_year, - "type": auto.get_object_type_display() if auto.object_type else "", - "engine_number": engine_number, - "body_number": body_number, - "chassis_number": body_number, - "color": color_value, - "tech_passport": tech_passport_value, - "fuel_type": fuel_type_value, - "engine_power": "", - "full_weight": "", - "empty_weight": "", - }, + "vehicle": vehicle_ctx, "customer": customer_ctx, "owner": owner_ctx, "contract": contract_ctx, "company": { - "director": director_name, - }, - "rates": { - "rur": "", - "usd": "", - "eur": "", - }, - "inspection": { - "tires": "", - "engine": "", - "chassis": "", - "transmission": "", - "body": "", + "director": "—", }, + "rates": rates_ctx, + "inspection": inspection_ctx, "cost": { "engine_volume": "", "factory_price": _format_currency(cost_final), @@ -311,7 +247,65 @@ class ValuationReportPDFView(APIView): } return ctx - def _customer_context(self, customer): + def _vehicle_context(self, auto, vehicle): + brand_name = "" + model_name = "" + if vehicle: + brand_name = vehicle.brand.name if vehicle.brand else "" + model_name = vehicle.model.name if vehicle.model else "" + if not brand_name: + brand_name = auto.car_brand or "" + if not model_name: + model_name = auto.car_model or "" + full_brand = f"{brand_name} {model_name}".strip() + + plate_number = (vehicle.license_plate if vehicle else None) or auto.car_number or "" + manufacture_year = "" + if vehicle and vehicle.manufacture_year: + manufacture_year = str(vehicle.manufacture_year) + elif auto.manufacture_year: + manufacture_year = str(auto.manufacture_year) + + production_date = f"{manufacture_year}-yil" if manufacture_year else "" + engine_number = (vehicle.engine_number if vehicle else None) or auto.car_dvigatel_number or "" + body_number = vehicle.vin_number if vehicle and vehicle.vin_number else "" + + color_value = "" + if vehicle and vehicle.color: + color_value = vehicle.color.name + elif auto.car_color: + color_value = auto.car_color + + fuel_type_value = "" + if vehicle and vehicle.fuel_type: + fuel_type_value = vehicle.fuel_type.name + + tech_passport_value = "" + if vehicle and (vehicle.tech_passport_series or vehicle.tech_passport_number): + tech_passport_value = ( + f"{vehicle.tech_passport_series or ''} № {vehicle.tech_passport_number or ''}" + ).strip() + elif auto.tex_passport_serie_num: + tech_passport_value = auto.tex_passport_serie_num + + return { + "brand": full_brand, + "plate_number": plate_number, + "production_date": production_date, + "production_year": manufacture_year, + "type": auto.get_object_type_display() if auto.object_type else "", + "engine_number": engine_number, + "body_number": body_number, + "chassis_number": body_number, + "color": color_value, + "tech_passport": tech_passport_value, + "fuel_type": fuel_type_value, + "engine_power": "", + "full_weight": "", + "empty_weight": "", + } + + def _customer_context(self, user): empty = { "name": "", "address": "", @@ -321,51 +315,41 @@ class ValuationReportPDFView(APIView): "bank": "", "mfo": "", } - if not customer: + if not user: return empty - if customer.customer_type == "legal": - return { - "name": customer.org_name or "", - "address": customer.org_address or "", - "phone": "", - "tin": customer.inn or "", - "account": customer.bank_account or "", - "bank": "", - "mfo": customer.mfo or "", - } - full_name = " ".join( - filter(None, [customer.last_name, customer.first_name, customer.middle_name]) - ) + full_name = " ".join(filter(None, [user.last_name, user.first_name])).strip() + if not full_name: + full_name = user.username or user.phone or "" return { "name": full_name, - "address": customer.address or "", - "phone": "", - "tin": customer.jshshir or "", + "address": "", + "phone": user.phone or "", + "tin": "", "account": "", "bank": "", "mfo": "", } - def _owner_context(self, owner): - empty = {"name": "", "address": ""} - if not owner: - return empty - type_field = getattr(owner, "owner_type", None) or getattr(owner, "customer_type", None) - if type_field == "legal": + def _owner_context(self, auto): + if auto.object_owner_type == ObjectOwnerType.LEGAL: return { - "name": owner.org_name or "", - "address": owner.org_address or "", + "name": auto.object_owner_legal_entity or "", + "address": auto.object_owner_residence or "", } full_name = " ".join( - filter(None, [owner.last_name, owner.first_name, owner.middle_name]) - ) + filter(None, [ + auto.object_owner_individual_person_l_name, + auto.object_owner_individual_person_f_name, + auto.object_owner_individual_person_p_name, + ]) + ).strip() return { "name": full_name, - "address": owner.address or "", + "address": auto.object_owner_residence or "", } def _contract_context(self, auto, fallback_date): - contract_date = auto.contract_date or fallback_date + contract_date = auto.contract_date or auto.rate_report_date or fallback_date return { "number": auto.registration_number or str(auto.pk), "day": f"{contract_date.day:02d}", @@ -373,6 +357,21 @@ class ValuationReportPDFView(APIView): "year": str(contract_date.year), } + def _inspection_context(self, payload): + provided = (payload or {}).get("inspection") or {} + return { + key: provided.get(key) or default + for key, default in DEFAULT_INSPECTION.items() + } + + def _rates_context(self, target_date): + rates = fetch_rates(target_date) + return { + "rur": rates.get("RUB", ""), + "usd": rates.get("USD", ""), + "eur": rates.get("EUR", ""), + } + def _empty_analog(self): return { "source": "", diff --git a/core/apps/evaluation/migrations/0046_mechanicauto_fields_and_multi_tex_passport.py b/core/apps/evaluation/migrations/0046_mechanicauto_fields_and_multi_tex_passport.py new file mode 100644 index 0000000..72c76f7 --- /dev/null +++ b/core/apps/evaluation/migrations/0046_mechanicauto_fields_and_multi_tex_passport.py @@ -0,0 +1,118 @@ +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("evaluation", "0045_alter_referenceitemmodel_type"), + ] + + operations = [ + migrations.RemoveField( + model_name="mechanicautoevaluationmodel", + name="car_type", + ), + migrations.RemoveField( + model_name="mechanicautoevaluationmodel", + name="tex_passport_file", + ), + migrations.AddField( + model_name="mechanicautoevaluationmodel", + name="distance_covered", + field=models.PositiveIntegerField(blank=True, null=True, verbose_name="distance covered"), + ), + migrations.AddField( + model_name="mechanicautoevaluationmodel", + name="object_owner_residence", + field=models.CharField(blank=True, max_length=255, null=True, verbose_name="object owner residence"), + ), + migrations.AddField( + model_name="mechanicautoevaluationmodel", + name="car_position", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="evaluation_mechanic_auto_car_position", + to="evaluation.referenceitemmodel", + verbose_name="car position", + ), + ), + migrations.AddField( + model_name="mechanicautoevaluationmodel", + name="body_type", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="evaluation_mechanic_auto_body_type", + to="evaluation.referenceitemmodel", + verbose_name="body type", + ), + ), + migrations.AddField( + model_name="mechanicautoevaluationmodel", + name="fuel_type", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="evaluation_mechanic_auto_fuel_type", + to="evaluation.referenceitemmodel", + verbose_name="fuel type", + ), + ), + migrations.AddField( + model_name="mechanicautoevaluationmodel", + name="state_car", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="evaluation_mechanic_auto_state_car", + to="evaluation.referenceitemmodel", + verbose_name="state car", + ), + ), + migrations.AddField( + model_name="mechanicautoevaluationmodel", + name="assessment_task_type", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="evaluation_mechanic_auto_assessment_task_type", + to="evaluation.referenceitemmodel", + verbose_name="assessment task type", + ), + ), + migrations.CreateModel( + name="MechanicAutoEvaluationTexPassportFile", + 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)), + ( + "file", + models.FileField( + upload_to="mechanic_evaluation/tech_passports/%Y/%m/", + verbose_name="tech passport file", + ), + ), + ( + "mechanic_auto_evaluation", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="tex_passport_files", + to="evaluation.mechanicautoevaluationmodel", + ), + ), + ], + options={ + "verbose_name": "Mechanic Auto Evaluation Tex Passport File", + "verbose_name_plural": "Mechanic Auto Evaluation Tex Passport Files", + "db_table": "MechanicAutoEvaluationTexPassportFile", + }, + ), + ] diff --git a/core/apps/evaluation/models/mechanic_auto.py b/core/apps/evaluation/models/mechanic_auto.py index 8a7f908..9a83192 100644 --- a/core/apps/evaluation/models/mechanic_auto.py +++ b/core/apps/evaluation/models/mechanic_auto.py @@ -4,7 +4,6 @@ from django_core.models import AbstractBaseModel from model_bakery import baker from core.apps.evaluation.choices.auto import ( - AutoCarType, AutoCarWheel, AutoEvaluationStatus, AutoObjectType, @@ -50,12 +49,57 @@ class MechanicAutoEvaluationModel(AbstractBaseModel): blank=True, ) - tex_passport_file = models.FileField( - verbose_name=_("tech passport file"), - upload_to="mechanic_evaluation/tech_passports/%Y/%m/", + distance_covered = models.PositiveIntegerField( + verbose_name=_("distance covered"), blank=True, null=True, ) + object_owner_residence = models.CharField( + verbose_name=_("object owner residence"), + max_length=255, + blank=True, + null=True, + ) + car_position = models.ForeignKey( + 'evaluation.ReferenceitemModel', + verbose_name=_("car position"), + blank=True, + null=True, + on_delete=models.SET_NULL, + related_name='evaluation_mechanic_auto_car_position', + ) + body_type = models.ForeignKey( + 'evaluation.ReferenceitemModel', + verbose_name=_("body type"), + blank=True, + null=True, + on_delete=models.SET_NULL, + related_name='evaluation_mechanic_auto_body_type', + ) + fuel_type = models.ForeignKey( + 'evaluation.ReferenceitemModel', + verbose_name=_("fuel type"), + blank=True, + null=True, + on_delete=models.SET_NULL, + related_name='evaluation_mechanic_auto_fuel_type', + ) + state_car = models.ForeignKey( + 'evaluation.ReferenceitemModel', + verbose_name=_("state car"), + blank=True, + null=True, + on_delete=models.SET_NULL, + related_name='evaluation_mechanic_auto_state_car', + ) + assessment_task_type = models.ForeignKey( + 'evaluation.ReferenceitemModel', + verbose_name=_("assessment task type"), + blank=True, + null=True, + on_delete=models.SET_NULL, + related_name='evaluation_mechanic_auto_assessment_task_type', + ) # ── Step 1 — Umumiy ma'lumotlar ────────────────────────────────── registration_number = models.CharField( @@ -170,12 +214,6 @@ class MechanicAutoEvaluationModel(AbstractBaseModel): blank=True, null=True, ) - car_type = models.IntegerField( - verbose_name=_("car type"), - choices=AutoCarType.choices, - blank=True, - null=True, - ) car_wheel = models.IntegerField( verbose_name=_("car wheel"), choices=AutoCarWheel.choices, @@ -248,3 +286,27 @@ class MechanicAutoEvaluationModel(AbstractBaseModel): db_table = "MechanicAutoEvaluation" verbose_name = _("Mechanic Auto Evaluation") verbose_name_plural = _("Mechanic Auto Evaluations") + + +class MechanicAutoEvaluationTexPassportFile(AbstractBaseModel): + mechanic_auto_evaluation = models.ForeignKey( + MechanicAutoEvaluationModel, + on_delete=models.CASCADE, + related_name="tex_passport_files", + ) + file = models.FileField( + verbose_name=_("tech passport file"), + upload_to="mechanic_evaluation/tech_passports/%Y/%m/", + ) + + def __str__(self): + return f"Tex passport file for MechanicAutoEvaluation #{self.mechanic_auto_evaluation_id}" + + @classmethod + def _baker(cls): + return baker.make(cls) + + class Meta: + db_table = "MechanicAutoEvaluationTexPassportFile" + verbose_name = _("Mechanic Auto Evaluation Tex Passport File") + verbose_name_plural = _("Mechanic Auto Evaluation Tex Passport Files") diff --git a/core/apps/evaluation/serializers/auto/MechanicAutoEvaluation.py b/core/apps/evaluation/serializers/auto/MechanicAutoEvaluation.py index ecddd4f..22ba10e 100644 --- a/core/apps/evaluation/serializers/auto/MechanicAutoEvaluation.py +++ b/core/apps/evaluation/serializers/auto/MechanicAutoEvaluation.py @@ -3,9 +3,12 @@ import re from django.contrib.auth import get_user_model from rest_framework import serializers +from django.db import transaction + from core.apps.evaluation.choices.request import RequestStatus from core.apps.evaluation.models import ( MechanicAutoEvaluationModel, + MechanicAutoEvaluationTexPassportFile, ReferenceitemModel, EvaluationrequestModel, ) @@ -14,6 +17,22 @@ from core.apps.evaluation.serializers.reference import ListReferenceitemSerializ User = get_user_model() +class MechanicAutoEvaluationTexPassportFileSerializer(serializers.ModelSerializer): + file = serializers.SerializerMethodField() + + class Meta: + model = MechanicAutoEvaluationTexPassportFile + fields = ["id", "file"] + + def get_file(self, obj): + request = self.context.get("request") + if not obj.file: + return None + if request: + return request.build_absolute_uri(obj.file.url) + return obj.file.url + + class BaseMechanicAutoevaluationSerializer(serializers.ModelSerializer): status_display = serializers.CharField(source="get_status_display", read_only=True) object_type_display = serializers.CharField(source="get_object_type_display", read_only=True, default=None) @@ -21,6 +40,12 @@ class BaseMechanicAutoevaluationSerializer(serializers.ModelSerializer): default=None) rate_type = ListReferenceitemSerializer(read_only=True) value_determined = ListReferenceitemSerializer(read_only=True) + car_position = ListReferenceitemSerializer(read_only=True) + body_type = ListReferenceitemSerializer(read_only=True) + fuel_type = ListReferenceitemSerializer(read_only=True) + state_car = ListReferenceitemSerializer(read_only=True) + assessment_task_type = ListReferenceitemSerializer(read_only=True) + tex_passport_files = MechanicAutoEvaluationTexPassportFileSerializer(many=True, read_only=True) user = serializers.SerializerMethodField(method_name="get_user", read_only=True) class Meta: @@ -36,7 +61,9 @@ class BaseMechanicAutoevaluationSerializer(serializers.ModelSerializer): "object_owner_individual_person_p_name", "object_owner_legal_entity", "object_owner_legal_inn", + "object_owner_residence", "tex_passport_serie_num", + "tex_passport_files", "rating_goal", "registration_number", "object_type", @@ -46,6 +73,12 @@ class BaseMechanicAutoevaluationSerializer(serializers.ModelSerializer): "car_number", "manufacture_year", "car_color", + "distance_covered", + "car_position", + "body_type", + "fuel_type", + "state_car", + "assessment_task_type", "status", "status_display", "created_at", @@ -72,7 +105,6 @@ class ListMechanicAutoevaluationSerializer(BaseMechanicAutoevaluationSerializer) class RetrieveMechanicAutoevaluationSerializer(BaseMechanicAutoevaluationSerializer): - car_type_display = serializers.CharField(source="get_car_type_display", read_only=True, default=None) car_wheel_display = serializers.CharField(source="get_car_wheel_display", read_only=True, default=None) class Meta(BaseMechanicAutoevaluationSerializer.Meta): @@ -90,11 +122,8 @@ class RetrieveMechanicAutoevaluationSerializer(BaseMechanicAutoevaluationSeriali "object_owner_legal_entity", "object_owner_legal_inn", "tex_passport_serie_num", - "tex_passport_file", "tex_passport_gived_date", "tex_passport_gived_location", - "car_type", - "car_type_display", "car_wheel", "car_wheel_display", "car_dvigatel_number", @@ -116,11 +145,41 @@ class UpdateMechanicAutoevaluationSerializer(serializers.ModelSerializer): required=False, allow_null=True, ) + car_position = serializers.PrimaryKeyRelatedField( + queryset=ReferenceitemModel.objects.all(), + required=False, + allow_null=True, + ) + body_type = serializers.PrimaryKeyRelatedField( + queryset=ReferenceitemModel.objects.all(), + required=False, + allow_null=True, + ) + fuel_type = serializers.PrimaryKeyRelatedField( + queryset=ReferenceitemModel.objects.all(), + required=False, + allow_null=True, + ) + state_car = serializers.PrimaryKeyRelatedField( + queryset=ReferenceitemModel.objects.all(), + required=False, + allow_null=True, + ) + assessment_task_type = serializers.PrimaryKeyRelatedField( + queryset=ReferenceitemModel.objects.all(), + required=False, + allow_null=True, + ) user = serializers.PrimaryKeyRelatedField( queryset=User.objects.all(), required=False, allow_null=True, ) + tex_passport_files = serializers.ListField( + child=serializers.FileField(), + required=False, + write_only=True, + ) class Meta: model = MechanicAutoEvaluationModel @@ -139,13 +198,14 @@ class UpdateMechanicAutoevaluationSerializer(serializers.ModelSerializer): "object_owner_individual_person_passport_num", "object_owner_legal_entity", "object_owner_legal_inn", + "object_owner_residence", "value_determined", "rate_type", - "tex_passport_file", + "assessment_task_type", + "tex_passport_files", "tex_passport_serie_num", "tex_passport_gived_date", "tex_passport_gived_location", - "car_type", "car_wheel", "car_brand", "car_model", @@ -153,6 +213,11 @@ class UpdateMechanicAutoevaluationSerializer(serializers.ModelSerializer): "manufacture_year", "car_dvigatel_number", "car_color", + "distance_covered", + "car_position", + "body_type", + "fuel_type", + "state_car", ] def validate_tex_passport_serie_num(self, value): @@ -197,6 +262,21 @@ class UpdateMechanicAutoevaluationSerializer(serializers.ModelSerializer): return attrs + def update(self, instance, validated_data): + files = validated_data.pop("tex_passport_files", None) + with transaction.atomic(): + for attr, value in validated_data.items(): + setattr(instance, attr, value) + instance.save() + if files is not None: + MechanicAutoEvaluationTexPassportFile.objects.bulk_create([ + MechanicAutoEvaluationTexPassportFile(mechanic_auto_evaluation=instance, file=f) for f in files + ]) + return instance + + def to_representation(self, instance): + return RetrieveMechanicAutoevaluationSerializer(instance, context=self.context).data + class CreateMechanicAutoevaluationSerializer(serializers.ModelSerializer): value_determined = serializers.PrimaryKeyRelatedField( @@ -209,6 +289,31 @@ class CreateMechanicAutoevaluationSerializer(serializers.ModelSerializer): required=False, allow_null=True, ) + car_position = serializers.PrimaryKeyRelatedField( + queryset=ReferenceitemModel.objects.all(), + required=False, + allow_null=True, + ) + body_type = serializers.PrimaryKeyRelatedField( + queryset=ReferenceitemModel.objects.all(), + required=False, + allow_null=True, + ) + fuel_type = serializers.PrimaryKeyRelatedField( + queryset=ReferenceitemModel.objects.all(), + required=False, + allow_null=True, + ) + state_car = serializers.PrimaryKeyRelatedField( + queryset=ReferenceitemModel.objects.all(), + required=False, + allow_null=True, + ) + assessment_task_type = serializers.PrimaryKeyRelatedField( + queryset=ReferenceitemModel.objects.all(), + required=False, + allow_null=True, + ) evaluation_request = serializers.PrimaryKeyRelatedField( queryset=EvaluationrequestModel.objects.all(), required=False, @@ -219,6 +324,11 @@ class CreateMechanicAutoevaluationSerializer(serializers.ModelSerializer): required=True, allow_null=False, ) + tex_passport_files = serializers.ListField( + child=serializers.FileField(), + required=False, + write_only=True, + ) class Meta: model = MechanicAutoEvaluationModel @@ -238,13 +348,14 @@ class CreateMechanicAutoevaluationSerializer(serializers.ModelSerializer): "object_owner_individual_person_passport_num", "object_owner_legal_entity", "object_owner_legal_inn", + "object_owner_residence", "value_determined", "rate_type", + "assessment_task_type", "tex_passport_serie_num", - "tex_passport_file", + "tex_passport_files", "tex_passport_gived_date", "tex_passport_gived_location", - "car_type", "car_wheel", "car_brand", "car_model", @@ -252,6 +363,11 @@ class CreateMechanicAutoevaluationSerializer(serializers.ModelSerializer): "manufacture_year", "car_dvigatel_number", "car_color", + "distance_covered", + "car_position", + "body_type", + "fuel_type", + "state_car", ] def validate_tex_passport_serie_num(self, value): @@ -296,11 +412,21 @@ class CreateMechanicAutoevaluationSerializer(serializers.ModelSerializer): return attrs def create(self, validated_data): + files = validated_data.pop("tex_passport_files", []) evaluation_req = validated_data.get("evaluation_request") if evaluation_req: evaluation_req.status = RequestStatus.IN_PROGRESS evaluation_req.save() - return super().create(validated_data) + with transaction.atomic(): + instance = super().create(validated_data) + if files: + MechanicAutoEvaluationTexPassportFile.objects.bulk_create([ + MechanicAutoEvaluationTexPassportFile(mechanic_auto_evaluation=instance, file=f) for f in files + ]) + return instance + + def to_representation(self, instance): + return RetrieveMechanicAutoevaluationSerializer(instance, context=self.context).data class MechanicAutoEvaluationAppraisersSerializer(serializers.Serializer): @@ -327,7 +453,6 @@ class MechanicAutoEvaluationModelSerializer(serializers.ModelSerializer): class Meta: model = MechanicAutoEvaluationModel fields = ( - "tex_passport_file", "registration_number", "contract_date", "object_inspection_date", @@ -341,12 +466,13 @@ class MechanicAutoEvaluationModelSerializer(serializers.ModelSerializer): "object_owner_individual_person_passport_num", "object_owner_legal_entity", "object_owner_legal_inn", + "object_owner_residence", "value_determined", "rate_type", + "assessment_task_type", "tex_passport_serie_num", "tex_passport_gived_date", "tex_passport_gived_location", - "car_type", "car_wheel", "car_brand", "car_model", @@ -354,6 +480,11 @@ class MechanicAutoEvaluationModelSerializer(serializers.ModelSerializer): "manufacture_year", "car_dvigatel_number", "car_color", + "distance_covered", + "car_position", + "body_type", + "fuel_type", + "state_car", "rating_goal", "status", "is_archived", diff --git a/resources/templates/documents/contract.html b/resources/templates/documents/contract.html index 6087d1a..4a2f239 100644 --- a/resources/templates/documents/contract.html +++ b/resources/templates/documents/contract.html @@ -846,7 +846,7 @@ © Ushbu hisobot mazmuni "SIFAT BAHOLASH" kompaniyasining mulki hisoblanadi va
ushbu hujjatning ruxsatsiz nusxalanishi noqonuniy hisoblanadi. -
Toshkent — {{ report.year }}
+
Toshkent — 2026
diff --git a/task.txt b/task.txt index f580023..15fc4f6 100644 --- a/task.txt +++ b/task.txt @@ -1 +1,41 @@ -Refrence item model'ga "assessment_task_type" kategoriya qo'shish kerak +object_type => Bu hozirda select. O’shanga value qo’shish kerak: bus, moto + + + +car_position => Qo’shish kerak. Bu hozirda select. /api/v1/reference-item/ api’dan value yuboraman + + + +distance_covered => Qo’shish kerak. Number. Bosib o’tilgan masofasi + + + +body_type => Qo’shish kerak. Bu hozirda select. /api/v1/reference-item/ api’dan value yuboraman + + + +fuel_type => Qo’shish kerak. Bu hozirda select. /api/v1/reference-item/ api’dan value yuboraman + + + +state_car => Qo’shish kerak. Bu hozirda select. /api/v1/reference-item/ api’dan value yuboraman + + + +car_type => manashu field ni olib tashlash kerak + + + +tex_passport_file => multiple qilish kerak + + + +assessment_task_type => Baholash vazifasi. Qo’shish kerak. Select bo’ladi. /api/v1/reference-item/ api’dan value yuboraman + + + +object_owner_residence => Obyekt egasi yashash joyi. Qo’shish kerak. string bo’ladi. + + + +manashu fieldlarni detail apidagi serializerga qoshish kerak auto-evaluationda ham va mechnic-auto-evaluationda ham \ No newline at end of file