diff --git a/core/apps/evaluation/admin/auto.py b/core/apps/evaluation/admin/auto.py index 78afa9c..9c655e0 100644 --- a/core/apps/evaluation/admin/auto.py +++ b/core/apps/evaluation/admin/auto.py @@ -8,22 +8,66 @@ from core.apps.evaluation.models import AutoEvaluationModel class AutoEvaluationAdmin(ModelAdmin): list_display = ( "id", - "valuation", - "vehicle", + "registration_number", + "object_type", + "car_brand", + "car_model", + "car_number", + "status", "created_at", ) + list_filter = ("status", "object_type", "rate_type", "value_determined", "object_owner_type") search_fields = ( - "valuation__conclusion_number", - "vehicle__brand", - "vehicle__model", - "vehicle__license_plate", + "registration_number", + "car_brand", + "car_model", + "car_number", ) readonly_fields = ("created_at", "updated_at") autocomplete_fields = ("valuation", "vehicle") fieldsets = ( ("Bog'lanishlar", { + "classes": ("collapse",), "fields": ("valuation", "vehicle"), }), + ("Step 1 — Umumiy ma'lumotlar", { + "fields": ( + "registration_number", + ("contract_date", "object_inspection_date"), + ("rate_date", "rate_report_date"), + "rate_object_name", + "object_type", + "status", + ), + }), + ("Step 2 — Shaxs ma'lumotlari", { + "fields": ( + "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"), + ("property_rights", "form_ownership"), + ("value_determined", "rate_type"), + ), + }), + ("Step 3 — Manzil ma'lumotlari", { + "fields": ( + ("object_location_province", "object_location_district"), + ("object_location_city", "object_location_neighborhood"), + ("object_location_street", "object_location_home"), + ("object_location_highways", "object_location_covenience"), + ), + }), + ("Step 4 — Avtomobil ma'lumotlari", { + "fields": ( + "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"), + ), + }), ("Tizim", { "classes": ("collapse",), "fields": ("created_at", "updated_at"), diff --git a/core/apps/evaluation/admin/vehicle.py b/core/apps/evaluation/admin/vehicle.py index ac3ec87..9fe81fc 100644 --- a/core/apps/evaluation/admin/vehicle.py +++ b/core/apps/evaluation/admin/vehicle.py @@ -18,20 +18,19 @@ class VehicleAdmin(ModelAdmin): "mileage", ) list_filter = ( - "fuel_type", - "body_type", "condition", "manufacture_year", ) search_fields = ( - "brand", - "model", + "brand__name", + "model__name", "license_plate", "vin_number", "engine_number", "tech_passport_number", ) readonly_fields = ("created_at", "updated_at") + autocomplete_fields = ("brand", "model", "color", "fuel_type", "body_type", "position") fieldsets = ( ("Texnik passport", { "fields": ( diff --git a/core/apps/evaluation/choices/auto.py b/core/apps/evaluation/choices/auto.py new file mode 100644 index 0000000..33b7bc7 --- /dev/null +++ b/core/apps/evaluation/choices/auto.py @@ -0,0 +1,70 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + + +class AutoObjectType(models.TextChoices): + LIGHTWEIGHT_AUTO = "lightweight_auto", _("Yengil automobil") + TRUCK_CAR = "truck_car", _("Yuk automobil") + SPECIAL_TECH = "special_tech", _("Maxsus texnika") + + +class AutoEvaluationStatus(models.TextChoices): + CREATED = "yaratildi", _("Yaratildi") + EVALUATOR_ASSIGNED = "baxolovchi_biriktirildi", _("Baholovchi biriktirildi") + EVALUATED = "baxolandi", _("Baholandi") + REJECTED = "rad_etildi", _("Rad etildi") + APPROVED = "tasdiqlandi", _("Tasdiqlandi") + + +class ObjectOwnerType(models.IntegerChoices): + INDIVIDUAL = 1, _("Jismoniy shaxs") + LEGAL = 2, _("Yuridik shaxs") + + +class PropertyRights(models.IntegerChoices): + PERMANENT_OWNERSHIP = 1, _("Doimiy egalik") + PERMANENT_USE = 2, _("Doimiy foydalanish") + TEMPORARY_USE = 3, _("Vaqtinchalik foydalanish") + TERM_LEASE = 4, _("Muddatli ijara") + LIFETIME_INHERITANCE = 5, _("Umrbod meros qilib olish") + + +class FormOwnership(models.IntegerChoices): + PRIVATE = 1, _("Xususiy") + STATE = 2, _("Davlat") + JSC = 3, _("AJ") + LLC = 4, _("MCHJ") + OTHER = 5, _("Boshqa") + + +class ValueDetermined(models.IntegerChoices): + MARKET_VALUE = 1, _("Bozor qiymati") + TAX_PURPOSE = 2, _("Soliq maqsadlari uchun") + LIQUIDATION_VALUE = 3, _("Tugatish qiymati") + UTILIZATION_VALUE = 4, _("Utilizatsiya qiymati") + + +class RateType(models.IntegerChoices): + CREDIT_COLLATERAL = 1, _("Kredit ta'minoti sifatida garovga qo'yish") + SALE_PURPOSE = 2, _("Sotish maqsadida bozor qiymatini aniqlash") + TAX_PURPOSE = 3, _("Soliqqa tortish maqsadida") + OTHER = 4, _("Boshqa") + + +class LocationHighways(models.IntegerChoices): + CENTER = 1, _("Tuman/Shahar markazi") + FAR_FROM_CENTER = 2, _("Tuman/shahar markazidan uzoqda") + + +class LocationConvenience(models.IntegerChoices): + POPULATED_AREA = 1, _("Aholi gavjum hudud") + MARKET_AREA = 2, _("Bozor hududi") + + +class AutoCarType(models.IntegerChoices): + HATCHBACK = 1, _("Xetchbek") + UNIVERSAL = 2, _("Universal") + + +class AutoCarWheel(models.IntegerChoices): + FOUR_BY_FOUR = 1, _("4x4") diff --git a/core/apps/evaluation/filters/auto.py b/core/apps/evaluation/filters/auto.py index d73bb5b..4f933da 100644 --- a/core/apps/evaluation/filters/auto.py +++ b/core/apps/evaluation/filters/auto.py @@ -1,13 +1,45 @@ -# from django_filters import rest_framework as filters +from django_filters import rest_framework as filters -# from core.apps.evaluation.models import AutoEvaluationModel +from core.apps.evaluation.models import AutoEvaluationModel -# class AutoevaluationFilter(filters.FilterSet): -# # name = filters.CharFilter(field_name="name", lookup_expr="icontains") +class AutoevaluationFilter(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") + property_rights = filters.NumberFilter(field_name="property_rights", lookup_expr="exact") + form_ownership = filters.NumberFilter(field_name="form_ownership", lookup_expr="exact") + object_location_province = filters.CharFilter( + field_name="object_location_province", lookup_expr="icontains" + ) + 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") -# class Meta: -# model = AutoEvaluationModel -# fields = [ -# "name", -# ] + 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 = AutoEvaluationModel + fields = [ + "status", + "object_type", + "object_owner_type", + "rate_type", + "value_determined", + "property_rights", + "form_ownership", + "object_location_province", + "client", + "created_from", + "created_to", + "rate_date_from", + "rate_date_to", + ] diff --git a/core/apps/evaluation/migrations/0015_autoevaluationmodel_contract_date_and_more.py b/core/apps/evaluation/migrations/0015_autoevaluationmodel_contract_date_and_more.py new file mode 100644 index 0000000..05f0279 --- /dev/null +++ b/core/apps/evaluation/migrations/0015_autoevaluationmodel_contract_date_and_more.py @@ -0,0 +1,108 @@ +# Generated by Django 5.2.7 on 2026-03-09 12:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('evaluation', '0014_alter_quickevaluationmodel_body_type_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='autoevaluationmodel', + name='contract_date', + field=models.DateField(blank=True, null=True, verbose_name='contract date'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='form_ownership', + field=models.CharField(blank=True, max_length=255, null=True, verbose_name='form of ownership'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='object_inspection_date', + field=models.DateField(blank=True, null=True, verbose_name='object inspection date'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='object_location_city', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='object location city'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='object_location_district', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='object location district'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='object_location_province', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='object location province'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='object_owner_type', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='object owner type'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='object_type', + field=models.CharField(blank=True, choices=[('lightweight_auto', 'Lightweight Auto'), ('truck_car', 'Truck Car'), ('special_tech', 'Special Tech')], max_length=50, null=True, verbose_name='object type'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='property_rights', + field=models.CharField(blank=True, max_length=255, null=True, verbose_name='property rights'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='rate_date', + field=models.DateField(blank=True, null=True, verbose_name='rate date'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='rate_object_name', + field=models.CharField(blank=True, max_length=255, null=True, verbose_name='rate object name'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='rate_report_date', + field=models.DateField(blank=True, null=True, verbose_name='rate report date'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='rate_type', + field=models.IntegerField(blank=True, choices=[(1, '1-tur'), (2, '2-tur'), (3, '3-tur'), (4, '4-tur')], null=True, verbose_name='rate type'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='rating_goal', + field=models.CharField(blank=True, choices=[('sotuv', 'Sotuv'), ('kredit', 'Kredit'), ('sugurta', "Sug'urta"), ('boshqa', 'Boshqa')], max_length=50, null=True, verbose_name='rating goal'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='registration_number', + field=models.CharField(blank=True, max_length=50, null=True, verbose_name='registration number'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='status', + field=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'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='tex_passport_gived_date', + field=models.DateField(blank=True, null=True, verbose_name='tech passport given date'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='tex_passport_serie_num', + field=models.CharField(blank=True, max_length=20, null=True, verbose_name='tech passport series and number'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='value_determined', + field=models.IntegerField(blank=True, choices=[(1, '1-qiymat'), (2, '2-qiymat'), (3, '3-qiymat'), (4, '4-qiymat')], null=True, verbose_name='value determined'), + ), + ] diff --git a/core/apps/evaluation/migrations/0016_alter_vehiclemodel_body_type_and_more.py b/core/apps/evaluation/migrations/0016_alter_vehiclemodel_body_type_and_more.py new file mode 100644 index 0000000..1f08c3a --- /dev/null +++ b/core/apps/evaluation/migrations/0016_alter_vehiclemodel_body_type_and_more.py @@ -0,0 +1,44 @@ +# Generated by Django 5.2.7 on 2026-03-09 12:34 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('evaluation', '0015_autoevaluationmodel_contract_date_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='vehiclemodel', + name='body_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vehicle_body_types', to='evaluation.referenceitemmodel', verbose_name='body type'), + ), + migrations.AlterField( + model_name='vehiclemodel', + name='brand', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vehicle_brands', to='evaluation.referenceitemmodel', verbose_name='brand'), + ), + migrations.AlterField( + model_name='vehiclemodel', + name='color', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vehicle_colors', to='evaluation.referenceitemmodel', verbose_name='color'), + ), + migrations.AlterField( + model_name='vehiclemodel', + name='fuel_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vehicle_fuel_types', to='evaluation.referenceitemmodel', verbose_name='fuel type'), + ), + migrations.AlterField( + model_name='vehiclemodel', + name='model', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vehicle_models', to='evaluation.referenceitemmodel', verbose_name='model'), + ), + migrations.AlterField( + model_name='vehiclemodel', + name='position', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vehicle_positions', to='evaluation.referenceitemmodel', verbose_name='position'), + ), + ] diff --git a/core/apps/evaluation/migrations/0017_autoevaluationmodel_car_brand_and_more.py b/core/apps/evaluation/migrations/0017_autoevaluationmodel_car_brand_and_more.py new file mode 100644 index 0000000..c4c4551 --- /dev/null +++ b/core/apps/evaluation/migrations/0017_autoevaluationmodel_car_brand_and_more.py @@ -0,0 +1,159 @@ +# Generated by Django 5.2.7 on 2026-03-09 12:54 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('evaluation', '0016_alter_vehiclemodel_body_type_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='autoevaluationmodel', + name='car_brand', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='car brand'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='car_color', + field=models.CharField(blank=True, max_length=50, null=True, verbose_name='car color'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='car_dvigatel_number', + field=models.CharField(blank=True, max_length=50, null=True, verbose_name='engine number'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='car_model', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='car model'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='car_number', + field=models.CharField(blank=True, max_length=20, null=True, verbose_name='car number'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='car_type', + field=models.IntegerField(blank=True, choices=[(1, 'Xetchbek'), (2, 'Universal')], null=True, verbose_name='car type'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='car_wheel', + field=models.IntegerField(blank=True, choices=[(1, '4x4')], null=True, verbose_name='car wheel'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='manufacture_year', + field=models.CharField(blank=True, max_length=10, null=True, verbose_name='manufacture year'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='object_location_covenience', + field=models.IntegerField(blank=True, choices=[(1, 'Aholi gavjum hudud'), (2, 'Bozor hududi')], null=True, verbose_name='location convenience'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='object_location_highways', + field=models.IntegerField(blank=True, choices=[(1, 'Tuman/Shahar markazi'), (2, 'Tuman/shahar markazidan uzoqda')], null=True, verbose_name='location highways'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='object_location_home', + field=models.CharField(blank=True, max_length=50, null=True, verbose_name='object location home'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='object_location_neighborhood', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='object location neighborhood'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='object_location_street', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='object location street'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='object_owner_individual_person_f_name', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='owner first name'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='object_owner_individual_person_l_name', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='owner last name'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='object_owner_individual_person_p_name', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='owner patronymic'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='object_owner_individual_person_passport_num', + field=models.CharField(blank=True, max_length=20, null=True, verbose_name='owner passport number'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='object_owner_legal_entity', + field=models.CharField(blank=True, max_length=255, null=True, verbose_name='legal entity name'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='object_owner_legal_inn', + field=models.CharField(blank=True, max_length=20, null=True, verbose_name='legal entity INN'), + ), + migrations.AddField( + model_name='autoevaluationmodel', + name='tex_passport_gived_location', + field=models.CharField(blank=True, max_length=255, null=True, verbose_name='tech passport given location'), + ), + migrations.AlterField( + model_name='autoevaluationmodel', + name='form_ownership', + field=models.IntegerField(blank=True, choices=[(1, 'Xususiy'), (2, 'Davlat'), (3, 'AJ'), (4, 'MCHJ'), (5, 'Boshqa')], null=True, verbose_name='form of ownership'), + ), + migrations.AlterField( + model_name='autoevaluationmodel', + name='object_owner_type', + field=models.IntegerField(blank=True, choices=[(1, 'Jismoniy shaxs'), (2, 'Yuridik shaxs')], null=True, verbose_name='object owner type'), + ), + migrations.AlterField( + model_name='autoevaluationmodel', + name='object_type', + field=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'), + ), + migrations.AlterField( + model_name='autoevaluationmodel', + name='property_rights', + field=models.IntegerField(blank=True, choices=[(1, 'Doimiy egalik'), (2, 'Doimiy foydalanish'), (3, 'Vaqtinchalik foydalanish'), (4, 'Muddatli ijara'), (5, 'Umrbod meros qilib olish')], null=True, verbose_name='property rights'), + ), + migrations.AlterField( + model_name='autoevaluationmodel', + name='rate_type', + field=models.IntegerField(blank=True, choices=[(1, "Kredit ta'minoti sifatida garovga qo'yish"), (2, 'Sotish maqsadida bozor qiymatini aniqlash'), (3, 'Soliqqa tortish maqsadida'), (4, 'Boshqa')], null=True, verbose_name='rate type'), + ), + migrations.AlterField( + model_name='autoevaluationmodel', + name='rating_goal', + field=models.CharField(blank=True, max_length=50, null=True, verbose_name='rating goal'), + ), + migrations.AlterField( + model_name='autoevaluationmodel', + name='valuation', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='auto_detail', to='evaluation.valuationmodel', verbose_name='valuation'), + ), + migrations.AlterField( + model_name='autoevaluationmodel', + name='value_determined', + field=models.IntegerField(blank=True, choices=[(1, 'Bozor qiymati'), (2, 'Soliq maqsadlari uchun'), (3, 'Tugatish qiymati'), (4, 'Utilizatsiya qiymati')], null=True, verbose_name='value determined'), + ), + migrations.AlterField( + model_name='autoevaluationmodel', + name='vehicle', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='evaluation', to='evaluation.vehiclemodel', verbose_name='vehicle'), + ), + ] diff --git a/core/apps/evaluation/models/auto.py b/core/apps/evaluation/models/auto.py index d5771d6..206b027 100644 --- a/core/apps/evaluation/models/auto.py +++ b/core/apps/evaluation/models/auto.py @@ -3,6 +3,19 @@ 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, + FormOwnership, + LocationConvenience, + LocationHighways, + ObjectOwnerType, + PropertyRights, + RateType, + ValueDetermined, +) from .valuation import ValuationModel from .vehicle import VehicleModel @@ -14,17 +27,260 @@ class AutoEvaluationModel(AbstractBaseModel): on_delete=models.CASCADE, related_name="auto_detail", verbose_name=_("valuation"), + null=True, + blank=True, ) vehicle = models.OneToOneField( VehicleModel, on_delete=models.CASCADE, related_name="evaluation", verbose_name=_("vehicle"), + null=True, + blank=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, + ) + rate_object_name = models.CharField( + verbose_name=_("rate object name"), + max_length=255, + 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, + ) + property_rights = models.IntegerField( + verbose_name=_("property rights"), + choices=PropertyRights.choices, + blank=True, + null=True, + ) + form_ownership = models.IntegerField( + verbose_name=_("form of ownership"), + choices=FormOwnership.choices, + blank=True, + null=True, + ) + value_determined = models.IntegerField( + verbose_name=_("value determined"), + choices=ValueDetermined.choices, + blank=True, + null=True, + ) + rate_type = models.IntegerField( + verbose_name=_("rate type"), + choices=RateType.choices, + blank=True, + null=True, + ) + + # ── Step 3 — Manzil ma'lumotlari ──────────────────────────────── + object_location_province = models.CharField( + verbose_name=_("object location province"), + max_length=100, + blank=True, + null=True, + ) + object_location_district = models.CharField( + verbose_name=_("object location district"), + max_length=100, + blank=True, + null=True, + ) + object_location_city = models.CharField( + verbose_name=_("object location city"), + max_length=100, + blank=True, + null=True, + ) + object_location_neighborhood = models.CharField( + verbose_name=_("object location neighborhood"), + max_length=100, + blank=True, + null=True, + ) + object_location_street = models.CharField( + verbose_name=_("object location street"), + max_length=100, + blank=True, + null=True, + ) + object_location_home = models.CharField( + verbose_name=_("object location home"), + max_length=50, + blank=True, + null=True, + ) + object_location_highways = models.IntegerField( + verbose_name=_("location highways"), + choices=LocationHighways.choices, + blank=True, + null=True, + ) + object_location_covenience = models.IntegerField( + verbose_name=_("location convenience"), + choices=LocationConvenience.choices, + blank=True, + null=True, + ) + + # ── 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, ) def __str__(self): - return f"Auto Evaluation for {self.valuation}" - + return f"Auto Evaluation {self.registration_number or self.pk}" @classmethod def _baker(cls): diff --git a/core/apps/evaluation/models/vehicle.py b/core/apps/evaluation/models/vehicle.py index c3a65ba..0b75208 100644 --- a/core/apps/evaluation/models/vehicle.py +++ b/core/apps/evaluation/models/vehicle.py @@ -3,8 +3,7 @@ from django.utils.translation import gettext_lazy as _ from django_core.models import AbstractBaseModel from model_bakery import baker - -from core.apps.evaluation.choices.vehicle import FuelType, BodyType, VehicleCondition +from core.apps.evaluation.choices.vehicle import VehicleCondition class VehicleModel(AbstractBaseModel): @@ -25,11 +24,21 @@ class VehicleModel(AbstractBaseModel): license_plate = models.CharField( verbose_name=_("license plate"), max_length=20, blank=True, null=True ) - brand = models.CharField( - verbose_name=_("brand"), max_length=100, blank=True, null=True + brand = models.ForeignKey( + "evaluation.ReferenceitemModel", + on_delete=models.SET_NULL, + null=True, + blank=True, + related_name="vehicle_brands", + verbose_name=_("brand"), ) - model = models.CharField( - verbose_name=_("model"), max_length=100, blank=True, null=True + model = models.ForeignKey( + "evaluation.ReferenceitemModel", + on_delete=models.SET_NULL, + null=True, + blank=True, + related_name="vehicle_models", + verbose_name=_("model"), ) manufacture_year = models.IntegerField( verbose_name=_("manufacture year"), blank=True, null=True @@ -40,27 +49,34 @@ class VehicleModel(AbstractBaseModel): engine_number = models.CharField( verbose_name=_("engine number"), max_length=50, blank=True, null=True ) - color = models.CharField( - verbose_name=_("color"), max_length=50, blank=True, null=True + color = models.ForeignKey( + "evaluation.ReferenceitemModel", + on_delete=models.SET_NULL, + null=True, + blank=True, + related_name="vehicle_colors", + verbose_name=_("color"), ) # 🛠 Texnik holati mileage = models.IntegerField( verbose_name=_("mileage"), blank=True, null=True, help_text=_("Distance in km") ) - fuel_type = models.CharField( + fuel_type = models.ForeignKey( + "evaluation.ReferenceitemModel", + on_delete=models.SET_NULL, + null=True, + blank=True, + related_name="vehicle_fuel_types", verbose_name=_("fuel type"), - max_length=20, - choices=FuelType.choices, - blank=True, - null=True, ) - body_type = models.CharField( - verbose_name=_("body type"), - max_length=20, - choices=BodyType.choices, - blank=True, + body_type = models.ForeignKey( + "evaluation.ReferenceitemModel", + on_delete=models.SET_NULL, null=True, + blank=True, + related_name="vehicle_body_types", + verbose_name=_("body type"), ) condition = models.CharField( verbose_name=_("condition"), @@ -69,12 +85,19 @@ class VehicleModel(AbstractBaseModel): blank=True, null=True, ) - position = models.CharField( - verbose_name=_("position"), max_length=50, blank=True, null=True + position = models.ForeignKey( + "evaluation.ReferenceitemModel", + on_delete=models.SET_NULL, + null=True, + blank=True, + related_name="vehicle_positions", + verbose_name=_("position"), ) def __str__(self): - return f"{self.brand} {self.model} ({self.license_plate})" + brand_name = self.brand.name if self.brand else "" + model_name = self.model.name if self.model else "" + return f"{brand_name} {model_name} ({self.license_plate})" @classmethod def _baker(cls): diff --git a/core/apps/evaluation/serializers/auto/AutoEvaluation.py b/core/apps/evaluation/serializers/auto/AutoEvaluation.py index beba944..49d83ce 100644 --- a/core/apps/evaluation/serializers/auto/AutoEvaluation.py +++ b/core/apps/evaluation/serializers/auto/AutoEvaluation.py @@ -1,45 +1,190 @@ +import re + from rest_framework import serializers + from core.apps.evaluation.models import AutoEvaluationModel -from core.apps.evaluation.serializers.valuation.Valuation import ListValuationSerializer -from core.apps.evaluation.serializers.vehicle.Vehicle import ListVehicleSerializer + class BaseAutoevaluationSerializer(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) + rate_type_display = serializers.CharField(source="get_rate_type_display", read_only=True, default=None) + value_determined_display = serializers.CharField(source="get_value_determined_display", read_only=True, default=None) + object_owner_type_display = serializers.CharField(source="get_object_owner_type_display", read_only=True, default=None) + property_rights_display = serializers.CharField(source="get_property_rights_display", read_only=True, default=None) + form_ownership_display = serializers.CharField(source="get_form_ownership_display", read_only=True, default=None) + class Meta: model = AutoEvaluationModel fields = [ "id", - "valuation", - "vehicle", + "registration_number", + "object_type", + "object_type_display", + "car_brand", + "car_model", + "car_number", + "manufacture_year", + "car_color", + "rate_type", + "rate_type_display", + "value_determined", + "value_determined_display", + "status", + "status_display", + "created_at", ] + class ListAutoevaluationSerializer(BaseAutoevaluationSerializer): - valuation_info = ListValuationSerializer(source="valuation", read_only=True) - vehicle_info = ListVehicleSerializer(source="vehicle", read_only=True) - class Meta(BaseAutoevaluationSerializer.Meta): - fields = BaseAutoevaluationSerializer.Meta.fields + [ - "valuation_info", - "vehicle_info", - ] + pass + class RetrieveAutoevaluationSerializer(BaseAutoevaluationSerializer): - from core.apps.evaluation.serializers.valuation.Valuation import RetrieveValuationSerializer - from core.apps.evaluation.serializers.vehicle.Vehicle import RetrieveVehicleSerializer - - valuation_detail = RetrieveValuationSerializer(source="valuation", read_only=True) - vehicle_detail = RetrieveVehicleSerializer(source="vehicle", read_only=True) + 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) + object_location_highways_display = serializers.CharField( + source="get_object_location_highways_display", read_only=True, default=None + ) + object_location_covenience_display = serializers.CharField( + source="get_object_location_covenience_display", read_only=True, default=None + ) class Meta(BaseAutoevaluationSerializer.Meta): fields = BaseAutoevaluationSerializer.Meta.fields + [ - "valuation_detail", - "vehicle_detail", - "created_at", + # Step 1 + "contract_date", + "object_inspection_date", + "rate_date", + "rate_report_date", + "rate_object_name", + # Step 2 + "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", + "property_rights", + "property_rights_display", + "form_ownership", + "form_ownership_display", + # Step 3 + "object_location_province", + "object_location_district", + "object_location_city", + "object_location_neighborhood", + "object_location_street", + "object_location_home", + "object_location_highways", + "object_location_highways_display", + "object_location_covenience", + "object_location_covenience_display", + # Step 4 + "tex_passport_serie_num", + "tex_passport_gived_date", + "tex_passport_gived_location", + "car_type", + "car_type_display", + "car_wheel", + "car_wheel_display", + "car_dvigatel_number", + # Extra + "valuation", + "vehicle", + "rating_goal", "updated_at", ] -class CreateAutoevaluationSerializer(BaseAutoevaluationSerializer): - class Meta(BaseAutoevaluationSerializer.Meta): + +class CreateAutoevaluationSerializer(serializers.ModelSerializer): + class Meta: + model = AutoEvaluationModel fields = [ - "valuation", - "vehicle", + # Step 1 + "registration_number", + "contract_date", + "object_inspection_date", + "rate_date", + "rate_report_date", + "rate_object_name", + "object_type", + # Step 2 + "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", + "property_rights", + "form_ownership", + "value_determined", + "rate_type", + # Step 3 + "object_location_province", + "object_location_district", + "object_location_city", + "object_location_neighborhood", + "object_location_street", + "object_location_home", + "object_location_highways", + "object_location_covenience", + # Step 4 + "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 diff --git a/core/apps/evaluation/serializers/vehicle/Vehicle.py b/core/apps/evaluation/serializers/vehicle/Vehicle.py index 218da3e..7593e1a 100644 --- a/core/apps/evaluation/serializers/vehicle/Vehicle.py +++ b/core/apps/evaluation/serializers/vehicle/Vehicle.py @@ -1,28 +1,39 @@ from rest_framework import serializers + from core.apps.evaluation.models import VehicleModel + class BaseVehicleSerializer(serializers.ModelSerializer): - fuel_type_display = serializers.CharField(source="get_fuel_type_display", read_only=True) - body_type_display = serializers.CharField(source="get_body_type_display", read_only=True) + brand_name = serializers.CharField(source="brand.name", read_only=True, default=None) + model_name = serializers.CharField(source="model.name", read_only=True, default=None) + color_name = serializers.CharField(source="color.name", read_only=True, default=None) + fuel_type_name = serializers.CharField(source="fuel_type.name", read_only=True, default=None) + body_type_name = serializers.CharField(source="body_type.name", read_only=True, default=None) condition_display = serializers.CharField(source="get_condition_display", read_only=True) + position_name = serializers.CharField(source="position.name", read_only=True, default=None) class Meta: model = VehicleModel fields = [ "id", "brand", + "brand_name", "model", + "model_name", "license_plate", "manufacture_year", "color", - "fuel_type_display", + "color_name", + "fuel_type_name", "condition_display", ] + class ListVehicleSerializer(BaseVehicleSerializer): class Meta(BaseVehicleSerializer.Meta): pass + class RetrieveVehicleSerializer(BaseVehicleSerializer): class Meta(BaseVehicleSerializer.Meta): fields = [ @@ -32,34 +43,40 @@ class RetrieveVehicleSerializer(BaseVehicleSerializer): "tech_passport_issued_date", "tech_passport_issued_by", "license_plate", - "model", "brand", + "brand_name", + "model", + "model_name", "manufacture_year", "vin_number", "engine_number", "color", + "color_name", "mileage", "fuel_type", - "fuel_type_display", + "fuel_type_name", "body_type", - "body_type_display", + "body_type_name", "condition", "condition_display", "position", + "position_name", "created_at", "updated_at", ] -class CreateVehicleSerializer(BaseVehicleSerializer): - class Meta(BaseVehicleSerializer.Meta): + +class CreateVehicleSerializer(serializers.ModelSerializer): + class Meta: + model = VehicleModel fields = [ "tech_passport_series", "tech_passport_number", "tech_passport_issued_date", "tech_passport_issued_by", "license_plate", - "model", "brand", + "model", "manufacture_year", "vin_number", "engine_number", diff --git a/core/apps/evaluation/views/auto.py b/core/apps/evaluation/views/auto.py index e49884d..7ed8eba 100644 --- a/core/apps/evaluation/views/auto.py +++ b/core/apps/evaluation/views/auto.py @@ -1,8 +1,11 @@ from django_core.mixins import BaseViewSetMixin +from django_filters.rest_framework import DjangoFilterBackend from drf_spectacular.utils import extend_schema +from rest_framework.filters import OrderingFilter, SearchFilter from rest_framework.permissions import AllowAny -from rest_framework.viewsets import ReadOnlyModelViewSet +from rest_framework.viewsets import ModelViewSet +from core.apps.evaluation.filters.auto import AutoevaluationFilter from core.apps.evaluation.models import AutoEvaluationModel from core.apps.evaluation.serializers.auto import ( CreateAutoevaluationSerializer, @@ -12,11 +15,52 @@ from core.apps.evaluation.serializers.auto import ( @extend_schema(tags=["AutoEvaluation"]) -class AutoEvaluationView(BaseViewSetMixin, ReadOnlyModelViewSet): - queryset = AutoEvaluationModel.objects.all() +class AutoEvaluationView(BaseViewSetMixin, ModelViewSet): + queryset = AutoEvaluationModel.objects.select_related( + "valuation", + "valuation__customer", + "vehicle", + ).all() serializer_class = ListAutoevaluationSerializer permission_classes = [AllowAny] + filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter] + filterset_class = AutoevaluationFilter + search_fields = [ + "registration_number", + "car_model", + "car_brand", + "car_number", + ] + ordering_fields = [ + "registration_number", + "contract_date", + "object_inspection_date", + "rate_date", + "rate_report_date", + "rate_object_name", + "object_type", + "object_owner_type", + "object_location_province", + "object_location_district", + "object_location_city", + "tex_passport_serie_num", + "tex_passport_gived_date", + "car_brand", + "car_model", + "car_number", + "manufacture_year", + "car_color", + "property_rights", + "form_ownership", + "value_determined", + "rate_type", + "status", + "created_at", + "updated_at", + ] + ordering = ["-created_at"] + action_permission_classes = {} action_serializer_class = { "list": ListAutoevaluationSerializer, diff --git a/core/apps/evaluation/views/vehicle.py b/core/apps/evaluation/views/vehicle.py index d997480..437d402 100644 --- a/core/apps/evaluation/views/vehicle.py +++ b/core/apps/evaluation/views/vehicle.py @@ -13,7 +13,9 @@ from core.apps.evaluation.serializers.vehicle import ( @extend_schema(tags=["Vehicle"]) class VehicleView(BaseViewSetMixin, ReadOnlyModelViewSet): - queryset = VehicleModel.objects.all() + queryset = VehicleModel.objects.select_related( + "brand", "model", "color", "fuel_type", "body_type", "position", + ).all() serializer_class = ListVehicleSerializer permission_classes = [AllowAny]