From 928561be51eab321770bc75522a2f4601bde5e92 Mon Sep 17 00:00:00 2001 From: xoliqberdiyev Date: Tue, 5 May 2026 16:53:49 +0500 Subject: [PATCH] feat: add mechnic-auto-model --- core/apps/evaluation/filters/__init__.py | 1 + core/apps/evaluation/filters/history.py | 31 +- core/apps/evaluation/filters/mechanic_auto.py | 37 ++ ...43_mechanicautoevaluationmodel_and_more.py | 268 +++++++++++++ core/apps/evaluation/models/__init__.py | 1 + core/apps/evaluation/models/history.py | 60 +++ core/apps/evaluation/models/mechanic_auto.py | 250 ++++++++++++ .../auto/MechanicAutoEvaluation.py | 364 ++++++++++++++++++ .../evaluation/serializers/auto/__init__.py | 1 + .../history/MechanicAutoEvaluationHistory.py | 39 ++ .../serializers/history/__init__.py | 1 + core/apps/evaluation/urls.py | 22 ++ core/apps/evaluation/views/__init__.py | 1 + core/apps/evaluation/views/history.py | 57 ++- core/apps/evaluation/views/mechanic_auto.py | 198 ++++++++++ task.txt | 61 +++ 16 files changed, 1390 insertions(+), 2 deletions(-) create mode 100644 core/apps/evaluation/filters/mechanic_auto.py create mode 100644 core/apps/evaluation/migrations/0043_mechanicautoevaluationmodel_and_more.py create mode 100644 core/apps/evaluation/models/mechanic_auto.py create mode 100644 core/apps/evaluation/serializers/auto/MechanicAutoEvaluation.py create mode 100644 core/apps/evaluation/serializers/history/MechanicAutoEvaluationHistory.py create mode 100644 core/apps/evaluation/views/mechanic_auto.py create mode 100644 task.txt diff --git a/core/apps/evaluation/filters/__init__.py b/core/apps/evaluation/filters/__init__.py index a625148..05b2217 100644 --- a/core/apps/evaluation/filters/__init__.py +++ b/core/apps/evaluation/filters/__init__.py @@ -1,4 +1,5 @@ from .auto import * # noqa +from .mechanic_auto import * # noqa from .customer import * # noqa from .document import * # noqa from .documentcategory import * # noqa diff --git a/core/apps/evaluation/filters/history.py b/core/apps/evaluation/filters/history.py index 390f79f..487ebb3 100644 --- a/core/apps/evaluation/filters/history.py +++ b/core/apps/evaluation/filters/history.py @@ -1,7 +1,11 @@ from django_filters import rest_framework as filters from core.apps.evaluation.choices.history import EvaluationEventType -from core.apps.evaluation.models import AutoevaluationhistoryModel, QuickevaluationhistoryModel +from core.apps.evaluation.models import ( + AutoevaluationhistoryModel, + MechanicAutoevaluationhistoryModel, + QuickevaluationhistoryModel, +) class AutoevaluationhistoryFilter(filters.FilterSet): @@ -29,6 +33,31 @@ class AutoevaluationhistoryFilter(filters.FilterSet): ] +class MechanicAutoevaluationhistoryFilter(filters.FilterSet): + mechanic_auto_evaluation = filters.NumberFilter( + field_name="mechanic_auto_evaluation", lookup_expr="exact" + ) + event_type = filters.ChoiceFilter( + field_name="event_type", + choices=EvaluationEventType.choices, + ) + created_from = filters.DateTimeFilter( + field_name="created_at", lookup_expr="gte" + ) + created_to = filters.DateTimeFilter( + field_name="created_at", lookup_expr="lte" + ) + + class Meta: + model = MechanicAutoevaluationhistoryModel + fields = [ + "mechanic_auto_evaluation", + "event_type", + "created_from", + "created_to", + ] + + class QuickevaluationhistoryFilter(filters.FilterSet): quick_evaluation = filters.NumberFilter( field_name="quick_evaluation", lookup_expr="exact" diff --git a/core/apps/evaluation/filters/mechanic_auto.py b/core/apps/evaluation/filters/mechanic_auto.py new file mode 100644 index 0000000..5eb36d1 --- /dev/null +++ b/core/apps/evaluation/filters/mechanic_auto.py @@ -0,0 +1,37 @@ +from django_filters import rest_framework as filters + +from core.apps.evaluation.models import MechanicAutoEvaluationModel + + +class MechanicAutoevaluationFilter(filters.FilterSet): + status = filters.CharFilter(method="filter_status") + object_type = filters.CharFilter(field_name="object_type", lookup_expr="exact") + object_owner_type = filters.NumberFilter(field_name="object_owner_type", lookup_expr="exact") + rate_type = filters.NumberFilter(field_name="rate_type", lookup_expr="exact") + value_determined = filters.NumberFilter(field_name="value_determined", lookup_expr="exact") + client = filters.NumberFilter(field_name="valuation__customer", lookup_expr="exact") + created_from = filters.DateFilter(field_name="created_at", lookup_expr="gte") + created_to = filters.DateFilter(field_name="created_at", lookup_expr="lte") + rate_date_from = filters.DateFilter(field_name="rate_date", lookup_expr="gte") + rate_date_to = filters.DateFilter(field_name="rate_date", lookup_expr="lte") + + def filter_status(self, queryset, name, value): + if value: + statuses = [s.strip() for s in value.split(",") if s.strip()] + return queryset.filter(status__in=statuses) + return queryset + + class Meta: + model = MechanicAutoEvaluationModel + fields = [ + "status", + "object_type", + "object_owner_type", + "rate_type", + "value_determined", + "client", + "created_from", + "created_to", + "rate_date_from", + "rate_date_to", + ] diff --git a/core/apps/evaluation/migrations/0043_mechanicautoevaluationmodel_and_more.py b/core/apps/evaluation/migrations/0043_mechanicautoevaluationmodel_and_more.py new file mode 100644 index 0000000..9116436 --- /dev/null +++ b/core/apps/evaluation/migrations/0043_mechanicautoevaluationmodel_and_more.py @@ -0,0 +1,268 @@ +# Generated by Django 5.2.7 on 2026-05-05 11:52 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("evaluation", "0042_alter_bonuscategory_category"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="MechanicAutoEvaluationModel", + 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)), + ( + "tex_passport_file", + models.FileField( + blank=True, + null=True, + upload_to="mechanic_evaluation/tech_passports/%Y/%m/", + verbose_name="tech passport file", + ), + ), + ( + "registration_number", + models.CharField(blank=True, max_length=50, null=True, verbose_name="registration number"), + ), + ("contract_date", models.DateField(blank=True, null=True, verbose_name="contract date")), + ( + "object_inspection_date", + models.DateField(blank=True, null=True, verbose_name="object inspection date"), + ), + ("rate_date", models.DateField(blank=True, null=True, verbose_name="rate date")), + ("rate_report_date", models.DateField(blank=True, null=True, verbose_name="rate report date")), + ( + "object_type", + models.CharField( + blank=True, + choices=[ + ("lightweight_auto", "Yengil automobil"), + ("truck_car", "Yuk automobil"), + ("special_tech", "Maxsus texnika"), + ], + max_length=50, + null=True, + verbose_name="object type", + ), + ), + ( + "object_owner_type", + models.IntegerField( + blank=True, + choices=[(1, "Jismoniy shaxs"), (2, "Yuridik shaxs")], + null=True, + verbose_name="object owner type", + ), + ), + ( + "object_owner_individual_person_f_name", + models.CharField(blank=True, max_length=100, null=True, verbose_name="owner first name"), + ), + ( + "object_owner_individual_person_l_name", + models.CharField(blank=True, max_length=100, null=True, verbose_name="owner last name"), + ), + ( + "object_owner_individual_person_p_name", + models.CharField(blank=True, max_length=100, null=True, verbose_name="owner patronymic"), + ), + ( + "object_owner_individual_person_passport_num", + models.CharField(blank=True, max_length=20, null=True, verbose_name="owner passport number"), + ), + ( + "object_owner_legal_entity", + models.CharField(blank=True, max_length=255, null=True, verbose_name="legal entity name"), + ), + ( + "object_owner_legal_inn", + models.CharField(blank=True, max_length=20, null=True, verbose_name="legal entity INN"), + ), + ( + "tex_passport_serie_num", + models.CharField( + blank=True, max_length=20, null=True, verbose_name="tech passport series and number" + ), + ), + ( + "tex_passport_gived_date", + models.DateField(blank=True, null=True, verbose_name="tech passport given date"), + ), + ( + "tex_passport_gived_location", + models.CharField( + blank=True, max_length=255, null=True, verbose_name="tech passport given location" + ), + ), + ( + "car_type", + models.IntegerField( + blank=True, choices=[(1, "Xetchbek"), (2, "Universal")], null=True, verbose_name="car type" + ), + ), + ( + "car_wheel", + models.IntegerField(blank=True, choices=[(1, "4x4")], null=True, verbose_name="car wheel"), + ), + ("car_brand", models.CharField(blank=True, max_length=100, null=True, verbose_name="car brand")), + ("car_model", models.CharField(blank=True, max_length=100, null=True, verbose_name="car model")), + ("car_number", models.CharField(blank=True, max_length=20, null=True, verbose_name="car number")), + ( + "manufacture_year", + models.CharField(blank=True, max_length=10, null=True, verbose_name="manufacture year"), + ), + ( + "car_dvigatel_number", + models.CharField(blank=True, max_length=50, null=True, verbose_name="engine number"), + ), + ("car_color", models.CharField(blank=True, max_length=50, null=True, verbose_name="car color")), + ("rating_goal", models.CharField(blank=True, max_length=50, null=True, verbose_name="rating goal")), + ( + "status", + models.CharField( + choices=[ + ("yaratildi", "Yaratildi"), + ("baxolovchi_biriktirildi", "Baholovchi biriktirildi"), + ("baxolandi", "Baholandi"), + ("rad_etildi", "Rad etildi"), + ("tasdiqlandi", "Tasdiqlandi"), + ], + default="yaratildi", + max_length=50, + verbose_name="status", + ), + ), + ("is_archived", models.BooleanField(default=False, verbose_name="is archived")), + ( + "appraisers", + models.ManyToManyField( + blank=True, + related_name="mechanic_auto_evaluation_appraisers", + to=settings.AUTH_USER_MODEL, + verbose_name="appraisers", + ), + ), + ( + "evaluation_request", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="mechanic_auto_evaluations_request", + to="evaluation.evaluationrequestmodel", + ), + ), + ( + "rate_type", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="evaluation_mechanic_auto_rate_type", + to="evaluation.referenceitemmodel", + verbose_name="rate type", + ), + ), + ( + "user", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="mechanic_auto_evaluations_user", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "valuation", + models.OneToOneField( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="mechanic_auto_detail", + to="evaluation.valuationmodel", + ), + ), + ( + "value_determined", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="evaluation_mechanic_auto_value_determined", + to="evaluation.referenceitemmodel", + verbose_name="value determined", + ), + ), + ( + "vehicle", + models.OneToOneField( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="mechanic_evaluation", + to="evaluation.vehiclemodel", + ), + ), + ], + options={ + "verbose_name": "Mechanic Auto Evaluation", + "verbose_name_plural": "Mechanic Auto Evaluations", + "db_table": "MechanicAutoEvaluation", + }, + ), + migrations.CreateModel( + name="MechanicAutoevaluationhistoryModel", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ( + "event_type", + models.CharField( + choices=[ + ("order_created", "Buyurtma yaratildi"), + ("status_changed", "Status o'zgartirildi"), + ("evaluator_assigned", "Baholovchi biriktirildi"), + ("document_uploaded", "Hujjat yuklandi"), + ("payment_made", "To'lov qilindi"), + ], + max_length=50, + verbose_name="event type", + ), + ), + ("actor_id", models.BigIntegerField(blank=True, null=True, verbose_name="actor id")), + ("actor_full_name", models.CharField(default="Tizim", max_length=255, verbose_name="actor full name")), + ("actor_role", models.CharField(default="system", max_length=50, verbose_name="actor role")), + ("meta", models.JSONField(blank=True, default=dict, verbose_name="meta")), + ("created_at", models.DateTimeField(auto_now_add=True, verbose_name="created at")), + ( + "mechanic_auto_evaluation", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="history", + to="evaluation.mechanicautoevaluationmodel", + verbose_name="mechanic auto evaluation", + ), + ), + ], + options={ + "verbose_name": "Mechanic Auto Evaluation History", + "verbose_name_plural": "Mechanic Auto Evaluation Histories", + "db_table": "MechanicAutoEvaluationHistory", + "ordering": ["created_at"], + "indexes": [ + models.Index( + fields=["mechanic_auto_evaluation_id", "created_at"], name="mech_auto_hist_eval_date_idx" + ), + models.Index(fields=["event_type"], name="mech_auto_hist_event_type_idx"), + ], + }, + ), + ] diff --git a/core/apps/evaluation/models/__init__.py b/core/apps/evaluation/models/__init__.py index 46a6a07..aa0a698 100644 --- a/core/apps/evaluation/models/__init__.py +++ b/core/apps/evaluation/models/__init__.py @@ -1,4 +1,5 @@ from .auto import * # noqa +from .mechanic_auto import * # noqa from .customer import * # noqa from .document import * # noqa from .documentcategory import * # noqa diff --git a/core/apps/evaluation/models/history.py b/core/apps/evaluation/models/history.py index e178839..1da342b 100644 --- a/core/apps/evaluation/models/history.py +++ b/core/apps/evaluation/models/history.py @@ -69,6 +69,66 @@ class AutoevaluationhistoryModel(models.Model): ] +class MechanicAutoevaluationhistoryModel(models.Model): + """MechanicAutoEvaluation bo'yicha barcha harakatlar logi — faqat o'qiladi, signallar tomonidan yoziladi.""" + + mechanic_auto_evaluation = models.ForeignKey( + "evaluation.MechanicAutoEvaluationModel", + on_delete=models.CASCADE, + related_name="history", + verbose_name=_("mechanic auto evaluation"), + ) + event_type = models.CharField( + verbose_name=_("event type"), + max_length=50, + choices=EvaluationEventType.choices, + ) + + actor_id = models.BigIntegerField( + verbose_name=_("actor id"), + null=True, + blank=True, + ) + actor_full_name = models.CharField( + verbose_name=_("actor full name"), + max_length=255, + default="Tizim", + ) + actor_role = models.CharField( + verbose_name=_("actor role"), + max_length=50, + default="system", + ) + + meta = models.JSONField( + verbose_name=_("meta"), + default=dict, + blank=True, + ) + + created_at = models.DateTimeField( + verbose_name=_("created at"), + auto_now_add=True, + ) + + def __str__(self): + return f"{self.get_event_type_display()} — MechanicAutoEval #{self.mechanic_auto_evaluation_id}" + + @classmethod + def _baker(cls): + return baker.make(cls) + + class Meta: + db_table = "MechanicAutoEvaluationHistory" + verbose_name = _("Mechanic Auto Evaluation History") + verbose_name_plural = _("Mechanic Auto Evaluation Histories") + ordering = ["created_at"] + indexes = [ + models.Index(fields=["mechanic_auto_evaluation_id", "created_at"], name="mech_auto_hist_eval_date_idx"), + models.Index(fields=["event_type"], name="mech_auto_hist_event_type_idx"), + ] + + class QuickevaluationhistoryModel(models.Model): """QuickEvaluation bo'yicha barcha harakatlar logi — faqat o'qiladi, signallar tomonidan yoziladi.""" diff --git a/core/apps/evaluation/models/mechanic_auto.py b/core/apps/evaluation/models/mechanic_auto.py new file mode 100644 index 0000000..8a7f908 --- /dev/null +++ b/core/apps/evaluation/models/mechanic_auto.py @@ -0,0 +1,250 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ +from django_core.models import AbstractBaseModel +from model_bakery import baker + +from core.apps.evaluation.choices.auto import ( + AutoCarType, + AutoCarWheel, + AutoEvaluationStatus, + AutoObjectType, + ObjectOwnerType, +) +from .valuation import ValuationModel +from .vehicle import VehicleModel + + +class MechanicAutoEvaluationModel(AbstractBaseModel): + user = models.ForeignKey( + "accounts.User", + on_delete=models.SET_NULL, + related_name="mechanic_auto_evaluations_user", + null=True, + blank=True, + ) + evaluation_request = models.ForeignKey( + "evaluation.EvaluationRequestModel", + on_delete=models.SET_NULL, + related_name="mechanic_auto_evaluations_request", + null=True, + blank=True, + ) + valuation = models.OneToOneField( + ValuationModel, + on_delete=models.CASCADE, + related_name="mechanic_auto_detail", + null=True, + blank=True, + ) + vehicle = models.OneToOneField( + VehicleModel, + on_delete=models.CASCADE, + related_name="mechanic_evaluation", + null=True, + blank=True, + ) + appraisers = models.ManyToManyField( + "accounts.User", + verbose_name=_("appraisers"), + related_name="mechanic_auto_evaluation_appraisers", + blank=True, + ) + + tex_passport_file = models.FileField( + verbose_name=_("tech passport file"), + upload_to="mechanic_evaluation/tech_passports/%Y/%m/", + blank=True, + null=True, + ) + + # ── Step 1 — Umumiy ma'lumotlar ────────────────────────────────── + registration_number = models.CharField( + verbose_name=_("registration number"), + max_length=50, + blank=True, + null=True, + ) + contract_date = models.DateField( + verbose_name=_("contract date"), + blank=True, + null=True, + ) + object_inspection_date = models.DateField( + verbose_name=_("object inspection date"), + blank=True, + null=True, + ) + rate_date = models.DateField( + verbose_name=_("rate date"), + blank=True, + null=True, + ) + rate_report_date = models.DateField( + verbose_name=_("rate report date"), + blank=True, + null=True, + ) + object_type = models.CharField( + verbose_name=_("object type"), + max_length=50, + choices=AutoObjectType.choices, + blank=True, + null=True, + ) + + # ── Step 2 — Shaxs ma'lumotlari ───────────────────────────────── + object_owner_type = models.IntegerField( + verbose_name=_("object owner type"), + choices=ObjectOwnerType.choices, + blank=True, + null=True, + ) + object_owner_individual_person_f_name = models.CharField( + verbose_name=_("owner first name"), + max_length=100, + blank=True, + null=True, + ) + object_owner_individual_person_l_name = models.CharField( + verbose_name=_("owner last name"), + max_length=100, + blank=True, + null=True, + ) + object_owner_individual_person_p_name = models.CharField( + verbose_name=_("owner patronymic"), + max_length=100, + blank=True, + null=True, + ) + object_owner_individual_person_passport_num = models.CharField( + verbose_name=_("owner passport number"), + max_length=20, + blank=True, + null=True, + ) + object_owner_legal_entity = models.CharField( + verbose_name=_("legal entity name"), + max_length=255, + blank=True, + null=True, + ) + object_owner_legal_inn = models.CharField( + verbose_name=_("legal entity INN"), + max_length=20, + blank=True, + null=True, + ) + value_determined = models.ForeignKey( + 'evaluation.ReferenceitemModel', + verbose_name=_("value determined"), + blank=True, + null=True, + on_delete=models.SET_NULL, + related_name='evaluation_mechanic_auto_value_determined' + ) + rate_type = models.ForeignKey( + 'evaluation.ReferenceitemModel', + verbose_name=_("rate type"), + blank=True, + null=True, + on_delete=models.SET_NULL, + related_name='evaluation_mechanic_auto_rate_type' + ) + + # ── Step 4 — Avtomobil ma'lumotlari ───────────────────────────── + tex_passport_serie_num = models.CharField( + verbose_name=_("tech passport series and number"), + max_length=20, + blank=True, + null=True, + ) + tex_passport_gived_date = models.DateField( + verbose_name=_("tech passport given date"), + blank=True, + null=True, + ) + tex_passport_gived_location = models.CharField( + verbose_name=_("tech passport given location"), + max_length=255, + 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, + blank=True, + null=True, + ) + car_brand = models.CharField( + verbose_name=_("car brand"), + max_length=100, + blank=True, + null=True, + ) + car_model = models.CharField( + verbose_name=_("car model"), + max_length=100, + blank=True, + null=True, + ) + car_number = models.CharField( + verbose_name=_("car number"), + max_length=20, + blank=True, + null=True, + ) + manufacture_year = models.CharField( + verbose_name=_("manufacture year"), + max_length=10, + blank=True, + null=True, + ) + car_dvigatel_number = models.CharField( + verbose_name=_("engine number"), + max_length=50, + blank=True, + null=True, + ) + car_color = models.CharField( + verbose_name=_("car color"), + max_length=50, + blank=True, + null=True, + ) + + # ── Natija ─────────────────────────────────────────────────────── + rating_goal = models.CharField( + verbose_name=_("rating goal"), + max_length=50, + blank=True, + null=True, + ) + status = models.CharField( + verbose_name=_("status"), + max_length=50, + choices=AutoEvaluationStatus.choices, + default=AutoEvaluationStatus.CREATED, + ) + is_archived = models.BooleanField( + verbose_name=_("is archived"), + default=False, + ) + + def __str__(self): + return f"Mechanic Auto Evaluation {self.registration_number or self.pk}" + + @classmethod + def _baker(cls): + return baker.make(cls) + + class Meta: + db_table = "MechanicAutoEvaluation" + verbose_name = _("Mechanic Auto Evaluation") + verbose_name_plural = _("Mechanic Auto Evaluations") diff --git a/core/apps/evaluation/serializers/auto/MechanicAutoEvaluation.py b/core/apps/evaluation/serializers/auto/MechanicAutoEvaluation.py new file mode 100644 index 0000000..34e0f06 --- /dev/null +++ b/core/apps/evaluation/serializers/auto/MechanicAutoEvaluation.py @@ -0,0 +1,364 @@ +import re + +from django.contrib.auth import get_user_model +from rest_framework import serializers + +from core.apps.evaluation.choices.request import RequestStatus +from core.apps.evaluation.models import ( + MechanicAutoEvaluationModel, + ReferenceitemModel, + EvaluationrequestModel, +) +from core.apps.evaluation.serializers.reference import ListReferenceitemSerializer + +User = get_user_model() + + +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) + object_owner_type_display = serializers.CharField(source="get_object_owner_type_display", read_only=True, + default=None) + rate_type = ListReferenceitemSerializer(read_only=True) + value_determined = ListReferenceitemSerializer(read_only=True) + user = serializers.SerializerMethodField(method_name="get_user", read_only=True) + + class Meta: + model = MechanicAutoEvaluationModel + fields = [ + "id", + "contract_date", + "object_inspection_date", + "object_owner_individual_person_passport_num", + "object_owner_type", + "object_owner_individual_person_f_name", + "object_owner_individual_person_l_name", + "object_owner_individual_person_p_name", + "object_owner_legal_entity", + "object_owner_legal_inn", + "tex_passport_serie_num", + "rating_goal", + "registration_number", + "object_type", + "object_type_display", + "car_brand", + "car_model", + "car_number", + "manufacture_year", + "car_color", + "status", + "status_display", + "created_at", + "value_determined", + "rate_type", + "user", + "evaluation_request", + ] + + def get_user(self, obj): + request = self.context.get('request') + return { + "id": obj.user.id, + "phone": obj.user.phone, + "first_name": obj.user.first_name, + "last_name": obj.user.last_name, + "avatar": request.build_absolute_uri(obj.user.avatar.url) if obj.user.avatar else None + } if obj.user else None + + +class ListMechanicAutoevaluationSerializer(BaseMechanicAutoevaluationSerializer): + class Meta(BaseMechanicAutoevaluationSerializer.Meta): + pass + + +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): + fields = BaseMechanicAutoevaluationSerializer.Meta.fields + [ + "contract_date", + "object_inspection_date", + "rate_date", + "rate_report_date", + "object_owner_type", + "object_owner_type_display", + "object_owner_individual_person_f_name", + "object_owner_individual_person_l_name", + "object_owner_individual_person_p_name", + "object_owner_individual_person_passport_num", + "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", + "valuation", + "vehicle", + "rating_goal", + "updated_at", + ] + + +class UpdateMechanicAutoevaluationSerializer(serializers.ModelSerializer): + value_determined = serializers.PrimaryKeyRelatedField( + queryset=ReferenceitemModel.objects.all(), + required=False, + allow_null=True, + ) + rate_type = serializers.PrimaryKeyRelatedField( + queryset=ReferenceitemModel.objects.all(), + required=False, + allow_null=True, + ) + + class Meta: + model = MechanicAutoEvaluationModel + fields = [ + "registration_number", + "contract_date", + "object_inspection_date", + "rate_date", + "rate_report_date", + "object_type", + "object_owner_type", + "object_owner_individual_person_f_name", + "object_owner_individual_person_l_name", + "object_owner_individual_person_p_name", + "object_owner_individual_person_passport_num", + "object_owner_legal_entity", + "object_owner_legal_inn", + "value_determined", + "rate_type", + "tex_passport_file", + "tex_passport_serie_num", + "tex_passport_gived_date", + "tex_passport_gived_location", + "car_type", + "car_wheel", + "car_brand", + "car_model", + "car_number", + "manufacture_year", + "car_dvigatel_number", + "car_color", + ] + + def validate_tex_passport_serie_num(self, value): + if value and not re.match(r"^[A-Z]{3}\s?\d{7}$", value): + raise serializers.ValidationError( + "Format: AAA 1234567 (3 harf + 7 raqam)" + ) + return value + + def validate_object_owner_individual_person_passport_num(self, value): + if value and not re.match(r"^[A-Z]{2}\s?\d{7}$", value): + raise serializers.ValidationError( + "Format: AA 1234567 (2 harf + 7 raqam)" + ) + return value + + def validate(self, attrs): + owner_type = attrs.get("object_owner_type") + + if owner_type == 1: + required_fields = { + "object_owner_individual_person_f_name": "Ismi", + "object_owner_individual_person_l_name": "Familiyasi", + "object_owner_individual_person_p_name": "Sharifi", + "object_owner_individual_person_passport_num": "Passport raqami", + } + for field, label in required_fields.items(): + if not attrs.get(field): + raise serializers.ValidationError( + {field: f"Jismoniy shaxs uchun {label} majburiy."} + ) + + elif owner_type == 2: + if not attrs.get("object_owner_legal_entity"): + raise serializers.ValidationError( + {"object_owner_legal_entity": "Yuridik shaxs nomi majburiy."} + ) + if not attrs.get("object_owner_legal_inn"): + raise serializers.ValidationError( + {"object_owner_legal_inn": "INN raqami majburiy."} + ) + + return attrs + + +class CreateMechanicAutoevaluationSerializer(serializers.ModelSerializer): + value_determined = serializers.PrimaryKeyRelatedField( + queryset=ReferenceitemModel.objects.all(), + required=False, + allow_null=True, + ) + rate_type = serializers.PrimaryKeyRelatedField( + queryset=ReferenceitemModel.objects.all(), + required=False, + allow_null=True, + ) + evaluation_request = serializers.PrimaryKeyRelatedField( + queryset=EvaluationrequestModel.objects.all(), + required=False, + allow_null=True, + ) + user = serializers.PrimaryKeyRelatedField( + queryset=User.objects.all(), + required=True, + allow_null=False, + ) + + class Meta: + model = MechanicAutoEvaluationModel + fields = [ + "user", + "registration_number", + "evaluation_request", + "contract_date", + "object_inspection_date", + "rate_date", + "rate_report_date", + "object_type", + "object_owner_type", + "object_owner_individual_person_f_name", + "object_owner_individual_person_l_name", + "object_owner_individual_person_p_name", + "object_owner_individual_person_passport_num", + "object_owner_legal_entity", + "object_owner_legal_inn", + "value_determined", + "rate_type", + "tex_passport_serie_num", + "tex_passport_file", + "tex_passport_gived_date", + "tex_passport_gived_location", + "car_type", + "car_wheel", + "car_brand", + "car_model", + "car_number", + "manufacture_year", + "car_dvigatel_number", + "car_color", + ] + + def validate_tex_passport_serie_num(self, value): + if value and not re.match(r"^[A-Z]{3}\s?\d{7}$", value): + raise serializers.ValidationError( + "Format: AAA 1234567 (3 harf + 7 raqam)" + ) + return value + + def validate_object_owner_individual_person_passport_num(self, value): + if value and not re.match(r"^[A-Z]{2}\s?\d{7}$", value): + raise serializers.ValidationError( + "Format: AA 1234567 (2 harf + 7 raqam)" + ) + return value + + def validate(self, attrs): + owner_type = attrs.get("object_owner_type") + if owner_type == 1: + required_fields = { + "object_owner_individual_person_f_name": "Ismi", + "object_owner_individual_person_l_name": "Familiyasi", + "object_owner_individual_person_p_name": "Sharifi", + "object_owner_individual_person_passport_num": "Passport raqami", + } + for field, label in required_fields.items(): + if not attrs.get(field): + raise serializers.ValidationError( + {field: f"Jismoniy shaxs uchun {label} majburiy."} + ) + + elif owner_type == 2: + if not attrs.get("object_owner_legal_entity"): + raise serializers.ValidationError( + {"object_owner_legal_entity": "Yuridik shaxs nomi majburiy."} + ) + if not attrs.get("object_owner_legal_inn"): + raise serializers.ValidationError( + {"object_owner_legal_inn": "INN raqami majburiy."} + ) + + return attrs + + def create(self, validated_data): + evaluation_req = validated_data.get("evaluation_request") + if evaluation_req: + evaluation_req.status = RequestStatus.IN_PROGRESS + evaluation_req.save() + return super().create(validated_data) + + +class MechanicAutoEvaluationAppraisersSerializer(serializers.Serializer): + ids = serializers.ListField(child=serializers.IntegerField()) + + def validate(self, data): + if not data.get("ids"): + raise serializers.ValidationError("Appraisers IDs are required.") + users = User.objects.filter(id__in=data["ids"]) + if not users: + raise serializers.ValidationError("Invalid appraisers IDs.") + data['users'] = users + return data + + +class MechanicAutoEvaluationModelSerializer(serializers.ModelSerializer): + user = serializers.StringRelatedField(read_only=True) + appraisers = serializers.PrimaryKeyRelatedField( + many=True, + queryset=User.objects.all(), + required=False + ) + + class Meta: + model = MechanicAutoEvaluationModel + fields = ( + "tex_passport_file", + "registration_number", + "contract_date", + "object_inspection_date", + "rate_date", + "rate_report_date", + "object_type", + "object_owner_type", + "object_owner_individual_person_f_name", + "object_owner_individual_person_l_name", + "object_owner_individual_person_p_name", + "object_owner_individual_person_passport_num", + "object_owner_legal_entity", + "object_owner_legal_inn", + "value_determined", + "rate_type", + "tex_passport_serie_num", + "tex_passport_gived_date", + "tex_passport_gived_location", + "car_type", + "car_wheel", + "car_brand", + "car_model", + "car_number", + "manufacture_year", + "car_dvigatel_number", + "car_color", + "rating_goal", + "status", + "is_archived", + "user", + "appraisers", + "created_at", + "updated_at", + ) + + read_only_fields = ( + "id", + "created_at", + "updated_at", + ) diff --git a/core/apps/evaluation/serializers/auto/__init__.py b/core/apps/evaluation/serializers/auto/__init__.py index 07a3169..73aa9f5 100644 --- a/core/apps/evaluation/serializers/auto/__init__.py +++ b/core/apps/evaluation/serializers/auto/__init__.py @@ -1 +1,2 @@ from .AutoEvaluation import * # noqa +from .MechanicAutoEvaluation import * # noqa diff --git a/core/apps/evaluation/serializers/history/MechanicAutoEvaluationHistory.py b/core/apps/evaluation/serializers/history/MechanicAutoEvaluationHistory.py new file mode 100644 index 0000000..acd4ff7 --- /dev/null +++ b/core/apps/evaluation/serializers/history/MechanicAutoEvaluationHistory.py @@ -0,0 +1,39 @@ +from rest_framework import serializers + +from core.apps.evaluation.models import MechanicAutoevaluationhistoryModel + + +class BaseMechanicAutoevaluationhistorySerializer(serializers.ModelSerializer): + event_type_display = serializers.CharField(source="get_event_type_display", read_only=True) + actor = serializers.SerializerMethodField() + + def get_actor(self, obj): + return { + "id": obj.actor_id, + "full_name": obj.actor_full_name, + "role": obj.actor_role, + } + + class Meta: + model = MechanicAutoevaluationhistoryModel + fields = [ + "id", + "event_type", + "event_type_display", + "actor", + "meta", + "created_at", + ] + + +class ListMechanicAutoevaluationhistorySerializer(BaseMechanicAutoevaluationhistorySerializer): + class Meta(BaseMechanicAutoevaluationhistorySerializer.Meta): ... + + +class RetrieveMechanicAutoevaluationhistorySerializer(BaseMechanicAutoevaluationhistorySerializer): + class Meta(BaseMechanicAutoevaluationhistorySerializer.Meta): + fields = BaseMechanicAutoevaluationhistorySerializer.Meta.fields + ["mechanic_auto_evaluation"] + + +class CreateMechanicAutoevaluationhistorySerializer(BaseMechanicAutoevaluationhistorySerializer): + class Meta(BaseMechanicAutoevaluationhistorySerializer.Meta): ... diff --git a/core/apps/evaluation/serializers/history/__init__.py b/core/apps/evaluation/serializers/history/__init__.py index 5285508..92797ca 100644 --- a/core/apps/evaluation/serializers/history/__init__.py +++ b/core/apps/evaluation/serializers/history/__init__.py @@ -1,2 +1,3 @@ from .AutoEvaluationHistory import * # noqa +from .MechanicAutoEvaluationHistory import * # noqa from .QuickEvaluationHistory import * # noqa diff --git a/core/apps/evaluation/urls.py b/core/apps/evaluation/urls.py index 7e64e0b..756eefa 100644 --- a/core/apps/evaluation/urls.py +++ b/core/apps/evaluation/urls.py @@ -8,6 +8,7 @@ router = DefaultRouter() router.register("document-category", views.DocumentCategoryView, basename="DocumentCategory") router.register("document", views.DocumentView, basename="Document") router.register("auto-evaluation-history", views.AutoEvaluationHistoryView, basename="auto-evaluation-history") +router.register("mechanic-auto-evaluation-history", views.MechanicAutoEvaluationHistoryView, basename="mechanic-auto-evaluation-history") router.register("quick-evaluation-history", views.QuickEvaluationHistoryView, basename="quick-evaluation-history") router.register("determined-value", views.DeterminedValueView, basename="determined-value") router.register("evaluation-purpose", views.EvaluationPurposeView, basename="evaluation-purpose") @@ -22,6 +23,7 @@ router.register("quick-evaluation", views.QuickEvaluationView, basename="quick-e router.register("movable-property-evaluation", views.MovablePropertyEvaluationView, basename="movable-property-evaluation") router.register("real-estate-evaluation", views.RealEstateEvaluationView, basename="real-estate-evaluation") router.register("auto-evaluation", views.AutoEvaluationView, basename="auto-evaluation") +router.register("mechanic-auto-evaluation", views.MechanicAutoEvaluationView, basename="mechanic-auto-evaluation") router.register("vehicle", views.VehicleView, basename="vehicle") router.register("valuation", views.ValuationView, basename="valuation") router.register("property-owner", views.PropertyOwnerView, basename="property-owner") @@ -72,6 +74,26 @@ urlpatterns = [ ] )), + # Mechanic Auto Evaluation + path("mechanic-auto-evaluation/", include( + [ + path("admin/", views.AdminMechanicEvaluationsAPIView.as_view(), name="admin-mechanic-evaluations"), + path('archive/', include( + [ + path('/', views.MechanicAutoEvaluationArchiveAPIView.as_view()), + path('list/', views.MechanicAutoEvaluationArchivedListAPIView.as_view()) + ] + )), + path('appraisers/', include( + [ + path("/list/", views.MechanicAutoEvaluationListAppraisersView.as_view()), + path("/set/", views.MechanicAutoEvaluationSetAppraisersView.as_view()), + path("/remove/", views.MechanicAutoEvaluationRemoveAppraisersView.as_view()), + ] + )) + ] + )), + # Evaluation Request path("evaluation-request/", include( [ diff --git a/core/apps/evaluation/views/__init__.py b/core/apps/evaluation/views/__init__.py index 5f9ff27..44c6ae8 100644 --- a/core/apps/evaluation/views/__init__.py +++ b/core/apps/evaluation/views/__init__.py @@ -1,4 +1,5 @@ from .auto import * # noqa +from .mechanic_auto import * # noqa from .customer import * # noqa from .document import * # noqa from .documentcategory import * # noqa diff --git a/core/apps/evaluation/views/history.py b/core/apps/evaluation/views/history.py index dc45064..ed24ef1 100644 --- a/core/apps/evaluation/views/history.py +++ b/core/apps/evaluation/views/history.py @@ -16,9 +16,14 @@ from rest_framework.viewsets import ReadOnlyModelViewSet # core apps from core.apps.evaluation.filters.history import ( AutoevaluationhistoryFilter, + MechanicAutoevaluationhistoryFilter, QuickevaluationhistoryFilter, ) -from core.apps.evaluation.models import AutoevaluationhistoryModel, QuickevaluationhistoryModel +from core.apps.evaluation.models import ( + AutoevaluationhistoryModel, + MechanicAutoevaluationhistoryModel, + QuickevaluationhistoryModel, +) from core.apps.evaluation.serializers import history as serializers @@ -72,6 +77,56 @@ class AutoEvaluationHistoryView(BaseViewSetMixin, ReadOnlyModelViewSet): }) +@extend_schema( + tags=["MechanicAutoEvaluationHistory"], + parameters=[ + OpenApiParameter("mechanic_auto_evaluation", int, description="MechanicAutoEvaluation ID bo'yicha filter"), + OpenApiParameter("event_type", str, description="Event turi bo'yicha filter"), + OpenApiParameter("created_from", str, description="Boshlanish sanasi (ISO 8601)"), + OpenApiParameter("created_to", str, description="Tugash sanasi (ISO 8601)"), + ], +) +class MechanicAutoEvaluationHistoryView(BaseViewSetMixin, ReadOnlyModelViewSet): + queryset = MechanicAutoevaluationhistoryModel.objects.only( + "id", "mechanic_auto_evaluation_id", "event_type", + "actor_id", "actor_full_name", "actor_role", + "meta", "created_at", + ) + serializer_class = serializers.ListMechanicAutoevaluationhistorySerializer + permission_classes = [AllowAny] + pagination_class = None + + filter_backends = [DjangoFilterBackend, OrderingFilter] + filterset_class = MechanicAutoevaluationhistoryFilter + ordering_fields = [ + "id", + "event_type", + "event_type_display", + "actor", + "meta", + "created_at", + ] + ordering = ["created_at"] + + action_permission_classes = {} + action_serializer_class = { + "list": serializers.ListMechanicAutoevaluationhistorySerializer, + "retrieve": serializers.RetrieveMechanicAutoevaluationhistorySerializer, + "create": serializers.CreateMechanicAutoevaluationhistorySerializer, + } + + def list(self, request, *args, **kwargs): + queryset = self.filter_queryset(self.get_queryset()) + results = list(queryset) + serializer = self.get_serializer(results, many=True) + return Response({ + "count": len(results), + "next": None, + "previous": None, + "results": serializer.data, + }) + + @extend_schema( tags=["QuickEvaluationHistory"], parameters=[ diff --git a/core/apps/evaluation/views/mechanic_auto.py b/core/apps/evaluation/views/mechanic_auto.py new file mode 100644 index 0000000..81232a6 --- /dev/null +++ b/core/apps/evaluation/views/mechanic_auto.py @@ -0,0 +1,198 @@ +from django.db.models import Q +from django.shortcuts import get_object_or_404 +from django_core.mixins import BaseViewSetMixin +from django_filters.rest_framework import DjangoFilterBackend +from drf_spectacular.utils import extend_schema, OpenApiParameter +from rest_framework import generics +from rest_framework.filters import OrderingFilter, SearchFilter +from rest_framework.generics import GenericAPIView, ListAPIView +from rest_framework.permissions import AllowAny, IsAuthenticated +from rest_framework.response import Response +from rest_framework.views import APIView +from rest_framework.viewsets import ModelViewSet + +from core.apps.accounts.permissions import IsAdminRole +from core.apps.accounts.serializers.user import UserSerializer +from core.apps.evaluation.filters.mechanic_auto import MechanicAutoevaluationFilter +from core.apps.evaluation.models import MechanicAutoEvaluationModel +from core.apps.evaluation.serializers.auto.MechanicAutoEvaluation import ( + ListMechanicAutoevaluationSerializer, + RetrieveMechanicAutoevaluationSerializer, + CreateMechanicAutoevaluationSerializer, + UpdateMechanicAutoevaluationSerializer, + MechanicAutoEvaluationAppraisersSerializer, + MechanicAutoEvaluationModelSerializer, +) + + +@extend_schema(tags=["MechanicAutoEvaluation"]) +class MechanicAutoEvaluationView(BaseViewSetMixin, ModelViewSet): + queryset = MechanicAutoEvaluationModel.objects.select_related( + "valuation", + "valuation__customer", + "vehicle", + ).filter(is_archived=False) + serializer_class = ListMechanicAutoevaluationSerializer + permission_classes = [AllowAny] + + filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter] + filterset_class = MechanicAutoevaluationFilter + search_fields = [ + "registration_number", + "car_model", + "car_brand", + "car_number", + ] + ordering_fields = [ + "id", + "contract_date", + "object_inspection_date", + "object_owner_individual_person_passport_num", + "object_owner_type", + "object_owner_individual_person_f_name", + "object_owner_individual_person_l_name", + "object_owner_individual_person_p_name", + "object_owner_legal_entity", + "object_owner_legal_inn", + "tex_passport_serie_num", + "rating_goal", + "registration_number", + "object_type", + "object_type_display", + "car_brand", + "car_model", + "car_number", + "manufacture_year", + "car_color", + "status", + "status_display", + "created_at", + "value_determined", + "rate_type", + ] + ordering = ["-created_at"] + + action_permission_classes = {} + action_serializer_class = { + "list": ListMechanicAutoevaluationSerializer, + "retrieve": RetrieveMechanicAutoevaluationSerializer, + "create": CreateMechanicAutoevaluationSerializer, + "update": UpdateMechanicAutoevaluationSerializer, + "partial_update": UpdateMechanicAutoevaluationSerializer, + } + + def serializer_context(self): + return self.serializer_class(context={'request': self.request}) + + +@extend_schema(tags=["MechanicAutoEvaluation"]) +class MechanicAutoEvaluationSetAppraisersView(GenericAPIView): + permission_classes = [IsAuthenticated] + queryset = MechanicAutoEvaluationModel.objects.all() + serializer_class = MechanicAutoEvaluationAppraisersSerializer + + def post(self, request, id): + try: + evaluation = get_object_or_404(MechanicAutoEvaluationModel, id=id) + serializer = self.serializer_class(data=request.data) + serializer.is_valid(raise_exception=True) + users = serializer.validated_data.get("users") + evaluation.appraisers.set(users) + evaluation.save() + return Response({"success": True, "data": "Foydalanuvchilar muvaffaqiyatli qo'shildi"}) + except Exception as e: + return Response({"error": str(e)}, status=500) + + +@extend_schema(tags=["MechanicAutoEvaluation"]) +class MechanicAutoEvaluationRemoveAppraisersView(GenericAPIView): + permission_classes = [IsAuthenticated] + queryset = MechanicAutoEvaluationModel.objects.all() + serializer_class = MechanicAutoEvaluationAppraisersSerializer + + def post(self, request, id): + try: + evaluation = get_object_or_404(MechanicAutoEvaluationModel, id=id) + serializer = self.serializer_class(data=request.data) + serializer.is_valid(raise_exception=True) + users = serializer.validated_data.get("users") + evaluation.appraisers.remove(*users) + evaluation.save() + return Response({"success": True, "data": "Foydalanuvchilar muvaffaqiyatli o'chirildi"}) + except Exception as e: + return Response({"error": str(e)}, status=500) + + +@extend_schema(tags=["MechanicAutoEvaluation"]) +class MechanicAutoEvaluationListAppraisersView(GenericAPIView): + permission_classes = [IsAuthenticated] + queryset = MechanicAutoEvaluationModel.objects.all() + serializer_class = UserSerializer + + @extend_schema( + parameters=[ + OpenApiParameter( + name="search", + type=str, + description="Search query", + required=False, + ) + ] + ) + def get(self, request, id): + try: + search_query = request.query_params.get("search", "") + evaluation = get_object_or_404(MechanicAutoEvaluationModel, id=id) + query = evaluation.appraisers.all() + if search_query: + query = query.filter( + Q(phone__icontains=search_query) | + Q(first_name__icontains=search_query) | + Q(last_name__icontains=search_query) + ) + page = self.paginate_queryset(query) + serializer = self.serializer_class(page, many=True) + return self.get_paginated_response(serializer.data) + except Exception as e: + return Response({"error": str(e)}, status=500) + + +@extend_schema(tags=["MechanicAutoEvaluation"]) +class MechanicAutoEvaluationArchivedListAPIView(ListAPIView): + permission_classes = [IsAuthenticated] + serializer_class = ListMechanicAutoevaluationSerializer + + def get_queryset(self): + return MechanicAutoEvaluationModel.objects.filter(is_archived=True) + + +@extend_schema(tags=["MechanicAutoEvaluation"]) +class MechanicAutoEvaluationArchiveAPIView(APIView): + permission_classes = [IsAuthenticated] + + def post(self, request, pk): + evaluation = get_object_or_404(MechanicAutoEvaluationModel, pk=pk) + evaluation.is_archived = request.data["is_archived"] + evaluation.save() + return Response( + { + "success": True, + "status": evaluation.status, + "id": evaluation.pk + }, + status=200 + ) + + +@extend_schema(tags=["MechanicAutoEvaluation"]) +class AdminMechanicEvaluationsAPIView(generics.GenericAPIView): + permission_classes = [IsAuthenticated, IsAdminRole] + queryset = MechanicAutoEvaluationModel.objects.all() + serializer_class = MechanicAutoEvaluationModelSerializer + + def get(self, request): + evaluations = MechanicAutoEvaluationModel.objects.filter( + created_by=self.request.user + ).distinct() + serializer = MechanicAutoEvaluationModelSerializer(evaluations, many=True) + return Response(serializer.data) diff --git a/task.txt b/task.txt new file mode 100644 index 0000000..ca8e9eb --- /dev/null +++ b/task.txt @@ -0,0 +1,61 @@ +Odatiy avto baholash api'larini kopiyasi kerak. Qo'lda baholash uchun + +[SIFAT-91] task bajarilib bo’linganidan so’ng ushbu taskni boshlang + +Qo’lda baholash degani narxlar va hujjatlarni xodimlarni o’zlari aniqlab kiritadi. “Qo’lda baholash”ni odatiy “Avto baholash”dan farqli tomoni: + + + + + +Hujjatlar Xodimlar tomonidan yuklanadi + + + +Narx automatik xisoblanmaydi + + + +Mexanik baholash yaratilishida foydalanuvchi tanlanishi kerak + + + +Xozircha quyidagilarni copy qilib yasab bersangiz bo’lgani. Qolganini alohida task qilib kiritaman. + + + + + +/api/v1/auto-evaluation/ => /api/v1/mechanic-auto-evaluation/ + + + +/api/v1/auto-evaluation/:id/ => /api/v1/mechanic-auto-evaluation/:id/ + + + +/api/v1/auto-evaluation/appraisers/:id/list/ => /api/v1/mechanic-auto-evaluation/appraisers/:id/list/ + + + +/api/v1/auto-evaluation/appraisers/:id/remove/ => /api/v1/mechanic-auto-evaluation/appraisers/:id/remove/ + + + +/api/v1/auto-evaluation/appraisers/:id/set/ => /api/v1/mechanic-auto-evaluation/appraisers/:id/set/ + + + +/api/v1/auto-evaluation-history/ => /api/v1/mechanic-auto-evaluation-history/ + + + +/api/v1/auto-evaluation/archive/list/ => /api/v1/mechanic-auto-evaluation/archive/list/ + + + +/api/v1/auto-evaluation/archive/:id/ => /api/v1/mechanic-auto-evaluation/archive/:id/ + + + +Endpointlar ko’rsatilganiday bo’lsin