feat: wire contract PDF context and align MechanicAuto with AutoEvaluation

- contract PDF: map report/customer/owner/contract from AutoEvaluationModel
  fields, accept inspection via POST serializer, fetch CBU.uz currency rates
- MechanicAutoEvaluation: add distance_covered, object_owner_residence and
  car_position/body_type/fuel_type/state_car/assessment_task_type FKs; drop
  car_type and single tex_passport_file in favour of multi-file FK model

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
xoliqberdiyev
2026-05-05 18:51:24 +05:00
parent 25e92623fd
commit 80a1f5ff17
10 changed files with 560 additions and 164 deletions

View File

@@ -0,0 +1,118 @@
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("evaluation", "0045_alter_referenceitemmodel_type"),
]
operations = [
migrations.RemoveField(
model_name="mechanicautoevaluationmodel",
name="car_type",
),
migrations.RemoveField(
model_name="mechanicautoevaluationmodel",
name="tex_passport_file",
),
migrations.AddField(
model_name="mechanicautoevaluationmodel",
name="distance_covered",
field=models.PositiveIntegerField(blank=True, null=True, verbose_name="distance covered"),
),
migrations.AddField(
model_name="mechanicautoevaluationmodel",
name="object_owner_residence",
field=models.CharField(blank=True, max_length=255, null=True, verbose_name="object owner residence"),
),
migrations.AddField(
model_name="mechanicautoevaluationmodel",
name="car_position",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="evaluation_mechanic_auto_car_position",
to="evaluation.referenceitemmodel",
verbose_name="car position",
),
),
migrations.AddField(
model_name="mechanicautoevaluationmodel",
name="body_type",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="evaluation_mechanic_auto_body_type",
to="evaluation.referenceitemmodel",
verbose_name="body type",
),
),
migrations.AddField(
model_name="mechanicautoevaluationmodel",
name="fuel_type",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="evaluation_mechanic_auto_fuel_type",
to="evaluation.referenceitemmodel",
verbose_name="fuel type",
),
),
migrations.AddField(
model_name="mechanicautoevaluationmodel",
name="state_car",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="evaluation_mechanic_auto_state_car",
to="evaluation.referenceitemmodel",
verbose_name="state car",
),
),
migrations.AddField(
model_name="mechanicautoevaluationmodel",
name="assessment_task_type",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="evaluation_mechanic_auto_assessment_task_type",
to="evaluation.referenceitemmodel",
verbose_name="assessment task type",
),
),
migrations.CreateModel(
name="MechanicAutoEvaluationTexPassportFile",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
(
"file",
models.FileField(
upload_to="mechanic_evaluation/tech_passports/%Y/%m/",
verbose_name="tech passport file",
),
),
(
"mechanic_auto_evaluation",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="tex_passport_files",
to="evaluation.mechanicautoevaluationmodel",
),
),
],
options={
"verbose_name": "Mechanic Auto Evaluation Tex Passport File",
"verbose_name_plural": "Mechanic Auto Evaluation Tex Passport Files",
"db_table": "MechanicAutoEvaluationTexPassportFile",
},
),
]

View File

