Merge pull request 'feat: add mechnic-auto-model' (#136) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m55s

Reviewed-on: #136
This commit is contained in:
2026-05-05 11:54:11 +00:00
16 changed files with 1390 additions and 2 deletions

View File

@@ -1,4 +1,5 @@
from .auto import * # noqa from .auto import * # noqa
from .mechanic_auto import * # noqa
from .customer import * # noqa from .customer import * # noqa
from .document import * # noqa from .document import * # noqa
from .documentcategory import * # noqa from .documentcategory import * # noqa

View File

@@ -1,7 +1,11 @@
from django_filters import rest_framework as filters from django_filters import rest_framework as filters
from core.apps.evaluation.choices.history import EvaluationEventType 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): 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): class QuickevaluationhistoryFilter(filters.FilterSet):
quick_evaluation = filters.NumberFilter( quick_evaluation = filters.NumberFilter(
field_name="quick_evaluation", lookup_expr="exact" field_name="quick_evaluation", lookup_expr="exact"

View File

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

View File

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

View File

@@ -1,4 +1,5 @@
from .auto import * # noqa from .auto import * # noqa
from .mechanic_auto import * # noqa
from .customer import * # noqa from .customer import * # noqa
from .document import * # noqa from .document import * # noqa
from .documentcategory import * # noqa from .documentcategory import * # noqa

View File

@@ -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): class QuickevaluationhistoryModel(models.Model):
"""QuickEvaluation bo'yicha barcha harakatlar logi — faqat o'qiladi, signallar tomonidan yoziladi.""" """QuickEvaluation bo'yicha barcha harakatlar logi — faqat o'qiladi, signallar tomonidan yoziladi."""

View File

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

View File

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

View File

@@ -1 +1,2 @@
from .AutoEvaluation import * # noqa from .AutoEvaluation import * # noqa
from .MechanicAutoEvaluation import * # noqa

View File

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

View File

@@ -1,2 +1,3 @@
from .AutoEvaluationHistory import * # noqa from .AutoEvaluationHistory import * # noqa
from .MechanicAutoEvaluationHistory import * # noqa
from .QuickEvaluationHistory import * # noqa from .QuickEvaluationHistory import * # noqa

View File

@@ -8,6 +8,7 @@ router = DefaultRouter()
router.register("document-category", views.DocumentCategoryView, basename="DocumentCategory") router.register("document-category", views.DocumentCategoryView, basename="DocumentCategory")
router.register("document", views.DocumentView, basename="Document") router.register("document", views.DocumentView, basename="Document")
router.register("auto-evaluation-history", views.AutoEvaluationHistoryView, basename="auto-evaluation-history") 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("quick-evaluation-history", views.QuickEvaluationHistoryView, basename="quick-evaluation-history")
router.register("determined-value", views.DeterminedValueView, basename="determined-value") router.register("determined-value", views.DeterminedValueView, basename="determined-value")
router.register("evaluation-purpose", views.EvaluationPurposeView, basename="evaluation-purpose") 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("movable-property-evaluation", views.MovablePropertyEvaluationView, basename="movable-property-evaluation")
router.register("real-estate-evaluation", views.RealEstateEvaluationView, basename="real-estate-evaluation") router.register("real-estate-evaluation", views.RealEstateEvaluationView, basename="real-estate-evaluation")
router.register("auto-evaluation", views.AutoEvaluationView, basename="auto-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("vehicle", views.VehicleView, basename="vehicle")
router.register("valuation", views.ValuationView, basename="valuation") router.register("valuation", views.ValuationView, basename="valuation")
router.register("property-owner", views.PropertyOwnerView, basename="property-owner") 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('<int:pk>/', views.MechanicAutoEvaluationArchiveAPIView.as_view()),
path('list/', views.MechanicAutoEvaluationArchivedListAPIView.as_view())
]
)),
path('appraisers/', include(
[
path("<int:id>/list/", views.MechanicAutoEvaluationListAppraisersView.as_view()),
path("<int:id>/set/", views.MechanicAutoEvaluationSetAppraisersView.as_view()),
path("<int:id>/remove/", views.MechanicAutoEvaluationRemoveAppraisersView.as_view()),
]
))
]
)),
# Evaluation Request # Evaluation Request
path("evaluation-request/", include( path("evaluation-request/", include(
[ [

View File

@@ -1,4 +1,5 @@
from .auto import * # noqa from .auto import * # noqa
from .mechanic_auto import * # noqa
from .customer import * # noqa from .customer import * # noqa
from .document import * # noqa from .document import * # noqa
from .documentcategory import * # noqa from .documentcategory import * # noqa

View File

@@ -16,9 +16,14 @@ from rest_framework.viewsets import ReadOnlyModelViewSet
# core apps # core apps
from core.apps.evaluation.filters.history import ( from core.apps.evaluation.filters.history import (
AutoevaluationhistoryFilter, AutoevaluationhistoryFilter,
MechanicAutoevaluationhistoryFilter,
QuickevaluationhistoryFilter, 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 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( @extend_schema(
tags=["QuickEvaluationHistory"], tags=["QuickEvaluationHistory"],
parameters=[ parameters=[

View File

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

61
task.txt Normal file
View File

@@ -0,0 +1,61 @@
Odatiy avto baholash api'larini kopiyasi kerak. Qo'lda baholash uchun
[SIFAT-91] task bajarilib bolinganidan song ushbu taskni boshlang
Qolda baholash degani narxlar va hujjatlarni xodimlarni ozlari aniqlab kiritadi. “Qolda 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 bolgani. 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 korsatilganiday bolsin