82 Commits

Author SHA1 Message Date
github-actions[bot]
a17c2a52ce 🔄 Update image to 161 [CI SKIP] 2026-05-05 13:55:08 +00:00
e5df804b08 Merge pull request 'feat: wire contract PDF context and align MechanicAuto with AutoEvaluation' (#141) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 7m59s
Reviewed-on: #141
2026-05-05 13:52:46 +00:00
xoliqberdiyev
80a1f5ff17 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>
2026-05-05 18:51:24 +05:00
github-actions[bot]
42f088f8f4 🔄 Update image to 160 [CI SKIP] 2026-05-05 12:50:33 +00:00
cb98fbdc7e Merge pull request 'fix: allow all roles except 'user' on tech-passport endpoint' (#140) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m57s
Reviewed-on: #140
2026-05-05 12:48:03 +00:00
github-actions[bot]
30792dd0b9 🔄 Update image to 159 [CI SKIP] 2026-05-05 12:47:45 +00:00
xoliqberdiyev
25e92623fd fix: allow all roles except 'user' on tech-passport endpoint
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 17:47:39 +05:00
27d1d70010 Merge pull request 'feat: add assessment_task_type to ReferenceType choices' (#139) from behruz into main
Some checks failed
Deploy to Production / build-and-deploy (push) Has been cancelled
Reviewed-on: #139
2026-05-05 12:45:24 +00:00
xoliqberdiyev
cb795fe3b8 feat: add assessment_task_type to ReferenceType choices
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 17:44:56 +05:00
github-actions[bot]
64da9f20d3 🔄 Update image to 158 [CI SKIP] 2026-05-05 12:10:06 +00:00
075188e85d Merge pull request 'add new fields for auto-evalution model' (#138) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m54s
Reviewed-on: #138
2026-05-05 12:07:47 +00:00
xoliqberdiyev
9a730fa0a3 add new fields for auto-evalution model 2026-05-05 17:07:28 +05:00
github-actions[bot]
91bdeb4b67 🔄 Update image to 157 [CI SKIP] 2026-05-05 11:59:57 +00:00
91073731dc Merge pull request 'change' (#137) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 3m1s
Reviewed-on: #137
2026-05-05 11:57:38 +00:00
xoliqberdiyev
e665939b1b change 2026-05-05 16:57:08 +05:00
github-actions[bot]
ee877c0fc6 🔄 Update image to 156 [CI SKIP] 2026-05-05 11:56:32 +00:00
ccb4bd8176 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
2026-05-05 11:54:11 +00:00
xoliqberdiyev
928561be51 feat: add mechnic-auto-model 2026-05-05 16:53:49 +05:00
github-actions[bot]
84f0df3f97 🔄 Update image to 155 [CI SKIP] 2026-05-05 11:38:10 +00:00
704d3b9e6b Merge pull request 'feat: add new model for comment files' (#135) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m49s
Reviewed-on: #135
2026-05-05 11:35:53 +00:00
xoliqberdiyev
9dcce296a6 a lot of changds 2026-05-05 16:35:41 +05:00
xoliqberdiyev
4ac21100a3 feat: add new model for comment files 2026-05-05 16:32:22 +05:00
github-actions[bot]
b0d5a2f334 🔄 Update image to 154 [CI SKIP] 2026-05-05 11:26:19 +00:00
ba3d6c4e47 Merge pull request 'behruz' (#134) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 5m12s
Reviewed-on: #134
2026-05-05 11:23:52 +00:00
f3834fad70 Merge pull request 'update' (#133) from shaxob into main
Some checks failed
Deploy to Production / build-and-deploy (push) Has been cancelled
Reviewed-on: #133
2026-05-05 11:23:35 +00:00
xoliqberdiyev
269817f25e write generate pdf api 2026-05-05 16:23:08 +05:00
xoliqberdiyev
8158d7146e a lot of changes 2026-05-05 14:37:37 +05:00
Shaxobff
617bed99ae update 2026-05-05 00:18:41 +05:00
github-actions[bot]
9b782fe7bd 🔄 Update image to 152 [CI SKIP] 2026-05-04 12:51:51 +00:00
135f580db2 Merge pull request 'shaxob' (#132) from shaxob into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m23s
Reviewed-on: #132
2026-05-04 12:49:58 +00:00
Shaxobff
0c622759cc update 2026-05-04 17:41:50 +05:00
github-actions[bot]
1d750b1c1c 🔄 Update image to 151 [CI SKIP] 2026-05-04 12:33:34 +00:00
2b26c52a5c Merge pull request 'behruz' (#131) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m20s
Reviewed-on: #131
2026-05-04 12:31:44 +00:00
xoliqberdiyev
51b3535a80 write 2026-05-04 17:29:20 +05:00
Shaxobff
9028e2f102 Merge branch 'main' of https://gitea.felixits.uz/sifatbaho/backend-v1 into shaxob 2026-05-04 16:17:12 +05:00
Shaxobff
0c9c726756 add generation_pdf pdf , fix 500 error , install reportlab 2026-05-04 16:15:12 +05:00
xoliqberdiyev
c88ea1aa77 Merge branch 'main' of https://gitea.felixits.uz/sifatbaho/backend-v1 into behruz 2026-05-04 14:47:05 +05:00
github-actions[bot]
581021cbb7 🔄 Update image to 150 [CI SKIP] 2026-05-04 05:38:53 +00:00
62f65385e1 fix: DB_HOST=postgres (stack.yaml service nomi bilan mos)
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m13s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 10:37:11 +05:00
github-actions[bot]
76d2fe5090 🔄 Update image to 149 [CI SKIP] 2026-05-04 05:14:11 +00:00
92d23901a1 ci cd uchun test commit
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 4m28s
2026-05-04 10:12:19 +05:00
github-actions[bot]
42987e4154 🔄 Update image to 148 [CI SKIP] 2026-05-04 04:50:59 +00:00
84b14da3f4 ci cd uchun test commit
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m10s
2026-05-04 09:49:08 +05:00
Shaxobff
1ff23af8bf update 2026-05-01 17:15:01 +05:00
Shaxobff
feecb580c1 update 2026-05-01 16:54:38 +05:00
xoliqberdiyev
cb53924f9b change 2026-04-30 16:33:00 +05:00
github-actions[bot]
f53125cfdc 🔄 Update image to 147 [CI SKIP] 2026-04-30 11:05:42 +00:00
65ab51e652 Merge pull request 'update' (#128) from shaxob into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m11s
Reviewed-on: #128
2026-04-30 11:03:51 +00:00
2997810fae Merge pull request 'behruz' (#129) from behruz into main
Some checks failed
Deploy to Production / build-and-deploy (push) Has been cancelled
Reviewed-on: #129
2026-04-30 11:03:44 +00:00
xoliqberdiyev
d014f5a2fb fix 2026-04-30 16:02:04 +05:00
xoliqberdiyev
7d49929772 Merge branch 'main' of https://gitea.felixits.uz/sifatbaho/backend-v1 into behruz 2026-04-30 15:22:18 +05:00
Shaxobff
c29546a04b update 2026-04-30 11:11:12 +05:00
xoliqberdiyev
b39c080de3 add tasks app 2026-04-29 18:40:51 +05:00
github-actions[bot]
7ad385af94 🔄 Update image to 145 [CI SKIP] 2026-04-29 12:30:39 +00:00
3781ce29e5 Merge pull request 'shaxob' (#127) from shaxob into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m19s
Reviewed-on: #127
2026-04-29 12:28:42 +00:00
Shaxobff
db7e34c1c2 update 2026-04-29 16:12:12 +05:00
Shaxobff
1cb9551e81 update 2026-04-29 14:21:33 +05:00
Shaxobff
51b30c2cc4 update 2026-04-29 11:57:23 +05:00
Shaxobff
dc4c98bfc9 update 2026-04-29 11:18:50 +05:00
github-actions[bot]
abed9e59b4 🔄 Update image to 144 [CI SKIP] 2026-04-28 13:41:10 +00:00
f238c92a09 Merge pull request 'fix' (#125) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m11s
Reviewed-on: #125
2026-04-28 13:32:46 +00:00
xoliqberdiyev
113f2da120 fix 2026-04-28 18:32:24 +05:00
github-actions[bot]
99b265f68f 🔄 Update image to 143 [CI SKIP] 2026-04-28 13:19:46 +00:00
c5d60e799c Merge pull request 'fix' (#124) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m17s
Reviewed-on: #124
2026-04-28 13:17:52 +00:00
xoliqberdiyev
7829c9c625 fix 2026-04-28 18:17:21 +05:00
github-actions[bot]
7f462674a8 🔄 Update image to 142 [CI SKIP] 2026-04-28 13:05:16 +00:00
f7be3be5d2 Merge pull request 'behruz' (#123) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m12s
Reviewed-on: #123
2026-04-28 13:03:29 +00:00
xoliqberdiyev
557f9f821d Merge branch 'main' of https://gitea.felixits.uz/sifatbaho/backend-v1 into behruz 2026-04-28 18:03:07 +05:00
xoliqberdiyev
5f70d69896 fux 2026-04-28 18:02:39 +05:00
github-actions[bot]
4ea7070a8f 🔄 Update image to 141 [CI SKIP] 2026-04-28 12:50:39 +00:00
8b02f3a3a3 Merge pull request 'add new admin user delete api' (#122) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m12s
Reviewed-on: #122
2026-04-28 12:48:46 +00:00
xoliqberdiyev
f0d93b10ac Merge branch 'main' of https://gitea.felixits.uz/sifatbaho/backend-v1 into behruz 2026-04-28 17:48:03 +05:00
xoliqberdiyev
172ddf4da4 add new admin user delete api 2026-04-28 17:47:55 +05:00
435dd56334 Merge pull request 'fix error' (#121) from behruz into main
Some checks failed
Deploy to Production / build-and-deploy (push) Has been cancelled
Reviewed-on: #121
2026-04-28 12:40:16 +00:00
xoliqberdiyev
779c9db924 fix error 2026-04-28 17:39:55 +05:00
eaaba123b0 Merge pull request 'add new fields to request-evalution api' (#120) from behruz into main
Some checks failed
Deploy to Production / build-and-deploy (push) Failing after 53s
Reviewed-on: #120
2026-04-28 12:30:43 +00:00
xoliqberdiyev
63c4ad81eb add new fields to request-evalution api 2026-04-28 17:30:11 +05:00
github-actions[bot]
d065891ad5 🔄 Update image to 138 [CI SKIP] 2026-04-28 11:11:54 +00:00
94c4d03925 Merge pull request 'remove unused fields from auto-evalution model' (#119) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m10s
Reviewed-on: #119
2026-04-28 11:10:06 +00:00
xoliqberdiyev
4a958f064b remove unused fields from auto-evalution model 2026-04-28 16:09:25 +05:00
github-actions[bot]
d1f0a5a9ae 🔄 Update image to 137 [CI SKIP] 2026-04-28 11:07:21 +00:00
0084d11c62 Merge pull request 'change response of permissions apis' (#118) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 4m25s
Reviewed-on: #118
2026-04-28 11:05:35 +00:00
104 changed files with 6906 additions and 279 deletions

View File

@@ -23,7 +23,7 @@ DB_ENGINE=django.db.backends.postgresql_psycopg2
DB_NAME=django
DB_USER=postgres
DB_PASSWORD=2309
DB_HOST=db
DB_HOST=postgres
DB_PORT=5432
# Cache

View File

@@ -151,6 +151,11 @@ jobs:
git fetch origin main
git reset --hard origin/main
if [ ! -f .env ]; then
cp .env.example .env
echo ".env yaratildi, production qiymatlarini kiriting!"
fi
export PORT=8085
docker pull ${{ secrets.DOCKER_USERNAME }}/${{ env.PROJECT_NAME }}:${{ github.run_number }}
docker stack deploy -c stack.yaml ${{ env.PROJECT_NAME }} --with-registry-auth

View File

@@ -14,6 +14,8 @@ APPS = [
"rest_framework_simplejwt",
"django_core",
"core.apps.accounts.apps.AccountsConfig",
'core.apps.tasks.apps.TasksConfig',
'core.apps.documents.apps.DocumentsConfig',
]
if env.bool("SILK_ENABLED", False):

View File

@@ -186,5 +186,57 @@ PAGES = [
"link": reverse_lazy("admin:shared_villagemodel_changelist"),
},
]
},
{
"title": _("Ruxsatlar"),
"separator": True,
"items": [
{
"title": _("Ruxsatlar"),
"icon": "attach_file",
"link": reverse_lazy("admin:accounts_permission_changelist"),
},
{
"title": _("Sahifa uchun ruxsatlar"),
"icon": "attach_file",
"link": reverse_lazy("admin:accounts_permissiontotab_changelist"),
},
{
"title": _("Actionlar uchun ruxsatlar"),
"icon": "attach_file",
"link": reverse_lazy("admin:accounts_permissiontoaction_changelist"),
},
{
"title": _("Role"),
"icon": "attach_file",
"link": reverse_lazy("admin:accounts_role_changelist"),
},
]
},
{
"title": _("Task Management"),
"separator": True,
"items": [
{
"title": _("Task"),
"icon": "task",
"link": reverse_lazy("admin:tasks_task_changelist"),
},
{
"title": _("Column"),
"icon": "tag",
"link": reverse_lazy("admin:tasks_column_changelist"),
},
{
"title": _("Comment"),
"icon": "message",
"link": reverse_lazy("admin:tasks_comment_changelist"),
},
{
"title": _("Label"),
"icon": "tag",
"link": reverse_lazy("admin:tasks_label_changelist"),
},
]
}
]

View File

@@ -13,7 +13,7 @@ from config.env import env
def home(request):
return HttpResponse("OK: #560cbe8000acd2c690f4efc4d40bfc131aa37796")
return HttpResponse("OK: #e5df804b080de139b62f6987059cc35229d63bf8")
urlpatterns = [
@@ -23,6 +23,8 @@ urlpatterns = [
path("api/v1/", include("core.apps.evaluation.urls")),
path("api/v1/", include("core.apps.payment.urls")),
path("api/v1/", include("core.apps.chat.urls")),
path("api/v1/tasks/", include("core.apps.tasks.urls")),
path("api/v1/documents/", include("core.apps.documents.urls")),
]
urlpatterns += [
path("admin/", admin.site.urls),

View File

@@ -1,2 +1,3 @@
from .core import * # noqa
from .user import * # noqa
from .permission import *

View File

@@ -0,0 +1,77 @@
from django.contrib import admin
from core.apps.accounts.models.permission import (
PermissionToAction,
PermissionToTab,
Permission,
Role,
)
@admin.register(PermissionToAction)
class PermissionToActionAdmin(admin.ModelAdmin):
list_display = ("id", "name", "code", "created_at")
search_fields = ("name", "code")
fieldsets = (
("Asosiy", {
"fields": ("name", "code"),
}),
)
@admin.register(PermissionToTab)
class PermissionToTabAdmin(admin.ModelAdmin):
list_display = ("id", "name", "code", "created_at")
search_fields = ("name", "code")
filter_horizontal = ("permission_to_actions",)
fieldsets = (
("Asosiy", {
"fields": ("name", "code"),
}),
("Harakatlar", {
"fields": ("permission_to_actions",),
}),
)
@admin.register(Permission)
class PermissionAdmin(admin.ModelAdmin):
list_display = ("id", "name", "code", "created_at")
search_fields = ("name", "code")
filter_horizontal = ("permission_tabs",)
fieldsets = (
("Asosiy", {
"fields": ("name", "code"),
}),
("Boglanishlar", {
"fields": ("permission_tabs",),
}),
)
@admin.register(Role)
class RoleAdmin(admin.ModelAdmin):
list_display = ("id", "name")
search_fields = ("name",)
filter_horizontal = (
"permissions",
"permission_to_tabs",
"permission_to_actions",
)
fieldsets = (
("Asosiy ma'lumotlar", {
"fields": ("name", "comment"),
}),
("Sahifa ruxsatlari", {
"fields": ("permissions",),
}),
("Bolim ruxsatlari", {
"fields": ("permission_to_tabs",),
}),
("Harakat ruxsatlari", {
"fields": ("permission_to_actions",),
}),
)

View File

@@ -0,0 +1,15 @@
from rest_framework.exceptions import PermissionDenied
from rest_framework.permissions import BasePermission
from core.apps.accounts.choices import RoleChoice
class IsAdminRole(BasePermission):
def has_permission(self, request, view):
if not request.user.is_authenticated:
return False
if request.user.role != RoleChoice.ADMIN:
raise PermissionDenied("Only admin can access this")
return True

View File

@@ -10,7 +10,7 @@ class PermissionToActionSerializer(serializers.ModelSerializer):
class PermissionToTabSerializer(serializers.ModelSerializer):
permission_to_actions = PermissionToActionSerializer(many=True)
permission_to_actions = PermissionToActionSerializer(many=True, read_only=True)
class Meta:
model = PermissionToTab
@@ -18,17 +18,35 @@ class PermissionToTabSerializer(serializers.ModelSerializer):
class PermissionSerializer(serializers.ModelSerializer):
permission_to_tabs = PermissionToTabSerializer(many=True)
permission_tabs = PermissionToTabSerializer(many=True, read_only=True)
class Meta:
model = Permission
fields = ['id', 'name', 'permission_to_tabs']
fields = ['id', 'name', 'permission_tabs']
class PermissionToActionListSerializer(serializers.ModelSerializer):
class Meta:
model = PermissionToAction
fields = ['id', 'name']
class PermissionToTabListSerializer(serializers.ModelSerializer):
class Meta:
model = PermissionToTab
fields = ['id', 'name']
class PermissionListSerializer(serializers.ModelSerializer):
class Meta:
model = Permission
fields = ['id', 'name']
class RoleListSerializer(serializers.ModelSerializer):
permissions = PermissionSerializer(many=True)
permission_to_tabs = PermissionToTabSerializer(many=True)
permission_to_actions = PermissionToActionSerializer(many=True)
permissions = PermissionListSerializer(many=True)
permission_to_tabs = PermissionToTabListSerializer(many=True)
permission_to_actions = PermissionToActionListSerializer(many=True)
class Meta:
model = Role

View File

@@ -54,4 +54,22 @@ class UserCreateSerializer(serializers.ModelSerializer):
"first_name",
"last_name",
"password",
"role"]
"role"
]
class ShortUserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = [
'id',
'first_name',
'last_name',
'avatar',
]
def get_avatar(self, obj):
request = self.context.get('request')
if obj.avatar:
return request.build_absolute_uri(obj.avatar.url)
return None

View File

@@ -9,6 +9,7 @@ from .views import RegisterView, ResetPasswordView, MeView, ChangePasswordView,
from rest_framework.routers import DefaultRouter
from .views.permission import PermissionToActionViewSet, PermissionToTabViewSet, PermissionViewSet, RoleViewSet
from core.apps.accounts.views.user import DeleteAdminUserApiView, UserDetailAPIView
router = DefaultRouter()
router.register("auth", RegisterView, basename="auth")
@@ -26,9 +27,11 @@ urlpatterns = [
path("", include(router.urls)),
path("auth/token/", jwt_views.TokenObtainPairView.as_view(), name="token_obtain_pair"),
path("auth/token/verify/", jwt_views.TokenVerifyView.as_view(), name="token_verify"),
path("auth/token/refresh/",jwt_views.TokenRefreshView.as_view()),
path("auth/token/refresh/", jwt_views.TokenRefreshView.as_view()),
path("user/list/", UserListApiView.as_view(), name="user-list"),
path("admin-user/list/", AdminUserListApiView.as_view(), name="admin-user-list"),
path("admin/create/", AdminCreateAPIView.as_view(), name="user-create"),
path("admin/update/<int:pk>/", AdminUpdateAPIView.as_view(), name="user-update"),
path('user/admin/<int:pk>/delete/', DeleteAdminUserApiView.as_view(), name='user-delete'),
path('user/<int:pk>/', UserDetailAPIView.as_view(), name='user-detail'),
]

View File

@@ -30,7 +30,6 @@ class PermissionToTabViewSet(BaseViewSetMixin, ModelViewSet):
serializer_class = PermissionToTabSerializer
@extend_schema(tags=["permission"])
class PermissionViewSet(BaseViewSetMixin, ModelViewSet):
queryset = Permission.objects.all()

View File

@@ -9,6 +9,8 @@ from rest_framework.views import APIView
from rest_framework.viewsets import ModelViewSet
from core.apps.accounts.choices.user import RoleChoice
from core.apps.accounts.models import Role
from core.apps.accounts.serializers.permission import RoleListSerializer
from core.apps.accounts.serializers.user import UserSerializer, AdminUserSerializer, UserCreateSerializer
User = get_user_model()
@@ -64,7 +66,7 @@ class AdminCreateAPIView(APIView):
return Response(serializer.data, status=201)
@extend_schema(tags=['User'],)
@extend_schema(tags=['User'], )
class AdminUpdateAPIView(generics.GenericAPIView):
permission_classes = [IsAuthenticated]
serializer_class = UserCreateSerializer
@@ -79,3 +81,32 @@ class AdminUpdateAPIView(generics.GenericAPIView):
serializer.save()
return Response(serializer.data, status=200)
class DeleteAdminUserApiView(APIView):
permission_classes = [IsAuthenticated]
def delete(self, request, pk):
user = get_object_or_404(User, pk=pk)
user.delete()
return Response(status=204)
class UserDetailAPIView(generics.RetrieveAPIView):
permission_classes = [IsAuthenticated]
serializer_class = UserSerializer
lookup_field = 'id'
class AdminPermissionsAPIView(generics.GenericAPIView):
permission_classes = [IsAuthenticated]
queryset = User.objects.all()
def get(self, request):
if request.user.role.name != RoleChoice.ADMIN:
return Response({'detail': 'Forbidden'}, status=403)
admin_role = Role.objects.get(name=RoleChoice.ADMIN)
serializer = RoleListSerializer(admin_role)
return Response(serializer.data)

View File

View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class DocumentsConfig(AppConfig):
name = "core.apps.documents"

View File

@@ -0,0 +1,13 @@
from rest_framework import serializers
class InspectionSerializer(serializers.Serializer):
tires = serializers.CharField(required=False, allow_blank=True, default="Qoniqarli")
engine = serializers.CharField(required=False, allow_blank=True, default="Qoniqarli")
chassis = serializers.CharField(required=False, allow_blank=True, default="Qoniqarli")
transmission = serializers.CharField(required=False, allow_blank=True, default="Qoniqarli")
body = serializers.CharField(required=False, allow_blank=True, default="Qoniqarli")
class ContractPDFRequestSerializer(serializers.Serializer):
inspection = InspectionSerializer(required=False)

View File

View File

@@ -0,0 +1,33 @@
from datetime import date
import requests
CBU_URL = "https://cbu.uz/oz/arkhiv-kursov-valyut/json/{code}/{date}/"
TIMEOUT_SECONDS = 5
CURRENCY_CODES = ("USD", "EUR", "RUB")
def fetch_rates(target_date):
"""CBU.uz dan berilgan sanaga oid USD, EUR, RUB kurslarini olish.
Tarmoq xatosi yoki notogri javob bolsa bosh dict qaytadi.
"""
if target_date is None:
target_date = date.today()
date_str = target_date.strftime("%Y-%m-%d")
rates = {}
for code in CURRENCY_CODES:
try:
resp = requests.get(
CBU_URL.format(code=code, date=date_str),
timeout=TIMEOUT_SECONDS,
)
resp.raise_for_status()
data = resp.json()
if isinstance(data, list) and data:
rate_value = data[0].get("Rate")
if rate_value:
rates[code] = rate_value
except (requests.RequestException, ValueError):
continue
return rates

View File

@@ -0,0 +1,7 @@
from django.urls import path
from core.apps.documents.views.contract import ValuationReportPDFView
urlpatterns = [
path('generate-contract-pdf/<int:pk>/', ValuationReportPDFView.as_view(), name='generate_contract_pdf'),
]

View File

@@ -0,0 +1,386 @@
from datetime import date
from decimal import Decimal
from django.shortcuts import get_object_or_404
from django.template.loader import render_to_string
from django.http import HttpResponse
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from weasyprint import HTML
from core.apps.evaluation.models import AutoEvaluationModel
from core.apps.evaluation.choices.auto import ObjectOwnerType
from core.apps.documents.serializers.contract import ContractPDFRequestSerializer
from core.apps.documents.services.cbu_rates import fetch_rates
UZ_MONTHS = {
1: "yanvar", 2: "fevral", 3: "mart", 4: "aprel",
5: "may", 6: "iyun", 7: "iyul", 8: "avgust",
9: "sentabr", 10: "oktabr", 11: "noyabr", 12: "dekabr",
}
UZ_ONES = [
"", "bir", "ikki", "uch", "to'rt", "besh",
"olti", "yetti", "sakkiz", "to'qqiz",
]
UZ_TENS = [
"", "o'n", "yigirma", "o'ttiz", "qirq", "ellik",
"oltmish", "yetmish", "sakson", "to'qson",
]
DEFAULT_INSPECTION = {
"tires": "Qoniqarli",
"engine": "Qoniqarli",
"chassis": "Qoniqarli",
"transmission": "Qoniqarli",
"body": "Qoniqarli",
}
def _format_currency(value):
if value is None:
return "0"
try:
int_val = int(Decimal(value))
except (ValueError, TypeError):
return "0"
return f"{int_val:,}".replace(",", " ")
def _format_date(value):
if not value:
return ""
return value.strftime("%d.%m.%Y")
def _three_digit_words(num):
if num == 0:
return ""
words = []
hundreds = num // 100
rest = num % 100
if hundreds:
if hundreds == 1:
words.append("bir yuz")
else:
words.append(f"{UZ_ONES[hundreds]} yuz")
tens = rest // 10
ones = rest % 10
if tens:
words.append(UZ_TENS[tens])
if ones:
words.append(UZ_ONES[ones])
return " ".join(words)
def _number_to_uzbek_words(value):
if value is None:
return ""
try:
num = int(Decimal(value))
except (ValueError, TypeError):
return ""
if num == 0:
return "nol"
parts = []
billions = num // 1_000_000_000
millions = (num % 1_000_000_000) // 1_000_000
thousands = (num % 1_000_000) // 1_000
rest = num % 1_000
if billions:
parts.append(f"{_three_digit_words(billions)} milliard")
if millions:
parts.append(f"{_three_digit_words(millions)} million")
if thousands:
parts.append(f"{_three_digit_words(thousands)} ming")
if rest:
parts.append(_three_digit_words(rest))
text = " ".join(parts).strip()
return text[0].upper() + text[1:] if text else ""
class ValuationReportPDFView(APIView):
"""
Baholash hisobotini PDF formatida yuklab olish uchun API.
GET /api/documents/generate-contract-pdf/<pk>/
POST /api/documents/generate-contract-pdf/<pk>/
POST so'rov tanasida inspection malumotlarini yuborish mumkin:
{
"inspection": {
"tires": "...",
"engine": "...",
"chassis": "...",
"transmission": "...",
"body": "..."
}
}
"""
def get(self, request, pk, *args, **kwargs):
return self._generate_pdf(request, pk, payload={})
def post(self, request, pk, *args, **kwargs):
serializer = ContractPDFRequestSerializer(data=request.data or {})
serializer.is_valid(raise_exception=True)
return self._generate_pdf(request, pk, payload=serializer.validated_data)
def _generate_pdf(self, request, pk, payload):
auto_evaluation = get_object_or_404(
AutoEvaluationModel.objects.select_related(
"user",
"vehicle",
"vehicle__brand",
"vehicle__model",
"vehicle__color",
"vehicle__fuel_type",
"vehicle__body_type",
),
pk=pk,
)
context = self._build_context(auto_evaluation, payload)
html_string = render_to_string("documents/contract.html", context)
base_url = request.build_absolute_uri("/")
try:
pdf_file = HTML(string=html_string, base_url=base_url).write_pdf()
except Exception as e:
return Response(
{"error": f"PDF yaratishda xatolik: {str(e)}"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
report_number = context["report"]["number"]
filename = f"baholash_hisoboti_{report_number}.pdf"
response = HttpResponse(pdf_file, content_type="application/pdf")
response["Content-Disposition"] = f'attachment; filename="{filename}"'
response["Content-Length"] = len(pdf_file)
return response
def _build_context(self, auto, payload):
vehicle = auto.vehicle
user = auto.user
report_date = auto.rate_report_date or auto.contract_date or date.today()
valuation_date = auto.rate_date or report_date
inspection_date = auto.object_inspection_date or report_date
report_number = auto.registration_number or f"{auto.pk}/{report_date.year}"
# Bozor qiymati hozircha hisoblanmagan — default 0.
final_value = None
market_value_formatted = (
f"{_format_currency(final_value)} so'm" if final_value is not None else "0 so'm"
)
market_value_words = (
f"{_number_to_uzbek_words(final_value)} so'm" if final_value is not None else ""
)
cost_final = final_value
comparative_final = final_value
vehicle_ctx = self._vehicle_context(auto, vehicle)
customer_ctx = self._customer_context(user)
owner_ctx = self._owner_context(auto)
contract_ctx = self._contract_context(auto, report_date)
inspection_ctx = self._inspection_context(payload)
rates_ctx = self._rates_context(valuation_date)
ctx = {
"logo_url": "",
"report": {
"number": report_number,
"date": _format_date(report_date),
"valuation_date": _format_date(valuation_date),
"inspection_date": _format_date(inspection_date),
"year": str(report_date.year or date.today().year),
"market_value_formatted": market_value_formatted,
"market_value_words": market_value_words,
},
"vehicle": vehicle_ctx,
"customer": customer_ctx,
"owner": owner_ctx,
"contract": contract_ctx,
"company": {
"director": "",
},
"rates": rates_ctx,
"inspection": inspection_ctx,
"cost": {
"engine_volume": "",
"factory_price": _format_currency(cost_final),
"replacement_value": _format_currency(cost_final),
"wear_percent": "",
"final_value": _format_currency(cost_final),
"final_value_words": _number_to_uzbek_words(cost_final) + (" so'm" if cost_final else ""),
},
"comparative": {
"final_value": _format_currency(comparative_final),
"final_value_usd": "",
"final_value_words": _number_to_uzbek_words(comparative_final) + (" so'm" if comparative_final else ""),
},
"approach": {
"cost": {
"value": _format_currency(cost_final),
"weight": "30%",
"weighted": "",
},
"comparative": {
"value": _format_currency(comparative_final),
"weight": "70%",
"weighted": "",
},
"weighted_total": _format_currency(final_value),
},
"analog_1": self._empty_analog(),
"analog_2": self._empty_analog(),
"analog_3": self._empty_analog(),
}
return ctx
def _vehicle_context(self, auto, vehicle):
brand_name = ""
model_name = ""
if vehicle:
brand_name = vehicle.brand.name if vehicle.brand else ""
model_name = vehicle.model.name if vehicle.model else ""
if not brand_name:
brand_name = auto.car_brand or ""
if not model_name:
model_name = auto.car_model or ""
full_brand = f"{brand_name} {model_name}".strip()
plate_number = (vehicle.license_plate if vehicle else None) or auto.car_number or ""
manufacture_year = ""
if vehicle and vehicle.manufacture_year:
manufacture_year = str(vehicle.manufacture_year)
elif auto.manufacture_year:
manufacture_year = str(auto.manufacture_year)
production_date = f"{manufacture_year}-yil" if manufacture_year else ""
engine_number = (vehicle.engine_number if vehicle else None) or auto.car_dvigatel_number or ""
body_number = vehicle.vin_number if vehicle and vehicle.vin_number else ""
color_value = ""
if vehicle and vehicle.color:
color_value = vehicle.color.name
elif auto.car_color:
color_value = auto.car_color
fuel_type_value = ""
if vehicle and vehicle.fuel_type:
fuel_type_value = vehicle.fuel_type.name
tech_passport_value = ""
if vehicle and (vehicle.tech_passport_series or vehicle.tech_passport_number):
tech_passport_value = (
f"{vehicle.tech_passport_series or ''}{vehicle.tech_passport_number or ''}"
).strip()
elif auto.tex_passport_serie_num:
tech_passport_value = auto.tex_passport_serie_num
return {
"brand": full_brand,
"plate_number": plate_number,
"production_date": production_date,
"production_year": manufacture_year,
"type": auto.get_object_type_display() if auto.object_type else "",
"engine_number": engine_number,
"body_number": body_number,
"chassis_number": body_number,
"color": color_value,
"tech_passport": tech_passport_value,
"fuel_type": fuel_type_value,
"engine_power": "",
"full_weight": "",
"empty_weight": "",
}
def _customer_context(self, user):
empty = {
"name": "",
"address": "",
"phone": "",
"tin": "",
"account": "",
"bank": "",
"mfo": "",
}
if not user:
return empty
full_name = " ".join(filter(None, [user.last_name, user.first_name])).strip()
if not full_name:
full_name = user.username or user.phone or ""
return {
"name": full_name,
"address": "",
"phone": user.phone or "",
"tin": "",
"account": "",
"bank": "",
"mfo": "",
}
def _owner_context(self, auto):
if auto.object_owner_type == ObjectOwnerType.LEGAL:
return {
"name": auto.object_owner_legal_entity or "",
"address": auto.object_owner_residence or "",
}
full_name = " ".join(
filter(None, [
auto.object_owner_individual_person_l_name,
auto.object_owner_individual_person_f_name,
auto.object_owner_individual_person_p_name,
])
).strip()
return {
"name": full_name,
"address": auto.object_owner_residence or "",
}
def _contract_context(self, auto, fallback_date):
contract_date = auto.contract_date or auto.rate_report_date or fallback_date
return {
"number": auto.registration_number or str(auto.pk),
"day": f"{contract_date.day:02d}",
"month": UZ_MONTHS.get(contract_date.month, ""),
"year": str(contract_date.year),
}
def _inspection_context(self, payload):
provided = (payload or {}).get("inspection") or {}
return {
key: provided.get(key) or default
for key, default in DEFAULT_INSPECTION.items()
}
def _rates_context(self, target_date):
rates = fetch_rates(target_date)
return {
"rur": rates.get("RUB", ""),
"usd": rates.get("USD", ""),
"eur": rates.get("EUR", ""),
}
def _empty_analog(self):
return {
"source": "",
"phone": "",
"description": "",
"year": "",
"mileage": "",
"price": "",
"adjusted_price_1": "",
"final_price": "",
"weight": "",
}

View File

@@ -55,7 +55,7 @@ class AutoEvaluationAdmin(ModelAdmin):
"fields": (
"tex_passport_serie_num",
("tex_passport_gived_date", "tex_passport_gived_location"),
("car_type", "car_wheel"),
("car_wheel",),
("car_brand", "car_model"),
("car_number", "manufacture_year"),
("car_dvigatel_number", "car_color"),

View File

@@ -6,6 +6,8 @@ class AutoObjectType(models.TextChoices):
LIGHTWEIGHT_AUTO = "lightweight_auto", _("Yengil automobil")
TRUCK_CAR = "truck_car", _("Yuk automobil")
SPECIAL_TECH = "special_tech", _("Maxsus texnika")
BUS = "bus", _("Avtobus")
MOTO = "moto", _("Mototsikl")
class AutoEvaluationStatus(models.TextChoices):

View File

@@ -14,3 +14,4 @@ class ReferenceType(models.TextChoices):
DETERMINED_VALUE = "determined_value", _("Determined value type")
PROPERTY_RIGHTS = "property_rights", _("Property rights")
OWNERSHIP_FORM = "ownership_form", _("Ownership form")
ASSESSMENT_TASK_TYPE = "assessment_task_type", _("Assessment task type")

View File

@@ -19,3 +19,8 @@ class RequestStatus(models.TextChoices):
IN_PROGRESS = "in_progress", _("Jarayonda")
COMPLETED = "completed", _("Bajarildi")
REJECTED = "rejected", _("Rad etildi")
class RequestPersonType(models.TextChoices):
INDIVIDUAL_PERSON = "individual_person", "Jismoniy shaxs"
LEGAL_PERSON = "legal_person", 'Yuridik shaxs',

View File

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

View File

@@ -1,7 +1,11 @@
from django_filters import rest_framework as filters
from core.apps.evaluation.choices.history import EvaluationEventType
from core.apps.evaluation.models import AutoevaluationhistoryModel, QuickevaluationhistoryModel
from core.apps.evaluation.models import (
AutoevaluationhistoryModel,
MechanicAutoevaluationhistoryModel,
QuickevaluationhistoryModel,
)
class AutoevaluationhistoryFilter(filters.FilterSet):
@@ -29,6 +33,31 @@ class AutoevaluationhistoryFilter(filters.FilterSet):
]
class MechanicAutoevaluationhistoryFilter(filters.FilterSet):
mechanic_auto_evaluation = filters.NumberFilter(
field_name="mechanic_auto_evaluation", lookup_expr="exact"
)
event_type = filters.ChoiceFilter(
field_name="event_type",
choices=EvaluationEventType.choices,
)
created_from = filters.DateTimeFilter(
field_name="created_at", lookup_expr="gte"
)
created_to = filters.DateTimeFilter(
field_name="created_at", lookup_expr="lte"
)
class Meta:
model = MechanicAutoevaluationhistoryModel
fields = [
"mechanic_auto_evaluation",
"event_type",
"created_from",
"created_to",
]
class QuickevaluationhistoryFilter(filters.FilterSet):
quick_evaluation = filters.NumberFilter(
field_name="quick_evaluation", lookup_expr="exact"

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,53 @@
# Generated by Django 5.2.7 on 2026-04-28 11:07
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evaluation', '0035_autoevaluationmodel_is_archived'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.RemoveField(
model_name='autoevaluationmodel',
name='form_ownership',
),
migrations.RemoveField(
model_name='autoevaluationmodel',
name='property_rights',
),
migrations.RemoveField(
model_name='autoevaluationmodel',
name='rate_object_name',
),
migrations.AlterField(
model_name='autoevaluationmodel',
name='appraisers',
field=models.ManyToManyField(blank=True, null=True, to=settings.AUTH_USER_MODEL, verbose_name='appraisers'),
),
migrations.AlterField(
model_name='autoevaluationmodel',
name='evaluation_request',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='auto_evaluations_request', to='evaluation.evaluationrequestmodel'),
),
migrations.AlterField(
model_name='autoevaluationmodel',
name='user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='auto_evaluations_user', to=settings.AUTH_USER_MODEL),
),
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'),
),
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'),
),
]

View File

@@ -0,0 +1,49 @@
# Generated by Django 5.2.7 on 2026-04-28 11:41
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evaluation', '0036_remove_autoevaluationmodel_form_ownership_and_more'),
]
operations = [
migrations.AddField(
model_name='evaluationrequestmodel',
name='customer_and_owner_same',
field=models.BooleanField(default=False),
),
migrations.CreateModel(
name='EvaluationRequestCustomerModel',
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)),
('type', models.CharField(choices=[('individual_person', 'Jismoniy shaxs'), ('legal_person', 'Yuridik shaxs')], max_length=100)),
('jshshir', models.CharField(max_length=100)),
('evaluation_request', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='customer', to='evaluation.evaluationrequestmodel')),
],
options={
'verbose_name': 'Evaluation Request Customer',
'verbose_name_plural': 'Evaluation Request Customers',
},
),
migrations.CreateModel(
name='EvaluationRequestOwnerModel',
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)),
('type', models.CharField(choices=[('individual_person', 'Jismoniy shaxs'), ('legal_person', 'Yuridik shaxs')], max_length=100)),
('jshshir', models.CharField(max_length=100)),
('evaluation_request', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='owner', to='evaluation.evaluationrequestmodel')),
],
options={
'verbose_name': 'Evaluation Request Owner',
'verbose_name_plural': 'Evaluation Request Owners',
},
),
]

View File

@@ -0,0 +1,88 @@
# Generated by Django 5.2.7 on 2026-04-28 11:47
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evaluation', '0037_evaluationrequestmodel_customer_and_owner_same_and_more'),
]
operations = [
migrations.AddField(
model_name='evaluationrequestmodel',
name='distance_covered',
field=models.FloatField(blank=True, default=0.0, null=True),
),
migrations.AddField(
model_name='evaluationrequestmodel',
name='gov_number',
field=models.CharField(blank=True, max_length=100, null=True),
),
migrations.AlterField(
model_name='evaluationrequestmodel',
name='chassi',
field=models.CharField(blank=True, max_length=100, null=True),
),
migrations.AlterField(
model_name='evaluationrequestmodel',
name='customer_inn_number',
field=models.CharField(max_length=20),
),
migrations.AlterField(
model_name='evaluationrequestmodel',
name='is_archive',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='evaluationrequestmodel',
name='location_lat',
field=models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True),
),
migrations.AlterField(
model_name='evaluationrequestmodel',
name='location_lng',
field=models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True),
),
migrations.AlterField(
model_name='evaluationrequestmodel',
name='location_name',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AlterField(
model_name='evaluationrequestmodel',
name='need_delivering',
field=models.BooleanField(default=True),
),
migrations.AlterField(
model_name='evaluationrequestmodel',
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),
),
migrations.AlterField(
model_name='evaluationrequestmodel',
name='owner_inn_number',
field=models.CharField(max_length=20),
),
migrations.AlterField(
model_name='evaluationrequestmodel',
name='rate_type',
field=models.CharField(choices=[('auto', 'Automobil'), ('real_estate', "Ko'chmas mulk"), ('equipment', 'Uskuna')], max_length=50),
),
migrations.AlterField(
model_name='evaluationrequestmodel',
name='status',
field=models.CharField(choices=[('pending', 'Kutilmoqda'), ('in_progress', 'Jarayonda'), ('completed', 'Bajarildi'), ('rejected', 'Rad etildi')], default='pending', max_length=50),
),
migrations.AlterField(
model_name='evaluationrequestmodel',
name='tex_passport',
field=models.CharField(blank=True, max_length=20, null=True),
),
migrations.AlterField(
model_name='evaluationrequestmodel',
name='worked_hours',
field=models.IntegerField(blank=True, null=True),
),
]

View File

@@ -0,0 +1,31 @@
# Generated by Django 5.2.7 on 2026-05-01 06:45
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evaluation', '0038_evaluationrequestmodel_distance_covered_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Bonus',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('updated_at', models.DateTimeField(auto_now=True)),
('bonus_type', models.CharField(choices=[('lightweight_auto', 'Yengil automobil'), ('truck_car', 'Yuk automobil'), ('special_tech', 'Maxsus texnika')], max_length=50)),
('percentage', models.FloatField()),
('created_at', models.DateTimeField(auto_now_add=True)),
('price', models.FloatField()),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bonuses', to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
},
),
]

View File

@@ -0,0 +1,59 @@
# Generated by Django 5.2.7 on 2026-05-01 11:43
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evaluation', '0039_bonus'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='BaseValueBonus',
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)),
('base_price', models.DecimalField(decimal_places=2, max_digits=12)),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='BonusType',
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)),
('name', models.CharField(max_length=255)),
('category', models.CharField(choices=[('auto_transport', 'Avtotransport'), ('real estate', "ko'chmas mulk"), ('equipment', 'uskuna va jihozlar')], max_length=50)),
('percentage', models.PositiveIntegerField()),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='EmployeeBonus',
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)),
('percentage', models.PositiveIntegerField()),
('bonus_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='evaluation.bonustype')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bonuses', to=settings.AUTH_USER_MODEL)),
],
options={
'unique_together': {('user', 'bonus_type')},
},
),
migrations.DeleteModel(
name='Bonus',
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 5.2.7 on 2026-05-01 12:06
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('evaluation', '0040_basevaluebonus_bonustype_employeebonus_delete_bonus'),
]
operations = [
migrations.RenameModel(
old_name='BonusType',
new_name='BonusCategory',
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.7 on 2026-05-04 12:40
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evaluation', '0041_rename_bonustype_bonuscategory'),
]
operations = [
migrations.AlterField(
model_name='bonuscategory',
name='category',
field=models.CharField(choices=[('lightweight_auto', 'Yengil automobil'), ('truck_car', 'Yuk automobil'), ('special_tech', 'Maxsus texnika')], max_length=50),
),
]

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

@@ -0,0 +1,167 @@
# Generated by Django 5.2.7 on 2026-05-05 12:06
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("evaluation", "0043_mechanicautoevaluationmodel_and_more"),
]
operations = [
migrations.RemoveField(
model_name="autoevaluationmodel",
name="car_type",
),
migrations.RemoveField(
model_name="autoevaluationmodel",
name="tex_passport_file",
),
migrations.AddField(
model_name="autoevaluationmodel",
name="assessment_task_type",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="evaluation_auto_assessment_task_type",
to="evaluation.referenceitemmodel",
verbose_name="assessment task type",
),
),
migrations.AddField(
model_name="autoevaluationmodel",
name="body_type",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="evaluation_auto_body_type",
to="evaluation.referenceitemmodel",
verbose_name="body type",
),
),
migrations.AddField(
model_name="autoevaluationmodel",
name="car_position",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="evaluation_auto_car_position",
to="evaluation.referenceitemmodel",
verbose_name="car position",
),
),
migrations.AddField(
model_name="autoevaluationmodel",
name="distance_covered",
field=models.PositiveIntegerField(blank=True, null=True, verbose_name="distance covered"),
),
migrations.AddField(
model_name="autoevaluationmodel",
name="fuel_type",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="evaluation_auto_fuel_type",
to="evaluation.referenceitemmodel",
verbose_name="fuel type",
),
),
migrations.AddField(
model_name="autoevaluationmodel",
name="object_owner_residence",
field=models.CharField(blank=True, max_length=255, null=True, verbose_name="object owner residence"),
),
migrations.AddField(
model_name="autoevaluationmodel",
name="state_car",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="evaluation_auto_state_car",
to="evaluation.referenceitemmodel",
verbose_name="state car",
),
),
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"),
("bus", "Avtobus"),
("moto", "Mototsikl"),
],
max_length=50,
null=True,
verbose_name="object type",
),
),
migrations.AlterField(
model_name="bonuscategory",
name="category",
field=models.CharField(
choices=[
("lightweight_auto", "Yengil automobil"),
("truck_car", "Yuk automobil"),
("special_tech", "Maxsus texnika"),
("bus", "Avtobus"),
("moto", "Mototsikl"),
],
max_length=50,
),
),
migrations.AlterField(
model_name="mechanicautoevaluationmodel",
name="object_type",
field=models.CharField(
blank=True,
choices=[
("lightweight_auto", "Yengil automobil"),
("truck_car", "Yuk automobil"),
("special_tech", "Maxsus texnika"),
("bus", "Avtobus"),
("moto", "Mototsikl"),
],
max_length=50,
null=True,
verbose_name="object type",
),
),
migrations.CreateModel(
name="AutoEvaluationTexPassportFile",
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="auto_evaluation/tech_passports/%Y/%m/", verbose_name="tech passport file"
),
),
(
"auto_evaluation",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="tex_passport_files",
to="evaluation.autoevaluationmodel",
),
),
],
options={
"verbose_name": "Auto Evaluation Tex Passport File",
"verbose_name_plural": "Auto Evaluation Tex Passport Files",
"db_table": "AutoEvaluationTexPassportFile",
},
),
]