@@ -4,7 +4,6 @@ from django_core.models import AbstractBaseModel
from model_bakery import baker
from core.apps.evaluation.choices.auto import (
AutoCarType,
AutoCarWheel,
AutoEvaluationStatus,
AutoObjectType,
@@ -50,12 +49,57 @@ class MechanicAutoEvaluationModel(AbstractBaseModel):
blank=True,
)
tex_passport_file = models.FileField(
verbose_name=_("tech passport file"),
upload_to="mechanic_evaluation/tech_passports/%Y/%m/",
distance_covered = models.PositiveIntegerField(
verbose_name=_("distance covered"),
blank=True,
null=True,
)
object_owner_residence = models.CharField(
verbose_name=_("object owner residence"),
max_length=255,
blank=True,
null=True,
)
car_position = models.ForeignKey(
'evaluation.ReferenceitemModel',
verbose_name=_("car position"),
blank=True,
null=True,
on_delete=models.SET_NULL,
related_name='evaluation_mechanic_auto_car_position',
)
body_type = models.ForeignKey(
'evaluation.ReferenceitemModel',
verbose_name=_("body type"),
blank=True,
null=True,
on_delete=models.SET_NULL,
related_name='evaluation_mechanic_auto_body_type',
)
fuel_type = models.ForeignKey(
'evaluation.ReferenceitemModel',
verbose_name=_("fuel type"),
blank=True,
null=True,
on_delete=models.SET_NULL,
related_name='evaluation_mechanic_auto_fuel_type',
)
state_car = models.ForeignKey(
'evaluation.ReferenceitemModel',
verbose_name=_("state car"),
blank=True,
null=True,
on_delete=models.SET_NULL,
related_name='evaluation_mechanic_auto_state_car',
)
assessment_task_type = models.ForeignKey(
'evaluation.ReferenceitemModel',
verbose_name=_("assessment task type"),
blank=True,
null=True,
on_delete=models.SET_NULL,
related_name='evaluation_mechanic_auto_assessment_task_type',
)
# ── Step 1 — Umumiy ma'lumotlar ──────────────────────────────────
registration_number = models.CharField(
@@ -170,12 +214,6 @@ class MechanicAutoEvaluationModel(AbstractBaseModel):
blank=True,
null=True,
)
car_type = models.IntegerField(
verbose_name=_("car type"),
choices=AutoCarType.choices,
blank=True,
null=True,
)
car_wheel = models.IntegerField(
verbose_name=_("car wheel"),
choices=AutoCarWheel.choices,
@@ -248,3 +286,27 @@ class MechanicAutoEvaluationModel(AbstractBaseModel):
db_table = "MechanicAutoEvaluation"
verbose_name = _("Mechanic Auto Evaluation")
verbose_name_plural = _("Mechanic Auto Evaluations")
class MechanicAutoEvaluationTexPassportFile(AbstractBaseModel):
mechanic_auto_evaluation = models.ForeignKey(
MechanicAutoEvaluationModel,
on_delete=models.CASCADE,
related_name="tex_passport_files",
)
file = models.FileField(
verbose_name=_("tech passport file"),
upload_to="mechanic_evaluation/tech_passports/%Y/%m/",
)
def __str__(self):
return f"Tex passport file for MechanicAutoEvaluation #{self.mechanic_auto_evaluation_id}"
@classmethod
def _baker(cls):
return baker.make(cls)
class Meta:
db_table = "MechanicAutoEvaluationTexPassportFile"
verbose_name = _("Mechanic Auto Evaluation Tex Passport File")
verbose_name_plural = _("Mechanic Auto Evaluation Tex Passport Files")

View File

@@ -3,9 +3,12 @@ import re
from django.contrib.auth import get_user_model
from rest_framework import serializers
from django.db import transaction
from core.apps.evaluation.choices.request import RequestStatus
from core.apps.evaluation.models import (
MechanicAutoEvaluationModel,
MechanicAutoEvaluationTexPassportFile,
ReferenceitemModel,
EvaluationrequestModel,
)
@@ -14,6 +17,22 @@ from core.apps.evaluation.serializers.reference import ListReferenceitemSerializ
User = get_user_model()
class MechanicAutoEvaluationTexPassportFileSerializer(serializers.ModelSerializer):
file = serializers.SerializerMethodField()
class Meta:
model = MechanicAutoEvaluationTexPassportFile
fields = ["id", "file"]
def get_file(self, obj):
request = self.context.get("request")
if not obj.file:
return None
if request:
return request.build_absolute_uri(obj.file.url)
return obj.file.url
class BaseMechanicAutoevaluationSerializer(serializers.ModelSerializer):
status_display = serializers.CharField(source="get_status_display", read_only=True)
object_type_display = serializers.CharField(source="get_object_type_display", read_only=True, default=None)
@@ -21,6 +40,12 @@ class BaseMechanicAutoevaluationSerializer(serializers.ModelSerializer):
default=None)
rate_type = ListReferenceitemSerializer(read_only=True)
value_determined = ListReferenceitemSerializer(read_only=True)
car_position = ListReferenceitemSerializer(read_only=True)
body_type = ListReferenceitemSerializer(read_only=True)
fuel_type = ListReferenceitemSerializer(read_only=True)
state_car = ListReferenceitemSerializer(read_only=True)
assessment_task_type = ListReferenceitemSerializer(read_only=True)
tex_passport_files = MechanicAutoEvaluationTexPassportFileSerializer(many=True, read_only=True)
user = serializers.SerializerMethodField(method_name="get_user", read_only=True)
class Meta:
@@ -36,7 +61,9 @@ class BaseMechanicAutoevaluationSerializer(serializers.ModelSerializer):
"object_owner_individual_person_p_name",
"object_owner_legal_entity",
"object_owner_legal_inn",
"object_owner_residence",
"tex_passport_serie_num",
"tex_passport_files",
"rating_goal",
"registration_number",
"object_type",
@@ -46,6 +73,12 @@ class BaseMechanicAutoevaluationSerializer(serializers.ModelSerializer):
"car_number",
"manufacture_year",
"car_color",
"distance_covered",
"car_position",
"body_type",
"fuel_type",
"state_car",
"assessment_task_type",
"status",
"status_display",
"created_at",
@@ -72,7 +105,6 @@ class ListMechanicAutoevaluationSerializer(BaseMechanicAutoevaluationSerializer)
class RetrieveMechanicAutoevaluationSerializer(BaseMechanicAutoevaluationSerializer):
car_type_display = serializers.CharField(source="get_car_type_display", read_only=True, default=None)
car_wheel_display = serializers.CharField(source="get_car_wheel_display", read_only=True, default=None)
class Meta(BaseMechanicAutoevaluationSerializer.Meta):
@@ -90,11 +122,8 @@ class RetrieveMechanicAutoevaluationSerializer(BaseMechanicAutoevaluationSeriali
"object_owner_legal_entity",
"object_owner_legal_inn",
"tex_passport_serie_num",
"tex_passport_file",
"tex_passport_gived_date",
"tex_passport_gived_location",
"car_type",
"car_type_display",
"car_wheel",
"car_wheel_display",
"car_dvigatel_number",
@@ -116,11 +145,41 @@ class UpdateMechanicAutoevaluationSerializer(serializers.ModelSerializer):
required=False,
allow_null=True,
)
car_position = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
required=False,
allow_null=True,
)
body_type = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
required=False,
allow_null=True,
)
fuel_type = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
required=False,
allow_null=True,
)
state_car = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
required=False,
allow_null=True,
)
assessment_task_type = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
required=False,
allow_null=True,
)
user = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(),
required=False,
allow_null=True,
)
tex_passport_files = serializers.ListField(
child=serializers.FileField(),
required=False,
write_only=True,
)
class Meta:
model = MechanicAutoEvaluationModel
@@ -139,13 +198,14 @@ class UpdateMechanicAutoevaluationSerializer(serializers.ModelSerializer):
"object_owner_individual_person_passport_num",
"object_owner_legal_entity",
"object_owner_legal_inn",
"object_owner_residence",
"value_determined",
"rate_type",
"tex_passport_file",
"assessment_task_type",
"tex_passport_files",
"tex_passport_serie_num",
"tex_passport_gived_date",
"tex_passport_gived_location",
"car_type",
"car_wheel",
"car_brand",
"car_model",
@@ -153,6 +213,11 @@ class UpdateMechanicAutoevaluationSerializer(serializers.ModelSerializer):
"manufacture_year",
"car_dvigatel_number",
"car_color",
"distance_covered",
"car_position",
"body_type",
"fuel_type",
"state_car",
]
def validate_tex_passport_serie_num(self, value):
@@ -197,6 +262,21 @@ class UpdateMechanicAutoevaluationSerializer(serializers.ModelSerializer):
return attrs
def update(self, instance, validated_data):
files = validated_data.pop("tex_passport_files", None)
with transaction.atomic():
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
if files is not None:
MechanicAutoEvaluationTexPassportFile.objects.bulk_create([
MechanicAutoEvaluationTexPassportFile(mechanic_auto_evaluation=instance, file=f) for f in files
])
return instance
def to_representation(self, instance):
return RetrieveMechanicAutoevaluationSerializer(instance, context=self.context).data
class CreateMechanicAutoevaluationSerializer(serializers.ModelSerializer):
value_determined = serializers.PrimaryKeyRelatedField(
@@ -209,6 +289,31 @@ class CreateMechanicAutoevaluationSerializer(serializers.ModelSerializer):
required=False,
allow_null=True,
)
car_position = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
required=False,
allow_null=True,
)
body_type = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
required=False,
allow_null=True,
)
fuel_type = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
required=False,
allow_null=True,
)
state_car = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
required=False,
allow_null=True,
)
assessment_task_type = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
required=False,
allow_null=True,
)
evaluation_request = serializers.PrimaryKeyRelatedField(
queryset=EvaluationrequestModel.objects.all(),
required=False,
@@ -219,6 +324,11 @@ class CreateMechanicAutoevaluationSerializer(serializers.ModelSerializer):
required=True,
allow_null=False,
)
tex_passport_files = serializers.ListField(
child=serializers.FileField(),
required=False,
write_only=True,
)
class Meta:
model = MechanicAutoEvaluationModel
@@ -238,13 +348,14 @@ class CreateMechanicAutoevaluationSerializer(serializers.ModelSerializer):
"object_owner_individual_person_passport_num",
"object_owner_legal_entity",
"object_owner_legal_inn",
"object_owner_residence",
"value_determined",
"rate_type",
"assessment_task_type",
"tex_passport_serie_num",
"tex_passport_file",
"tex_passport_files",
"tex_passport_gived_date",
"tex_passport_gived_location",
"car_type",
"car_wheel",
"car_brand",
"car_model",
@@ -252,6 +363,11 @@ class CreateMechanicAutoevaluationSerializer(serializers.ModelSerializer):
"manufacture_year",
"car_dvigatel_number",
"car_color",
"distance_covered",
"car_position",
"body_type",
"fuel_type",
"state_car",
]
def validate_tex_passport_serie_num(self, value):
@@ -296,11 +412,21 @@ class CreateMechanicAutoevaluationSerializer(serializers.ModelSerializer):
return attrs
def create(self, validated_data):
files = validated_data.pop("tex_passport_files", [])
evaluation_req = validated_data.get("evaluation_request")
if evaluation_req:
evaluation_req.status = RequestStatus.IN_PROGRESS
evaluation_req.save()
return super().create(validated_data)
with transaction.atomic():
instance = super().create(validated_data)
if files:
MechanicAutoEvaluationTexPassportFile.objects.bulk_create([
MechanicAutoEvaluationTexPassportFile(mechanic_auto_evaluation=instance, file=f) for f in files
])
return instance
def to_representation(self, instance):
return RetrieveMechanicAutoevaluationSerializer(instance, context=self.context).data
class MechanicAutoEvaluationAppraisersSerializer(serializers.Serializer):
@@ -327,7 +453,6 @@ class MechanicAutoEvaluationModelSerializer(serializers.ModelSerializer):
class Meta:
model = MechanicAutoEvaluationModel
fields = (
"tex_passport_file",
"registration_number",
"contract_date",
"object_inspection_date",
@@ -341,12 +466,13 @@ class MechanicAutoEvaluationModelSerializer(serializers.ModelSerializer):
"object_owner_individual_person_passport_num",
"object_owner_legal_entity",
"object_owner_legal_inn",
"object_owner_residence",
"value_determined",
"rate_type",
"assessment_task_type",
"tex_passport_serie_num",
"tex_passport_gived_date",
"tex_passport_gived_location",
"car_type",
"car_wheel",
"car_brand",
"car_model",
@@ -354,6 +480,11 @@ class MechanicAutoEvaluationModelSerializer(serializers.ModelSerializer):
"manufacture_year",
"car_dvigatel_number",
"car_color",
"distance_covered",
"car_position",
"body_type",
"fuel_type",
"state_car",
"rating_goal",
"status",
"is_archived",