View File

@@ -0,0 +1,16 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evaluation', '0044_remove_autoevaluationmodel_car_type_and_more'),
]
operations = [
migrations.AlterField(
model_name='referenceitemmodel',
name='type',
field=models.CharField(choices=[('brand', 'Brand'), ('marka', 'Marka'), ('color', 'Color'), ('fuel_type', 'Fuel type'), ('body_type', 'Body type'), ('car_position', 'Car position'), ('state_car', 'Car state'), ('evaluation_purpose', 'Evaluation purpose'), ('determined_value', 'Determined value type'), ('property_rights', 'Property rights'), ('ownership_form', 'Ownership form'), ('assessment_task_type', 'Assessment task type')], max_length=50, verbose_name='type'),
),
]

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

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

View File

@@ -4,19 +4,15 @@ 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
@@ -26,7 +22,6 @@ class AutoEvaluationModel(AbstractBaseModel):
"accounts.User",
on_delete=models.SET_NULL,
related_name="auto_evaluations_user",
verbose_name=_("user"),
null=True,
blank=True,
)
@@ -34,7 +29,6 @@ class AutoEvaluationModel(AbstractBaseModel):
"evaluation.EvaluationRequestModel",
on_delete=models.SET_NULL,
related_name="auto_evaluations_request",
verbose_name=_("evaluation request"),
null=True,
blank=True,
)
@@ -42,7 +36,6 @@ class AutoEvaluationModel(AbstractBaseModel):
ValuationModel,
on_delete=models.CASCADE,
related_name="auto_detail",
verbose_name=_("valuation"),
null=True,
blank=True,
)
@@ -50,24 +43,67 @@ class AutoEvaluationModel(AbstractBaseModel):
VehicleModel,
on_delete=models.CASCADE,
related_name="evaluation",
verbose_name=_("vehicle"),
null=True,
blank=True,
)
appraisers = models.ManyToManyField(
"accounts.User",
verbose_name=_("appraisers"),
related_name="auto_evaluations",
blank=True,
null=True,
)
tex_passport_file = models.FileField(
verbose_name=_("tech passport file"),
upload_to="quick_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_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_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_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_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_auto_assessment_task_type',
)
# ── Step 1 — Umumiy ma'lumotlar ──────────────────────────────────
registration_number = models.CharField(
@@ -96,12 +132,6 @@ class AutoEvaluationModel(AbstractBaseModel):
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,
@@ -153,23 +183,6 @@ class AutoEvaluationModel(AbstractBaseModel):
blank=True,
null=True,
)
property_rights = models.ForeignKey(
'evaluation.ReferenceitemModel',
verbose_name=_("property rights"),
blank=True,
null=True,
on_delete=models.SET_NULL,
related_name='evaluation_auto_property_rights'
)
form_ownership = models.ForeignKey(
'evaluation.ReferenceitemModel',
verbose_name=_("form of ownership"),
blank=True,
null=True,
on_delete=models.SET_NULL,
related_name='evaluation_auto_form_ownership'
)
value_determined = models.ForeignKey(
'evaluation.ReferenceitemModel',
verbose_name=_("value determined"),
@@ -205,12 +218,6 @@ class AutoEvaluationModel(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,
@@ -272,8 +279,6 @@ class AutoEvaluationModel(AbstractBaseModel):
default=False,
)
def __str__(self):
return f"Auto Evaluation {self.registration_number or self.pk}"
@@ -285,3 +290,27 @@ class AutoEvaluationModel(AbstractBaseModel):
db_table = "AutoEvaluation"
verbose_name = _("Auto Evaluation")
verbose_name_plural = _("Auto Evaluations")
class AutoEvaluationTexPassportFile(AbstractBaseModel):
auto_evaluation = models.ForeignKey(
AutoEvaluationModel,
on_delete=models.CASCADE,
related_name="tex_passport_files",
)
file = models.FileField(
verbose_name=_("tech passport file"),
upload_to="auto_evaluation/tech_passports/%Y/%m/",
)
def __str__(self):
return f"Tex passport file for AutoEvaluation #{self.auto_evaluation_id}"
@classmethod
def _baker(cls):
return baker.make(cls)
class Meta:
db_table = "AutoEvaluationTexPassportFile"
verbose_name = _("Auto Evaluation Tex Passport File")
verbose_name_plural = _("Auto Evaluation Tex Passport Files")

View File

@@ -0,0 +1,33 @@
from django.db import models
from django.db.models.fields import PositiveIntegerField
from django_core.models import AbstractBaseModel
from core.apps.evaluation.choices.auto import AutoObjectType
class BaseValueBonus(AbstractBaseModel):
base_price = models.DecimalField(max_digits=12, decimal_places=2)
def __str__(self):
return f"Base: {self.base_price}"
class BonusCategory(AbstractBaseModel):
name = models.CharField(max_length=255)
category = models.CharField(
max_length=50,
choices=AutoObjectType.choices
)
percentage = PositiveIntegerField()
def __str__(self):
return self.name
class EmployeeBonus(AbstractBaseModel):
user = models.ForeignKey("accounts.User", on_delete=models.CASCADE, related_name="bonuses", )
bonus_type = models.ForeignKey(BonusCategory, on_delete=models.CASCADE)
percentage = models.PositiveIntegerField()
class Meta:
unique_together = ("user", "bonus_type")

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

View File

@@ -0,0 +1,312 @@
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 (
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,
)
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(
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_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")
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,12 +3,11 @@ from django.utils.translation import gettext_lazy as _
from django_core.models import AbstractBaseModel
from model_bakery import baker
from .valuation import ValuationModel
from core.apps.evaluation.choices.movable import (
MovablePropertyCategory,
MovablePropertyCondition,
)
from .valuation import ValuationModel
class MovablePropertyEvaluationModel(AbstractBaseModel):
@@ -51,4 +50,3 @@ class MovablePropertyEvaluationModel(AbstractBaseModel):
db_table = "MovablePropertyEvaluation"
verbose_name = _("Movable Property Evaluation")
verbose_name_plural = _("Movable Property Evaluations")

View File

@@ -8,64 +8,41 @@ from core.apps.evaluation.choices.request import (
EvaluationRateType,
RequestObjectType,
RequestStatus,
RequestPersonType,
)
from core.apps.evaluation.models import ReferenceitemModel
class EvaluationrequestModel(AbstractBaseModel):
# request sender
rate_type = models.CharField(max_length=50,choices=EvaluationRateType.choices)
object_type = models.CharField(max_length=50,choices=RequestObjectType.choices,blank=True,null=True)
status = models.CharField(max_length=50, choices=RequestStatus.choices, default=RequestStatus.PENDING)
distance_covered = models.FloatField(default=0.0, null=True, blank=True)
worked_hours = models.IntegerField(blank=True,null=True)
customer_inn_number = models.CharField(max_length=20)
owner_inn_number = models.CharField(max_length=20)
tex_passport = models.CharField(max_length=20,blank=True,null=True)
chassi = models.CharField(max_length=100,blank=True,null=True)
gov_number = models.CharField(max_length=100, null=True, blank=True)
location_name = models.CharField(max_length=255,blank=True,null=True)
location_lat = models.DecimalField(max_digits=9,decimal_places=6,blank=True, null=True)
location_lng = models.DecimalField(max_digits=9,decimal_places=6,blank=True,null=True)
need_delivering = models.BooleanField(default=True)
is_archive = models.BooleanField(default=False)
customer_and_owner_same = models.BooleanField(default=False)
###################
# Foreign Keys
###################
user = models.ForeignKey(
"accounts.User",
on_delete=models.CASCADE,
related_name="evaluation_requests",
verbose_name=_("user"),
)
# request type -> "Automobil", "Ko'chmas mulk", "Uskuna"
rate_type = models.CharField(
verbose_name=_("rate type"),
max_length=50,
choices=EvaluationRateType.choices,
)
###################
# Automobil fields
###################
object_type = models.CharField(
verbose_name=_("object type"),
max_length=50,
choices=RequestObjectType.choices,
blank=True,
null=True,
)
customer_inn_number = models.CharField(
verbose_name=_("customer INN number"),
max_length=20,
)
owner_inn_number = models.CharField(
verbose_name=_("owner INN number"),
max_length=20,
)
tex_passport = models.CharField(
verbose_name=_("tex passport"),
max_length=20,
blank=True,
null=True,
)
worked_hours = models.IntegerField(
verbose_name=_("worked hours"),
blank=True,
null=True,
)
chassi = models.CharField(
verbose_name=_("chassi"),
max_length=100,
blank=True,
null=True,
)
###################
# Value fields
###################
value_determined = models.ForeignKey(
"evaluation.ReferenceitemModel",
verbose_name=_("value determined"),
@@ -99,47 +76,6 @@ class EvaluationrequestModel(AbstractBaseModel):
null=True,
)
###################
# Location fields
###################
location_name = models.CharField(
verbose_name=_("location name"),
max_length=255,
blank=True,
null=True,
)
location_lat = models.DecimalField(
verbose_name=_("location latitude"),
max_digits=9,
decimal_places=6,
blank=True,
null=True,
)
location_lng = models.DecimalField(
verbose_name=_("location longitude"),
max_digits=9,
decimal_places=6,
blank=True,
null=True,
)
###################
# Other fields
###################
need_delivering = models.BooleanField(
verbose_name=_("need delivering"),
default=True,
)
status = models.CharField(
verbose_name=_("status"),
max_length=50,
choices=RequestStatus.choices,
default=RequestStatus.PENDING,
)
is_archive = models.BooleanField(
verbose_name=_("is archive"),
default=False,
)
def __str__(self):
return f"Requests #{self.pk}{self.get_rate_type_display()}"
@@ -166,3 +102,29 @@ class EvaluationrequestModel(AbstractBaseModel):
db_table = "EvaluationRequest"
verbose_name = _("Evaluation Request")
verbose_name_plural = _("Evaluation Requests")
class EvaluationRequestOwnerModel(AbstractBaseModel):
evaluation_request = models.OneToOneField(EvaluationrequestModel, on_delete=models.CASCADE, related_name='owner')
type = models.CharField(max_length=100, choices=RequestPersonType.choices)
jshshir = models.CharField(max_length=100)
def __str__(self):
return f"Owner #{self.pk}{self.type}"
class Meta:
verbose_name = _("Evaluation Request Owner")
verbose_name_plural = _("Evaluation Request Owners")
class EvaluationRequestCustomerModel(AbstractBaseModel):
evaluation_request = models.OneToOneField(EvaluationrequestModel, on_delete=models.CASCADE, related_name='customer')
type = models.CharField(max_length=100, choices=RequestPersonType.choices)
jshshir = models.CharField(max_length=100)
def __str__(self):
return f"Customer #{self.pk}{self.type}"
class Meta:
verbose_name = _("Evaluation Request Customer")
verbose_name_plural = _("Evaluation Request Customers")

View File

@@ -3,13 +3,36 @@ 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 AutoEvaluationModel, ReferenceitemModel, EvaluationrequestModel
from core.apps.evaluation.models import (
AutoEvaluationModel,
AutoEvaluationTexPassportFile,
ReferenceitemModel,
EvaluationrequestModel,
)
from core.apps.evaluation.serializers.reference import ListReferenceitemSerializer
User = get_user_model()
class AutoEvaluationTexPassportFileSerializer(serializers.ModelSerializer):
file = serializers.SerializerMethodField()
class Meta:
model = AutoEvaluationTexPassportFile
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 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)
@@ -17,8 +40,12 @@ class BaseAutoevaluationSerializer(serializers.ModelSerializer):
default=None)
rate_type = ListReferenceitemSerializer(read_only=True)
value_determined = ListReferenceitemSerializer(read_only=True)
property_rights = ListReferenceitemSerializer(read_only=True)
form_ownership = 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 = AutoEvaluationTexPassportFileSerializer(many=True, read_only=True)
user = serializers.SerializerMethodField(method_name="get_user", read_only=True)
class Meta:
@@ -34,7 +61,9 @@ class BaseAutoevaluationSerializer(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",
@@ -44,13 +73,17 @@ class BaseAutoevaluationSerializer(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",
"value_determined",
"rate_type",
"property_rights",
"form_ownership",
"user",
"evaluation_request",
]
@@ -72,16 +105,8 @@ class ListAutoevaluationSerializer(BaseAutoevaluationSerializer):
class RetrieveAutoevaluationSerializer(BaseAutoevaluationSerializer):
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 + [
# Step 1
@@ -89,7 +114,6 @@ class RetrieveAutoevaluationSerializer(BaseAutoevaluationSerializer):
"object_inspection_date",
"rate_date",
"rate_report_date",
"rate_object_name",
# Step 2
"object_owner_type",
"object_owner_type_display",
@@ -101,11 +125,8 @@ class RetrieveAutoevaluationSerializer(BaseAutoevaluationSerializer):
"object_owner_legal_inn",
# Step 4
"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",
@@ -118,21 +139,6 @@ class RetrieveAutoevaluationSerializer(BaseAutoevaluationSerializer):
class UpdateAutoevaluationSerializer(serializers.ModelSerializer):
property_rights = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
required=False,
allow_null=True,
)
value_determined = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
required=False,
allow_null=True,
)
form_ownership = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
required=False,
allow_null=True,
)
value_determined = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
required=False,
@@ -143,6 +149,36 @@ class UpdateAutoevaluationSerializer(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,
)
tex_passport_files = serializers.ListField(
child=serializers.FileField(),
required=False,
write_only=True,
)
class Meta:
model = AutoEvaluationModel
@@ -153,7 +189,6 @@ class UpdateAutoevaluationSerializer(serializers.ModelSerializer):
"object_inspection_date",
"rate_date",
"rate_report_date",
"rate_object_name",
"object_type",
# Step 2
"object_owner_type",
@@ -163,16 +198,15 @@ class UpdateAutoevaluationSerializer(serializers.ModelSerializer):
"object_owner_individual_person_passport_num",
"object_owner_legal_entity",
"object_owner_legal_inn",
"property_rights",
"form_ownership",
"object_owner_residence",
"value_determined",
"rate_type",
"assessment_task_type",
# Step 4
"tex_passport_file",
"tex_passport_files",
"tex_passport_serie_num",
"tex_passport_gived_date",
"tex_passport_gived_location",
"car_type",
"car_wheel",
"car_brand",
"car_model",
@@ -180,6 +214,11 @@ class UpdateAutoevaluationSerializer(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):
@@ -224,23 +263,23 @@ class UpdateAutoevaluationSerializer(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:
AutoEvaluationTexPassportFile.objects.bulk_create([
AutoEvaluationTexPassportFile(auto_evaluation=instance, file=f) for f in files
])
return instance
def to_representation(self, instance):
return RetrieveAutoevaluationSerializer(instance, context=self.context).data
class CreateAutoevaluationSerializer(serializers.ModelSerializer):
property_rights = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
required=False,
allow_null=True,
)
value_determined = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
required=False,
allow_null=True,
)
form_ownership = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
required=False,
allow_null=True,
)
value_determined = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
required=False,
@@ -251,11 +290,41 @@ class CreateAutoevaluationSerializer(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,
allow_null=True,
)
tex_passport_files = serializers.ListField(
child=serializers.FileField(),
required=False,
write_only=True,
)
class Meta:
model = AutoEvaluationModel
@@ -267,7 +336,6 @@ class CreateAutoevaluationSerializer(serializers.ModelSerializer):
"object_inspection_date",
"rate_date",
"rate_report_date",
"rate_object_name",
"object_type",
# Step 2
"object_owner_type",
@@ -277,16 +345,15 @@ class CreateAutoevaluationSerializer(serializers.ModelSerializer):
"object_owner_individual_person_passport_num",
"object_owner_legal_entity",
"object_owner_legal_inn",
"property_rights",
"form_ownership",
"object_owner_residence",
"value_determined",
"rate_type",
"assessment_task_type",
# Step 4
"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",
@@ -294,6 +361,11 @@ class CreateAutoevaluationSerializer(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):
@@ -338,13 +410,23 @@ class CreateAutoevaluationSerializer(serializers.ModelSerializer):
return attrs
def create(self, validated_data):
files = validated_data.pop("tex_passport_files", [])
user = self.context.get('request').user
validated_data['user'] = user
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:
AutoEvaluationTexPassportFile.objects.bulk_create([
AutoEvaluationTexPassportFile(auto_evaluation=instance, file=f) for f in files
])
return instance
def to_representation(self, instance):
return RetrieveAutoevaluationSerializer(instance, context=self.context).data
class AutoEvaluationAppraisersSerializer(serializers.Serializer):
@@ -359,6 +441,7 @@ class AutoEvaluationAppraisersSerializer(serializers.Serializer):
data['users'] = users
return data
class AutoEvaluationSerializer(serializers.Serializer):
brand = serializers.CharField()
brand_model = serializers.CharField()
@@ -368,3 +451,64 @@ class AutoEvaluationSerializer(serializers.Serializer):
condition = serializers.CharField()
fuel_type = serializers.CharField()
mileage = serializers.CharField()
class AutoEvaluationModelSerializer(serializers.ModelSerializer):
user = serializers.StringRelatedField(read_only=True)
appraisers = serializers.PrimaryKeyRelatedField(
many=True,
queryset=User.objects.all(),
required=False
)
class Meta:
model = AutoEvaluationModel
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",
"object_owner_residence",
"value_determined",
"rate_type",
"assessment_task_type",
"tex_passport_serie_num",
"tex_passport_gived_date",
"tex_passport_gived_location",
"car_wheel",
"car_brand",
"car_model",
"car_number",
"manufacture_year",
"car_dvigatel_number",
"car_color",
"distance_covered",
"car_position",
"body_type",
"fuel_type",
"state_car",
"rating_goal",
"status",
"is_archived",
"created_at",
"updated_at",
)
read_only_fields = (
"id",
"created_at",
"updated_at",
)

View File

@@ -0,0 +1,501 @@
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,
)
from core.apps.evaluation.serializers.reference import ListReferenceitemSerializer
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)
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)
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:
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",
"object_owner_residence",
"tex_passport_serie_num",
"tex_passport_files",
"rating_goal",
"registration_number",
"object_type",
"object_type_display",
"car_brand",
"car_model",
"car_number",
"manufacture_year",
"car_color",
"distance_covered",
"car_position",
"body_type",
"fuel_type",
"state_car",
"assessment_task_type",
"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_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_gived_date",
"tex_passport_gived_location",
"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,
)
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
fields = [
"user",
"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",
"object_owner_residence",
"value_determined",
"rate_type",
"assessment_task_type",
"tex_passport_files",
"tex_passport_serie_num",
"tex_passport_gived_date",
"tex_passport_gived_location",
"car_wheel",
"car_brand",
"car_model",
"car_number",
"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):
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 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(
queryset=ReferenceitemModel.objects.all(),
required=False,
allow_null=True,
)
rate_type = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
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,
allow_null=True,
)
user = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(),
required=True,
allow_null=False,
)
tex_passport_files = serializers.ListField(
child=serializers.FileField(),
required=False,
write_only=True,
)
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",
"object_owner_residence",
"value_determined",
"rate_type",
"assessment_task_type",
"tex_passport_serie_num",
"tex_passport_files",
"tex_passport_gived_date",
"tex_passport_gived_location",
"car_wheel",
"car_brand",
"car_model",
"car_number",
"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):
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):
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()
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):
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 = (
"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",
"object_owner_residence",
"value_determined",
"rate_type",
"assessment_task_type",
"tex_passport_serie_num",
"tex_passport_gived_date",
"tex_passport_gived_location",
"car_wheel",
"car_brand",
"car_model",
"car_number",
"manufacture_year",
"car_dvigatel_number",
"car_color",
"distance_covered",
"car_position",
"body_type",
"fuel_type",
"state_car",
"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 .MechanicAutoEvaluation import * # noqa

View File

@@ -0,0 +1,56 @@
from rest_framework import serializers
from core.apps.evaluation.models.bonus import BonusCategory, EmployeeBonus, BaseValueBonus
class BaseBonusSerializer(serializers.ModelSerializer):
class Meta:
model = BaseValueBonus
fields = ['id', 'base_price']
def create(self, validated_data):
if BaseValueBonus.objects.exists():
raise serializers.ValidationError("Base bonus already exists")
return super().create(validated_data)
class BonusCategorySerializer(serializers.ModelSerializer):
class Meta:
model = BonusCategory
fields = ['name', 'category', 'percentage']
class BonusCategoryListSerializer(serializers.ModelSerializer):
price = serializers.DecimalField(max_digits=12, decimal_places=2)
class Meta:
model = BonusCategory
fields = ['id', 'name', 'category', 'percentage' , 'price']
def get_price(self, obj):
base_obj = BaseValueBonus.objects.first()
if not base_obj:
return 0
return (base_obj.base_price * obj.percentage) / 100
class BonusEmployeeBonusSerializer(serializers.ModelSerializer):
class Meta:
model = EmployeeBonus
fields = ['user', 'bonus_type', 'percentage']
class EmployeeBonusListSerializer(serializers.ModelSerializer):
price = serializers.DecimalField(max_digits=12, decimal_places=2)
class Meta:
model = EmployeeBonus
fields = ['id', 'user', 'bonus_type', 'percentage' , 'price']
def get_price(self, obj):
base_obj = BaseValueBonus.objects.first()
if not base_obj:
return 0
return (base_obj.base_price * obj.percentage) / 100

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 .MechanicAutoEvaluationHistory import * # noqa
from .QuickEvaluationHistory import * # noqa

View File

@@ -128,3 +128,42 @@ class CreateQuickevaluationSerializer(serializers.ModelSerializer):
return super().create(validated_data)
class QuickEvaluationModelSerializer(serializers.ModelSerializer):
class Meta:
model = QuickEvaluationModel
fields = (
"id",
"created_by",
"brand",
"marka",
"car_position",
"body_type",
"color",
"fuel_type",
"state_car",
"tex_passport_serie_num",
"tech_passport_issued_date",
"tech_passport_issued_place",
"car_type",
"distance_covered",
"vin_number",
"car_number",
"car_manufactured_date",
"engine_number",
"estimated_price",
"status",
"is_archive",
"created_at",
"updated_at",
)
read_only_fields = (
"id",
"created_at",
"updated_at",
)

View File

@@ -4,8 +4,11 @@ from django.contrib.auth import get_user_model
from rest_framework import serializers
from core.apps.evaluation.models import EvaluationrequestModel, ReferenceitemModel
from core.apps.evaluation.models import EvaluationrequestModel, ReferenceitemModel, EvaluationRequestOwnerModel, EvaluationRequestCustomerModel
from core.apps.evaluation.serializers.reference import ListReferenceitemSerializer
from core.apps.evaluation.serializers.request.owner import EvaluationRequestOwnerSerializer
from core.apps.evaluation.serializers.request.req_customer import EvaluationRequestCustomerSerializer
User = get_user_model()
@@ -29,6 +32,8 @@ class BaseEvaluationrequestSerializer(serializers.ModelSerializer):
property_rights = ListReferenceitemSerializer(read_only=True)
form_ownership = ListReferenceitemSerializer(read_only=True)
user = serializers.SerializerMethodField(method_name="get_user")
customer = EvaluationRequestCustomerSerializer(read_only=True)
owner = EvaluationRequestOwnerSerializer(read_only=True)
class Meta:
model = EvaluationrequestModel
@@ -56,6 +61,8 @@ class BaseEvaluationrequestSerializer(serializers.ModelSerializer):
"created_at",
"updated_at",
"is_archive",
"customer",
"owner",
]
def get_location(self, obj):
@@ -113,6 +120,8 @@ class CreateEvaluationrequestSerializer(serializers.ModelSerializer):
rate_goal = serializers.PrimaryKeyRelatedField(required=False, queryset=ReferenceitemModel.objects.all())
property_rights = serializers.PrimaryKeyRelatedField(required=False, queryset=ReferenceitemModel.objects.all())
form_ownership = serializers.PrimaryKeyRelatedField(required=False, queryset=ReferenceitemModel.objects.all())
customer = EvaluationRequestCustomerSerializer()
owner = EvaluationRequestOwnerSerializer()
class Meta:
model = EvaluationrequestModel
@@ -131,6 +140,11 @@ class CreateEvaluationrequestSerializer(serializers.ModelSerializer):
"need_delivering",
"location",
"locationName",
"customer",
"owner",
"customer_and_owner_same",
"distance_covered",
"gov_number"
]
def validate_tex_passport(self, value):
@@ -179,7 +193,31 @@ class CreateEvaluationrequestSerializer(serializers.ModelSerializer):
if location_name:
validated_data["location_name"] = str(location_name)
validated_data["user"] = self.context["request"].user
return super().create(validated_data)
instance = super().create(validated_data)
customer = validated_data.pop("customer", None)
owner = validated_data.pop("owner", None)
EvaluationRequestCustomerModel.objects.create(
evaluation_request=instance,
type=customer.get("type"),
jshshir=customer.get("jshshir")
)
if not instance.customer_and_owner_same:
EvaluationRequestOwnerModel.objects.create(
evaluation_request=instance,
type=owner.get("type"),
jshshir=owner.get("jshshir")
)
else:
EvaluationRequestOwnerModel.objects.create(
evaluation_request=instance,
type=customer.get("type"),
jshshir=customer.get("jshshir")
)
return instance
class ArchiveEvaluationrequestSerializer(serializers.Serializer):
id = serializers.IntegerField(required=True)

View File

@@ -0,0 +1,9 @@
from rest_framework import serializers
from core.apps.evaluation.models.request import EvaluationRequestOwnerModel
class EvaluationRequestOwnerSerializer(serializers.ModelSerializer):
class Meta:
model = EvaluationRequestOwnerModel
fields = ["id", "evaluation_request", "type", "jshshir"]

View File

@@ -0,0 +1,9 @@
from rest_framework import serializers
from core.apps.evaluation.models.request import EvaluationRequestCustomerModel
class EvaluationRequestCustomerSerializer(serializers.ModelSerializer):
class Meta:
model = EvaluationRequestCustomerModel
fields = ["id", "evaluation_request", "type", "jshshir"]

View File

@@ -87,3 +87,29 @@ class CreateVehicleSerializer(serializers.ModelSerializer):
"condition",
"position",
]
class VehicleApplicationSerializer(serializers.Serializer):
person_name = serializers.CharField()
property_owner = serializers.CharField(max_length=100)
address = serializers.CharField(max_length=255)
marka = serializers.CharField(max_length=100)
model = serializers.CharField(max_length=100)
configuration = serializers.CharField(max_length=100)
auto_number = serializers.CharField(max_length=100)
date_created = serializers.DateTimeField()
mileage = serializers.IntegerField()
vehicle_identification = serializers.CharField(max_length=100)
engine_number = serializers.CharField(max_length=100)
colour = serializers.CharField(max_length=100)
registration_certificate_series = serializers.CharField(max_length=100)
tec_passport_number = serializers.CharField(max_length=100)
tec_passport_date = serializers.DateTimeField()
tec_passport_place = serializers.CharField(max_length=255)
body_type = serializers.CharField(max_length=100)
chassis = serializers.CharField(max_length=100)
plate = serializers.CharField(max_length=100)
value_type = serializers.CharField(max_length=100)
evaluation_purpose = serializers.CharField(max_length=100)
personal_id_number = serializers.IntegerField()
id_number = serializers.CharField(max_length=9)

View File

@@ -8,6 +8,7 @@ router = DefaultRouter()
router.register("document-category", views.DocumentCategoryView, basename="DocumentCategory")
router.register("document", views.DocumentView, basename="Document")
router.register("auto-evaluation-history", views.AutoEvaluationHistoryView, basename="auto-evaluation-history")
router.register("mechanic-auto-evaluation-history", views.MechanicAutoEvaluationHistoryView, basename="mechanic-auto-evaluation-history")
router.register("quick-evaluation-history", views.QuickEvaluationHistoryView, basename="quick-evaluation-history")
router.register("determined-value", views.DeterminedValueView, basename="determined-value")
router.register("evaluation-purpose", views.EvaluationPurposeView, basename="evaluation-purpose")
@@ -22,11 +23,15 @@ router.register("quick-evaluation", views.QuickEvaluationView, basename="quick-e
router.register("movable-property-evaluation", views.MovablePropertyEvaluationView, basename="movable-property-evaluation")
router.register("real-estate-evaluation", views.RealEstateEvaluationView, basename="real-estate-evaluation")
router.register("auto-evaluation", views.AutoEvaluationView, basename="auto-evaluation")
router.register("mechanic-auto-evaluation", views.MechanicAutoEvaluationView, basename="mechanic-auto-evaluation")
router.register("vehicle", views.VehicleView, basename="vehicle")
router.register("valuation", views.ValuationView, basename="valuation")
router.register("property-owner", views.PropertyOwnerView, basename="property-owner")
router.register("customer", views.CustomerView, basename="customer")
router.register("certificate", views.CertificateView, basename="certificate")
router.register("bonus-type", views.BonusTypeView, basename="bonus-type")
router.register("bonus-employee", views.BonusEmployeeViewSet, basename="bonus-employee")
router.register("bonus-base", views.BaseBonusViewSet, basename="bonus-base")
urlpatterns = [
path("", include(router.urls)),
@@ -37,6 +42,7 @@ urlpatterns = [
# Quick Evaluation
path('quick-evaluation/', include(
[
path("admin/", views.AdminQuickEvalAPIView.as_view(), name="quick-evaluation"),
path(
'archive/', include(
[
@@ -51,6 +57,7 @@ urlpatterns = [
# Auto Evaluation
path("auto-evaluation/", include(
[
path("admin/", views.AdminEvaluationsAPIView.as_view(), name="admin-evaluations"),
path('archive/', include(
[
path('<int:pk>/', views.AutoEvaluationArchiveAPIView.as_view()),
@@ -67,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
path("evaluation-request/", include(
[
@@ -83,4 +110,5 @@ urlpatterns = [
)),
path("calculate_avg_cost/", views.AvgCostAPIView.as_view()),
path("vehicle_document/", views.GeneratePDFView.as_view()),
]

View File

@@ -1,4 +1,5 @@
from .auto import * # noqa
from .mechanic_auto import * # noqa
from .customer import * # noqa
from .document import * # noqa
from .documentcategory import * # noqa
@@ -15,3 +16,4 @@ from .didox import * # noqa
from .tech_passport import * # noqa
from .certificate import * # noqa
from .avg_cost import *
from .bonus import *

View File

@@ -1,10 +1,9 @@
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
@@ -12,10 +11,12 @@ 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.auto import AutoevaluationFilter
from core.apps.evaluation.models import AutoEvaluationModel
from core.apps.evaluation.serializers import auto as serializers
from core.apps.evaluation.serializers import auto as serializers, AutoEvaluationModelSerializer
@extend_schema(tags=["AutoEvaluation"])
class AutoEvaluationView(BaseViewSetMixin, ModelViewSet):
@@ -62,8 +63,6 @@ class AutoEvaluationView(BaseViewSetMixin, ModelViewSet):
"created_at",
"value_determined",
"rate_type",
"property_rights",
"form_ownership",
]
ordering = ["-created_at"]
@@ -177,3 +176,17 @@ class AutoEvaluationArchiveAPIView(APIView):
},
status=200
)
@extend_schema(tags=["AutoEvaluation"])
class AdminEvaluationsAPIView(generics.GenericAPIView):
permission_classes = [IsAuthenticated, IsAdminRole]
queryset = AutoEvaluationModel.objects.all()
serializer_class = AutoEvaluationModel
def get(self, request):
auto_eval = AutoEvaluationModel.objects.filter(
created_by=self.request.user
).select_related('appraisers').distinct()
serializer = AutoEvaluationModelSerializer(auto_eval, many=True)
return Response(serializer.data)

View File

@@ -0,0 +1,60 @@
from django_core.mixins import BaseViewSetMixin
from drf_spectacular.utils import extend_schema
from rest_framework import viewsets
from rest_framework.permissions import IsAdminUser
from rest_framework.viewsets import ModelViewSet
# core
from core.apps.evaluation.models.bonus import BonusCategory, EmployeeBonus, BaseValueBonus
from core.apps.evaluation.serializers.bonus.Bonus import BonusCategorySerializer, \
BonusCategoryListSerializer, EmployeeBonusListSerializer, BonusEmployeeBonusSerializer, BaseBonusSerializer
@extend_schema(tags=["BaseBonus"])
class BaseBonusViewSet(BaseViewSetMixin, viewsets.ModelViewSet):
queryset = BaseValueBonus.objects.all()
serializer_class = BaseBonusSerializer
@extend_schema(tags=["Bonus-Category"])
class BonusTypeView(BaseViewSetMixin, ModelViewSet):
queryset = BonusCategory.objects.all()
serializer_class = BonusCategorySerializer
action_serializer_class = {
'create': BonusCategorySerializer,
'update': BonusCategorySerializer,
'partial_update': BonusCategorySerializer,
'list': BonusCategoryListSerializer,
'retrieve': BonusCategoryListSerializer,
}
action_permission_classes = {
'create': [IsAdminUser],
'update': [IsAdminUser],
'partial_update': [IsAdminUser],
'destroy': [IsAdminUser],
'list': [IsAdminUser],
}
class BonusEmployeeViewSet(BaseViewSetMixin, ModelViewSet):
queryset = EmployeeBonus.objects.all()
serializer_class = BonusEmployeeBonusSerializer
action_serializer_class = {
'create': BonusEmployeeBonusSerializer,
'update': BonusEmployeeBonusSerializer,
'partial_update': BonusEmployeeBonusSerializer,
'list': EmployeeBonusListSerializer,
'retrieve': EmployeeBonusListSerializer,
}
action_permission_classes = {
'create': [IsAdminUser],
'update': [IsAdminUser],
'partial_update': [IsAdminUser],
'destroy': [IsAdminUser],
'list': [IsAdminUser],
}

View File

@@ -16,9 +16,14 @@ from rest_framework.viewsets import ReadOnlyModelViewSet
# core apps
from core.apps.evaluation.filters.history import (
AutoevaluationhistoryFilter,
MechanicAutoevaluationhistoryFilter,
QuickevaluationhistoryFilter,
)
from core.apps.evaluation.models import AutoevaluationhistoryModel, QuickevaluationhistoryModel
from core.apps.evaluation.models import (
AutoevaluationhistoryModel,
MechanicAutoevaluationhistoryModel,
QuickevaluationhistoryModel,
)
from core.apps.evaluation.serializers import history as serializers
@@ -72,6 +77,56 @@ class AutoEvaluationHistoryView(BaseViewSetMixin, ReadOnlyModelViewSet):
})
@extend_schema(
tags=["MechanicAutoEvaluationHistory"],
parameters=[
OpenApiParameter("mechanic_auto_evaluation", int, description="MechanicAutoEvaluation ID bo'yicha filter"),
OpenApiParameter("event_type", str, description="Event turi bo'yicha filter"),
OpenApiParameter("created_from", str, description="Boshlanish sanasi (ISO 8601)"),
OpenApiParameter("created_to", str, description="Tugash sanasi (ISO 8601)"),
],
)
class MechanicAutoEvaluationHistoryView(BaseViewSetMixin, ReadOnlyModelViewSet):
queryset = MechanicAutoevaluationhistoryModel.objects.only(
"id", "mechanic_auto_evaluation_id", "event_type",
"actor_id", "actor_full_name", "actor_role",
"meta", "created_at",
)
serializer_class = serializers.ListMechanicAutoevaluationhistorySerializer
permission_classes = [AllowAny]
pagination_class = None
filter_backends = [DjangoFilterBackend, OrderingFilter]
filterset_class = MechanicAutoevaluationhistoryFilter
ordering_fields = [
"id",
"event_type",
"event_type_display",
"actor",
"meta",
"created_at",
]
ordering = ["created_at"]
action_permission_classes = {}
action_serializer_class = {
"list": serializers.ListMechanicAutoevaluationhistorySerializer,
"retrieve": serializers.RetrieveMechanicAutoevaluationhistorySerializer,
"create": serializers.CreateMechanicAutoevaluationhistorySerializer,
}
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
results = list(queryset)
serializer = self.get_serializer(results, many=True)
return Response({
"count": len(results),
"next": None,
"previous": None,
"results": serializer.data,
})
@extend_schema(
tags=["QuickEvaluationHistory"],
parameters=[

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)

View File

@@ -1,17 +1,13 @@
# django
from django.shortcuts import get_object_or_404
# django core
from django_core.mixins import BaseViewSetMixin
# django filters
from django_filters.rest_framework import DjangoFilterBackend
# swagger
from drf_spectacular.utils import extend_schema
# rest framework
from rest_framework import status
from rest_framework import status, generics
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.generics import ListAPIView
from rest_framework.parsers import FormParser, MultiPartParser
@@ -20,10 +16,11 @@ 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
# core apps
from core.apps.evaluation.filters.quick import QuickevaluationFilter
from core.apps.evaluation.models import QuickEvaluationModel
from core.apps.evaluation.serializers import quick as serializers
from core.apps.evaluation.serializers import quick as serializers, QuickEvaluationModelSerializer
@extend_schema(tags=["QuickEvaluation"])
@@ -87,3 +84,18 @@ class QuickEvaluationArchivedListAPIView(ListAPIView):
def get_queryset(self):
return QuickEvaluationModel.objects.filter(is_archive=True)
@extend_schema(tags=["QuickEvaluation"])
class AdminQuickEvalAPIView(generics.GenericAPIView):
permission_classes = [IsAuthenticated, IsAdminRole]
queryset = QuickEvaluationModel.objects.all()
serializer_class = QuickEvaluationModelSerializer
def get(self, request):
quick_eval = QuickEvaluationModel.objects.filter(
created_by=self.request.user
).select_related('created_by').distinct()
serializer = QuickEvaluationModelSerializer(quick_eval, many=True)
return Response(serializer.data)

View File

@@ -1,7 +1,7 @@
# rest framework
from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from rest_framework.permissions import BasePermission
from rest_framework.generics import GenericAPIView
# swagger
@@ -10,11 +10,18 @@ from drf_spectacular.utils import extend_schema
# core apps
from core.services.tech_passport import TechPassportService
from core.apps.evaluation.serializers import TechPassportSerializer
from core.apps.accounts.choices import RoleChoice
class IsNotUserRole(BasePermission):
def has_permission(self, request, view):
if not request.user or not request.user.is_authenticated:
return False
return request.user.role != RoleChoice.USER
class TechPassportAPIView(GenericAPIView):
authentication_classes = []
permission_classes = [IsAuthenticated]
permission_classes = [IsNotUserRole]
@extend_schema(
tags=["Tech Passport"],

View File

@@ -1,16 +1,19 @@
# django core
from django.http import HttpResponse
from django_core.mixins import BaseViewSetMixin
# swagger
from drf_spectacular.utils import extend_schema
# rest framework
from rest_framework.permissions import AllowAny
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.viewsets import ReadOnlyModelViewSet
# core apps
from core.apps.evaluation.models import VehicleModel
from core.apps.evaluation.serializers import vehicle as serialziers
from core.apps.evaluation.serializers import vehicle as serialziers, VehicleApplicationSerializer
from core.utils.generation_pdf import PDFService
@extend_schema(tags=["Vehicle"])
@@ -27,3 +30,19 @@ class VehicleView(BaseViewSetMixin, ReadOnlyModelViewSet):
"retrieve": serialziers.RetrieveVehicleSerializer,
"create": serialziers.CreateVehicleSerializer,
}
@extend_schema(tags=['GenerationDocument'], request=VehicleApplicationSerializer)
class GeneratePDFView(APIView):
permission_classes = [IsAuthenticated]
def post(self, request):
serializer = VehicleApplicationSerializer(data=request.data)
if not serializer.is_valid():
return Response(serializer.errors, status=400)
pdf_buffer = PDFService.generate_vehicle_pdf(serializer.validated_data)
response = HttpResponse(pdf_buffer, content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename="ariza.pdf"'
return response

View File

View File

@@ -0,0 +1,4 @@
from .column import *
from .comment import *
from .task import *
from .label import *

View File

@@ -0,0 +1,7 @@
from django.contrib import admin
from core.apps.tasks.models import Column
@admin.register(Column)
class ColumnAdmin(admin.ModelAdmin):
list_display = ('name',)

View File

@@ -0,0 +1,7 @@
from django.contrib import admin
from core.apps.tasks.models import Comment
@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
list_display = ('created_by', 'type')

View File

@@ -0,0 +1,7 @@
from django.contrib import admin
from core.apps.tasks.models import Label
@admin.register(Label)
class LabelAdmin(admin.ModelAdmin):
list_display = ('name',)

View File

@@ -0,0 +1,7 @@
from django.contrib import admin
from core.apps.tasks.models import Task
@admin.register(Task)
class TaskAdmin(admin.ModelAdmin):
list_display = ('name', 'created_by', 'priority')

8
core/apps/tasks/apps.py Normal file
View File

@@ -0,0 +1,8 @@
from django.apps import AppConfig
class TasksConfig(AppConfig):
name = "core.apps.tasks"
def ready(self):
from core.apps.tasks import admin

View File

@@ -0,0 +1,6 @@
from django.db import models
class MessageChoice(models.TextChoices):
FILE = "file", "File"
TEXT = "text", "Text"

View File

@@ -0,0 +1,7 @@
from django.db import models
class PriorityChoice(models.TextChoices):
LOW = "low", "Low"
MEDIUM = "medium", "Medium"
HIGH = "high", "High"

View File

@@ -0,0 +1,77 @@
# Generated by Django 5.2.7 on 2026-04-29 13:18
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Column',
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)),
('name', models.CharField(max_length=255)),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Label',
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)),
('name', models.CharField(max_length=255)),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Task',
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)),
('name', models.CharField(max_length=255)),
('description', models.TextField(blank=True)),
('priority', models.CharField(choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High')], max_length=255)),
('from_date', models.DateField(blank=True, null=True)),
('to_date', models.DateField(blank=True, null=True)),
('assignees', models.ManyToManyField(related_name='assigned_tasks', to=settings.AUTH_USER_MODEL)),
('column', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tasks', to='tasks.column')),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='created_tasks', to=settings.AUTH_USER_MODEL)),
('labels', models.ManyToManyField(related_name='tasks', to='tasks.label')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Comment',
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)),
('message', models.TextField()),
('file', models.FileField(blank=True, null=True, upload_to='comments/')),
('type', models.CharField(choices=[('file', 'File'), ('text', 'Text')], max_length=255)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='created_comments', to=settings.AUTH_USER_MODEL)),
('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='tasks.task')),
],
options={
'abstract': False,
},
),
]

View File

@@ -0,0 +1,21 @@
# Generated by Django 5.2.7 on 2026-04-29 13:20
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tasks', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterField(
model_name='comment',
name='created_by',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to=settings.AUTH_USER_MODEL),
),
]

View File

@@ -0,0 +1,29 @@
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tasks', '0002_alter_comment_created_by'),
]
operations = [
migrations.RemoveField(
model_name='comment',
name='file',
),
migrations.CreateModel(
name='CommentFile',
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='comments/')),
('comment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='files', to='tasks.comment')),
],
options={
'abstract': False,
},
),
]

View File

View File

@@ -0,0 +1,4 @@
from .column import *
from .comment import *
from .task import *
from .label import *

View File

@@ -0,0 +1,10 @@
from django.db import models
from django_core.models import AbstractBaseModel
class Column(AbstractBaseModel):
name = models.CharField(max_length=255)
def __str__(self):
return self.name

View File

@@ -0,0 +1,25 @@
from django.db import models
from django_core.models import AbstractBaseModel
from core.apps.tasks.choices.comment import MessageChoice
class Comment(AbstractBaseModel):
task = models.ForeignKey('tasks.Task', on_delete=models.CASCADE, related_name='comments')
message = models.TextField()
type = models.CharField(max_length=255, choices=MessageChoice.choices)
created_by = models.ForeignKey('accounts.User', on_delete=models.CASCADE, related_name='comments')
def __str__(self):
return f"{self.message} created by {self.created_by}"
class CommentFile(AbstractBaseModel):
comment = models.ForeignKey(
'tasks.Comment', on_delete=models.CASCADE, related_name='files'
)
file = models.FileField(upload_to='comments/')
def __str__(self):
return f"File for comment {self.comment_id}"

View File

@@ -0,0 +1,10 @@
from django.db import models
from django_core.models import AbstractBaseModel
class Label(AbstractBaseModel):
name = models.CharField(max_length=255)
def __str__(self):
return self.name

View File

@@ -0,0 +1,20 @@
from django.db import models
from django_core.models import AbstractBaseModel
from core.apps.tasks.choices.task import PriorityChoice
class Task(AbstractBaseModel):
column = models.ForeignKey('tasks.Column', on_delete=models.CASCADE, related_name='tasks')
name = models.CharField(max_length=255)
description = models.TextField(blank=True)
priority = models.CharField(max_length=255, choices=PriorityChoice.choices)
from_date = models.DateField(null=True, blank=True)
to_date = models.DateField(null=True, blank=True)
labels = models.ManyToManyField('tasks.Label', related_name='tasks')
assignees = models.ManyToManyField('accounts.User', related_name='assigned_tasks')
created_by = models.ForeignKey('accounts.User', on_delete=models.CASCADE, related_name='created_tasks')
def __str__(self):
return f"{self.name} created by {self.created_by}"

View File

@@ -0,0 +1,22 @@
from rest_framework import serializers
from core.apps.tasks.serializers.comment import CommentSerializer
from core.apps.tasks.serializers.task import TaskSerializer
from core.apps.tasks.models import Column
class BoardTaskSerializer(TaskSerializer):
comments = CommentSerializer(many=True, read_only=True)
class Meta(TaskSerializer.Meta):
TaskSerializer.Meta.fields += ['comments']
class BoardSerializer(serializers.ModelSerializer):
tasks = BoardTaskSerializer(many=True, read_only=True)
class Meta:
model = Column
fields = [
'id', 'name', 'tasks',
]

View File

@@ -0,0 +1,11 @@
from rest_framework import serializers
from core.apps.tasks.models.column import Column
class ColumnSerializer(serializers.ModelSerializer):
class Meta:
model = Column
fields = [
'id', 'name'
]

View File

@@ -0,0 +1,89 @@
from django.db import transaction
from rest_framework import serializers
from core.apps.tasks.models.comment import Comment, CommentFile
class CommentCreatedBySerializer(serializers.Serializer):
id = serializers.IntegerField()
first_name = serializers.CharField()
last_name = serializers.CharField()
avatar = serializers.SerializerMethodField()
def get_avatar(self, obj):
request = self.context.get('request')
if obj.avatar and request:
return request.build_absolute_uri(obj.avatar.url)
if obj.avatar:
return obj.avatar.url
return None
class CommentFileSerializer(serializers.ModelSerializer):
file = serializers.SerializerMethodField()
class Meta:
model = CommentFile
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 CommentSerializer(serializers.ModelSerializer):
created_by = CommentCreatedBySerializer(read_only=True)
files = CommentFileSerializer(many=True, read_only=True)
class Meta:
model = Comment
fields = [
'id', 'message', 'type', 'created_by', 'created_at', 'files'
]
class CommentCreateSerializer(serializers.ModelSerializer):
files = serializers.ListField(
child=serializers.FileField(),
required=False,
write_only=True,
)
class Meta:
model = Comment
fields = [
'id', 'message', 'type', 'task', 'files', 'created_at'
]
read_only_fields = ['id', 'created_at']
def create(self, validated_data):
files = validated_data.pop('files', [])
with transaction.atomic():
comment = Comment.objects.create(
created_by=self.context['request'].user,
**validated_data
)
CommentFile.objects.bulk_create([
CommentFile(comment=comment, file=f) for f in files
])
return comment
def update(self, instance, validated_data):
files = validated_data.pop('files', None)
with transaction.atomic():
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
if files is not None:
CommentFile.objects.bulk_create([
CommentFile(comment=instance, file=f) for f in files
])
return instance
def to_representation(self, instance):
return CommentSerializer(instance, context=self.context).data

View File

@@ -0,0 +1,11 @@
from rest_framework import serializers
from core.apps.tasks.models.label import Label
class LabelSerializer(serializers.ModelSerializer):
class Meta:
model = Label
fields = [
'id', 'name'
]

View File

@@ -0,0 +1,52 @@
from rest_framework import serializers
from core.apps.tasks.models.task import Task
from core.apps.accounts.serializers.user import ShortUserSerializer
from core.apps.tasks.serializers.label import LabelSerializer
class TaskSerializer(serializers.ModelSerializer):
labels = LabelSerializer(many=True)
assignees = serializers.SerializerMethodField(method_name='get_assignees')
created_by = serializers.SerializerMethodField(method_name='get_created_by')
class Meta:
model = Task
fields = [
'id',
'column',
'name',
'description',
'priority',
'from_date',
'to_date',
'labels',
'assignees',
'created_by'
]
def get_assignees(self, obj):
return ShortUserSerializer(obj.assignees.all(), many=True, context={"request": self.context['request']}).data
def get_created_by(self, obj):
return ShortUserSerializer(obj.created_by, context={"request": self.context['request']}).data
class TaskCreateSerializer(serializers.ModelSerializer):
class Meta:
model = Task
fields = [
'id',
'column',
'name',
'description',
'priority',
'from_date',
'to_date',
'labels',
'assignees',
]
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
return super().create(validated_data)

34
core/apps/tasks/urls.py Normal file
View File

@@ -0,0 +1,34 @@
from django.urls import path, include
from core.apps.tasks.views import task, column, comment, label, board
urlpatterns = [
path('column/', include(
[
path('list/', column.ColumnListApiView.as_view()),
path('create/', column.ColumnCreateApiView.as_view()),
path('<int:id>/update/', column.ColumnUpdateApiView.as_view()),
path('<int:id>/delete/', column.ColumnDeleteApiView.as_view())
]
)),
path('label/', include(
[
path('', label.LabelListCreateApiView.as_view()),
path('<int:id>/', label.LabelRetrieveUpdateDestroyApiView.as_view()),
]
)),
path('task/', include(
[
path('list/', task.TaskListView.as_view()),
path('<int:pk>/', task.TaskDetailView.as_view()),
path('create/', task.TaskCreateView.as_view()),
]
)),
path('comment/', include(
[
path('', comment.CommentListCreateAPIView.as_view()),
path('<int:pk>/', comment.CommentDetailAPIView.as_view()),
]
)),
path('board/', board.BoardListView.as_view()),
]

View File

@@ -0,0 +1,15 @@
from rest_framework import generics, permissions
from rest_framework.response import Response
from core.apps.tasks.serializers.board import BoardSerializer
from core.apps.tasks.models import Column
#test commit
class BoardListView(generics.ListAPIView):
queryset = Column.objects.order_by('id')
serializer_class = BoardSerializer
permission_classes = [permissions.IsAuthenticated]

View File

@@ -0,0 +1,47 @@
from django.db import transaction
from rest_framework import generics, permissions
from rest_framework.response import Response
from drf_spectacular.utils import extend_schema
from core.apps.tasks.serializers.column import ColumnSerializer
from core.apps.tasks.models.column import Column
@extend_schema(tags=['Tasks'])
class ColumnCreateApiView(generics.GenericAPIView):
permission_classes = [permissions.IsAuthenticated]
serializer_class = ColumnSerializer
@transaction.atomic
def post(self, request):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
column = serializer.save()
return Response(serializer.data)
@extend_schema(tags=['Tasks'])
class ColumnListApiView(generics.ListAPIView):
permission_classes = [permissions.IsAuthenticated]
serializer_class = ColumnSerializer
def get_queryset(self):
return Column.objects.order_by('id')
@extend_schema(tags=['Tasks'])
class ColumnUpdateApiView(generics.UpdateAPIView):
permission_classes = [permissions.IsAuthenticated]
serializer_class = ColumnSerializer
lookup_field = 'id'
queryset = Column.objects.all()
@extend_schema(tags=['Tasks'])
class ColumnDeleteApiView(generics.DestroyAPIView):
permission_classes = [permissions.IsAuthenticated]
serializer_class = ColumnSerializer
lookup_field = 'id'
queryset = Column.objects.all()

View File

@@ -0,0 +1,51 @@
from rest_framework import generics, permissions
from rest_framework.exceptions import PermissionDenied
from core.apps.tasks.models.comment import Comment
from core.apps.tasks.serializers.comment import CommentSerializer, CommentCreateSerializer
class CommentListCreateAPIView(generics.ListCreateAPIView):
queryset = Comment.objects.all()
permission_classes = [permissions.IsAuthenticated]
def get_serializer_class(self):
if self.request.method == 'POST':
return CommentCreateSerializer
return CommentSerializer
def get_queryset(self):
task_id = self.request.query_params.get('task_id')
queryset = self.queryset
if task_id:
queryset = queryset.filter(task_id=task_id)
return queryset.order_by('-id')
def get_serializer_context(self):
return {"request": self.request}
class CommentDetailAPIView(generics.RetrieveUpdateDestroyAPIView):
queryset = Comment.objects.all()
permission_classes = [permissions.IsAuthenticated]
def get_serializer_class(self):
if self.request.method in ['PUT', 'PATCH']:
return CommentCreateSerializer
return CommentSerializer
def get_serializer_context(self):
return {"request": self.request}
def perform_update(self, serializer):
comment = self.get_object()
if comment.created_by != self.request.user:
raise PermissionDenied("You cannot edit this comment")
serializer.save()
def perform_destroy(self, instance):
if instance.created_by != self.request.user:
raise PermissionDenied("You cannot delete this comment")
instance.delete()

View File

@@ -0,0 +1,22 @@
from rest_framework import generics, permissions
from rest_framework.response import Response
from drf_spectacular.utils import extend_schema
from core.apps.tasks.serializers.label import LabelSerializer
from core.apps.tasks.models.label import Label
@extend_schema(tags=['Tasks'])
class LabelListCreateApiView(generics.ListCreateAPIView):
queryset = Label.objects.order_by('id')
serializer_class = LabelSerializer
permission_classes = [permissions.IsAuthenticated]
@extend_schema(tags=['Tasks'])
class LabelRetrieveUpdateDestroyApiView(generics.RetrieveUpdateDestroyAPIView):
queryset = Label.objects.order_by('id')
serializer_class = LabelSerializer
permission_classes = [permissions.IsAuthenticated]
lookup_field = 'id'

View File

@@ -0,0 +1,39 @@
from django.db import transaction
from drf_spectacular.utils import extend_schema
from rest_framework import permissions, generics, status
from rest_framework.response import Response
from core.apps.tasks.models.task import Task
from core.apps.tasks.serializers.task import TaskSerializer, TaskCreateSerializer
@extend_schema(tags=['Tasks'])
class TaskCreateView(generics.GenericAPIView):
permission_classes = [permissions.IsAuthenticated]
serializer_class = TaskCreateSerializer
queryset = Task.objects.order_by('id')
@transaction.atomic
def post(self, request):
serializer = self.get_serializer(data=request.data)
if not serializer.is_valid(raise_exception=True):
return Response(serializer.validated_data, status=status.HTTP_400_BAD_REQUEST)
serializer.save()
return Response(serializer.data)
@extend_schema(tags=['Tasks'])
class TaskListView(generics.ListAPIView):
permission_classes = [permissions.IsAuthenticated]
serializer_class = TaskSerializer
queryset = Task.objects.order_by('id')
def serializer_context(self):
return self.serializer_class(context={"request": self.request})
@extend_schema(tags=['Tasks'])
class TaskDetailView(generics.RetrieveUpdateDestroyAPIView):
permission_classes = [permissions.IsAuthenticated]
serializer_class = TaskSerializer
queryset = Task.objects.order_by('id')

View File

@@ -0,0 +1,138 @@
# services.py
from io import BytesIO
from reportlab.lib import colors
from reportlab.lib.enums import TA_RIGHT, TA_CENTER
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer
class PDFService:
@staticmethod
def generate_vehicle_pdf(data):
buffer = BytesIO()
doc = SimpleDocTemplate(
buffer, pagesize=A4,
rightMargin=40, leftMargin=40,
topMargin=40, bottomMargin=40
)
styles = getSampleStyleSheet()
cell_style = ParagraphStyle(
'CellStyle', parent=styles['Normal'],
fontSize=9, leading=11
)
cell_style_bold = ParagraphStyle(
'CellStyleBold', parent=cell_style,
fontName='Helvetica-Bold',
textColor=colors.red
)
header_style = ParagraphStyle(
'HeaderStyle', parent=styles['Normal'],
alignment=TA_RIGHT, fontSize=10, leading=12
)
title_style = ParagraphStyle(
'TitleStyle', parent=styles['Normal'],
alignment=TA_CENTER, fontSize=14, leading=16
)
elements = []
header_text = (
f'<b>"Sifat baholash" MChJ direktori<br/>'
f"T.R.To'rayevga</b><br/><br/>"
f"{data.get('address', '')}<br/>"
f"ro'yxatda turuvchi fuqaro<br/>"
f"<u>{data.get('person_name', '')}</u> tomonidan<br/>"
f"<u>Avtotransport vositasini baholash uchun</u>"
)
elements.append(Paragraph(header_text, header_style))
elements.append(Spacer(1, 25))
elements.append(Paragraph("<b>A R I Z A</b>", title_style))
elements.append(Spacer(1, 10))
elements.append(Paragraph(
"Ushbu orqali quyidagi avtotransport vositasini baholab berishingizni so'rayman:",
cell_style
))
elements.append(Spacer(1, 10))
date_created = data.get('date_created')
year_str = str(date_created.year) + " yil" if date_created else ""
tec_date = data.get('tec_passport_date')
tec_date_str = tec_date.strftime('%d.%m.%Y yil') if tec_date else ""
raw_data = [
["Mulk egasi", data.get('property_owner', '')],
["Manzil", data.get('address', '')],
["Marka", data.get('marka', '')],
["Model", data.get('model', '')],
["Komplektatsiya", data.get('configuration', '')],
["Davlat raqami", data.get('auto_number', '')],
["Ishlab chiqarilgan yili", year_str],
["Bosib o'tgan masofasi", f"{data.get('mileage', 0):,}".replace(',', ' ')],
["№ kuzov (VIN)", data.get('vehicle_identification', '')],
["№ dvigatel", data.get('engine_number', '')],
["Rang", data.get('colour', '')],
["Texnik passport seriyasi", data.get('registration_certificate_series', '')],
["Texnik passport raqami", data.get('tec_passport_number', '')],
["Texnik passport berilgan sanasi", tec_date_str],
["Texnik passport berilgan joyi", data.get('tec_passport_place', '')],
["Kuzov turi", data.get('body_type', '')],
["Shassi", data.get('chassis', '')],
["Davlat belgisi (plate)", data.get('plate', '')], # ← was missing
]
table_data = [
[Paragraph(row[0], cell_style), Paragraph(str(row[1]), cell_style_bold)]
for row in raw_data
]
table = Table(table_data, colWidths=[170, 345])
table.setStyle(TableStyle([
('GRID', (0, 0), (-1, -1), 0.5, colors.black),
('VALIGN', (0, 0), (-1, -1), 'TOP'),
('LEFTPADDING', (0, 0), (-1, -1), 6),
('TOPPADDING', (0, 0), (-1, -1), 4),
('BOTTOMPADDING', (0, 0), (-1, -1), 4),
]))
elements.append(table)
elements.append(Spacer(1, 15))
elements.append(Paragraph("<b>Baholash maqsadi:</b>", cell_style))
purpose_raw = [
["Aniqlanayotgan qiymat turi", data.get('value_type', '')],
["Baholash maqsadi", data.get('evaluation_purpose', '')],
]
purpose_data = [
[Paragraph(r[0], cell_style), Paragraph(str(r[1]), cell_style_bold)]
for r in purpose_raw
]
pt = Table(purpose_data, colWidths=[170, 345])
pt.setStyle(TableStyle([
('GRID', (0, 0), (-1, -1), 0.5, colors.black),
('VALIGN', (0, 0), (-1, -1), 'TOP'),
]))
elements.append(pt)
elements.append(Spacer(1, 15))
elements.append(Paragraph("<b>Buyurtmachi rekvizitlari:</b>", cell_style))
footer_raw = [
["Shaxsiy raqam (PINFL)", str(data.get('personal_id_number', ''))],
["ID karta raqami", data.get('id_number', '')],
]
footer_data = [
[Paragraph(f[0], cell_style), Paragraph(str(f[1]), cell_style_bold)]
for f in footer_raw
]
ft = Table(footer_data, colWidths=[170, 345])
ft.setStyle(TableStyle([
('GRID', (0, 0), (-1, -1), 0.5, colors.black),
('VALIGN', (0, 0), (-1, -1), 'TOP'),
]))
elements.append(ft)
doc.build(elements)
buffer.seek(0)
return buffer

View File

@@ -5,6 +5,17 @@ ENV SCRIPT=$SCRIPT
WORKDIR /code
RUN apk add --no-cache \
glib \
gdk-pixbuf \
pango \
cairo \
libffi \
shared-mime-info \
fontconfig \
ttf-dejavu \
ttf-liberation
COPY requirements.txt /code/requirements.txt
RUN uv pip install -r requirements.txt
@@ -16,5 +27,3 @@ COPY ./resources/scripts/$SCRIPT /code/$SCRIPT
RUN chmod +x /code/resources/scripts/$SCRIPT
CMD sh /code/resources/scripts/$SCRIPT

View File

@@ -1,46 +1,146 @@
backports.tarfile==1.2.0
celery==5.4.0
django-cors-headers==4.6.0
django-environ==0.11.2
django-extensions==3.2.3
django-filter==24.3
django-redis==5.4.0
django-unfold==0.65.0
djangorestframework-simplejwt==5.3.1
drf-spectacular==0.28.0
importlib-metadata==8.5.0
importlib-resources==6.4.5
inflect==7.3.1
jaraco.collections==5.1.0
packaging==24.2
pip-chill==1.0.3
platformdirs==4.3.6
psycopg2-binary==2.9.10
tomli==2.2.1
uvicorn==0.32.1
jst-django-core~=1.2.2
rich
pydantic
aiohappyeyeballs
aiohttp
aiosignal
amqp
annotated-doc
annotated-types
arrow
asgiref
astor
attrs
backports.tarfile
bcrypt
pytest-django
requests
model_bakery
django-modeltranslation~=0.19.11
django-ckeditor-5==0.2.15
django-rosetta==0.10.1
django-cacheops~=7.1
# !NOTE: on-server
# gunicorn
django-storages
billiard
binaryornot
black
boto3
botocore
brotli
celery
certifi
cffi
chardet
charset-normalizer
click
click-didyoumean
click-plugins
click-repl
colorlog
cookiecutter
cssselect2
Django
django-cacheops
django-ckeditor-5
django-cors-headers
django-environ
django-extensions
django-filter
django-modeltranslation
django-redis
django-rosetta
django-storages
django-unfold
djangorestframework
djangorestframework-simplejwt
drf-spectacular
flake8
fonttools
frozenlist
funcy
g4f
grpcio
grpcio-tools
h11
idna
importlib_metadata
importlib_resources
inflect
inflection
iniconfig
isort
jaraco.collections
jaraco.context
jaraco.functools
jaraco.text
Jinja2
jmespath
jsonschema
jsonschema-specifications
jst-aicommit
jst-django
jst-django-core
kombu
markdown-it-py
MarkupSafe
mccabe
mdurl
model-bakery
more-itertools
multidict
mypy-extensions
nest-asyncio
packaging
pathspec
pillow
pip-chill
platformdirs
pluggy
polib
prompt-toolkit
propcache
protobuf
psycopg2-binary
pycodestyle
pycparser
pycryptodome
pydantic
pydantic_core
pydyf
pyflakes
Pygments
PyJWT
pyphen
pytest
pytest-django
python-dateutil
python-slugify
PyYAML
questionary
redis
referencing
reportlab
requests
rich
rpds-py
s3transfer
setuptools
shellingham
six
sqlparse
tenacity
text-unidecode
tinycss2
tinyhtml5
tomli
tqdm
typeguard
typer
typer-slim
types-python-dateutil
typing-inspection
typing_extensions
tzdata
uritemplate
urllib3
uvicorn
vine
wcwidth
weasyprint
webencodings
yarl
zipfile36
zipp
zopfli
# !NOTE: on-websocket
@@ -50,3 +150,5 @@ boto3
grpcio>=1.62.0
grpcio-tools>=1.62.0
protobuf>=4.25.0
reportlab

Some files were not shown because too many files have changed in this diff Show More