diff --git a/config/urls.py b/config/urls.py index 33c17ba..8e2f7a3 100644 --- a/config/urls.py +++ b/config/urls.py @@ -13,7 +13,7 @@ from config.env import env def home(request): - return HttpResponse("OK: #e5df804b080de139b62f6987059cc35229d63bf8") + return HttpResponse("OK: #e591779cdcdefce21c3df7d3378e35d1294bb1e7") urlpatterns = [ diff --git a/core/apps/accounts/permissions.py b/core/apps/accounts/permissions.py index cc451fe..9d6e684 100644 --- a/core/apps/accounts/permissions.py +++ b/core/apps/accounts/permissions.py @@ -9,7 +9,8 @@ class IsAdminRole(BasePermission): if not request.user.is_authenticated: return False - if request.user.role != RoleChoice.ADMIN: + if request.user.role not in [RoleChoice.ADMIN ,RoleChoice.SUPERUSER]: raise PermissionDenied("Only admin can access this") - return True + + diff --git a/core/apps/accounts/urls.py b/core/apps/accounts/urls.py index 44df55f..94a8ae7 100644 --- a/core/apps/accounts/urls.py +++ b/core/apps/accounts/urls.py @@ -9,7 +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 +from core.apps.accounts.views.user import DeleteAdminUserApiView, UserDetailAPIView, AdminPermissionsAPIView router = DefaultRouter() router.register("auth", RegisterView, basename="auth") @@ -34,4 +34,5 @@ urlpatterns = [ path("admin/update//", AdminUpdateAPIView.as_view(), name="user-update"), path('user/admin//delete/', DeleteAdminUserApiView.as_view(), name='user-delete'), path('user//', UserDetailAPIView.as_view(), name='user-detail'), + path('admin-permission/',AdminPermissionsAPIView.as_view(),name='admin-permissions'), ] diff --git a/core/apps/accounts/views/permission.py b/core/apps/accounts/views/permission.py index 2035b60..ed31f68 100644 --- a/core/apps/accounts/views/permission.py +++ b/core/apps/accounts/views/permission.py @@ -1,6 +1,6 @@ from django_core.mixins import BaseViewSetMixin from drf_spectacular.utils import extend_schema -from rest_framework.permissions import AllowAny, IsAdminUser +from rest_framework.permissions import IsAdminUser from rest_framework.viewsets import ModelViewSet from core.apps.accounts.models.permission import PermissionToAction, PermissionToTab, Permission, Role @@ -19,7 +19,7 @@ class PermissionToActionViewSet(BaseViewSetMixin, ModelViewSet): } action_permission_classes = { - 'create': [AllowAny], + 'create': [IsAdminUser], 'destroy': [IsAdminUser], } diff --git a/core/apps/accounts/views/user.py b/core/apps/accounts/views/user.py index ed02671..5801dad 100644 --- a/core/apps/accounts/views/user.py +++ b/core/apps/accounts/views/user.py @@ -97,7 +97,7 @@ class UserDetailAPIView(generics.RetrieveAPIView): serializer_class = UserSerializer lookup_field = 'id' - +@extend_schema(tags=['User']) class AdminPermissionsAPIView(generics.GenericAPIView): permission_classes = [IsAuthenticated] queryset = User.objects.all() diff --git a/core/apps/chat/choices/chat.py b/core/apps/chat/choices/chat.py index 097f470..d3d4faf 100644 --- a/core/apps/chat/choices/chat.py +++ b/core/apps/chat/choices/chat.py @@ -5,6 +5,7 @@ from django.utils.translation import gettext_lazy as _ class RoomType(models.TextChoices): AUTO_EVALUATION = "auto_evaluation", _("AutoEvaluation xonasi") DIRECT = "direct", _("To'g'ridan-to'g'ri") + MECHANIC_AUTO_EVALUATION = 'mechanic_evaluation', _("Mechanic Auto-Evaluation xonasi") class MessageType(models.TextChoices): diff --git a/core/apps/chat/migrations/0004_alter_chatroommodel_type.py b/core/apps/chat/migrations/0004_alter_chatroommodel_type.py new file mode 100644 index 0000000..98c78c6 --- /dev/null +++ b/core/apps/chat/migrations/0004_alter_chatroommodel_type.py @@ -0,0 +1,28 @@ +# Generated by Django 5.2.7 on 2026-05-06 12:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("chat", "0003_alter_chatmessagemodel_options_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="chatroommodel", + name="type", + field=models.CharField( + choices=[ + ("auto_evaluation", "AutoEvaluation xonasi"), + ("direct", "To'g'ridan-to'g'ri"), + ("mechanic_evaluation", "Mechanic Auto-Evaluation xonasi"), + ], + db_index=True, + default="auto_evaluation", + max_length=20, + verbose_name="type", + ), + ), + ] diff --git a/core/apps/chat/models/chat.py b/core/apps/chat/models/chat.py index 559a184..55aa5a8 100644 --- a/core/apps/chat/models/chat.py +++ b/core/apps/chat/models/chat.py @@ -3,6 +3,7 @@ from django.utils.translation import gettext_lazy as _ from django_core.models import AbstractBaseModel from model_bakery import baker +from core.apps import evaluation from core.apps.chat.choices.chat import MessageType, RoomType diff --git a/core/apps/evaluation/choices/auto.py b/core/apps/evaluation/choices/auto.py index 0e2574f..04750df 100644 --- a/core/apps/evaluation/choices/auto.py +++ b/core/apps/evaluation/choices/auto.py @@ -16,6 +16,7 @@ class AutoEvaluationStatus(models.TextChoices): EVALUATED = "baxolandi", _("Baholandi") REJECTED = "rad_etildi", _("Rad etildi") APPROVED = "tasdiqlandi", _("Tasdiqlandi") + PENDING = "pending", _("Pending") class ObjectOwnerType(models.IntegerChoices): diff --git a/core/apps/evaluation/migrations/0047_autoevaluationmodel_tender_contract_date_and_more.py b/core/apps/evaluation/migrations/0047_autoevaluationmodel_tender_contract_date_and_more.py new file mode 100644 index 0000000..bd823a2 --- /dev/null +++ b/core/apps/evaluation/migrations/0047_autoevaluationmodel_tender_contract_date_and_more.py @@ -0,0 +1,109 @@ +# Generated by Django 5.2.7 on 2026-05-06 12:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("evaluation", "0046_mechanicauto_fields_and_multi_tex_passport"), + ] + + operations = [ + migrations.AddField( + model_name="autoevaluationmodel", + name="tender_contract_date", + field=models.DateField(blank=True, null=True), + ), + migrations.AddField( + model_name="autoevaluationmodel", + name="tender_contract_file", + field=models.FileField(blank=True, null=True, upload_to="tender_contracts/%Y/%m/"), + ), + migrations.AddField( + model_name="autoevaluationmodel", + name="tender_contract_number", + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name="autoevaluationmodel", + name="with_tender_field", + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name="mechanicautoevaluationmodel", + name="tender_contract_date", + field=models.DateField(blank=True, null=True), + ), + migrations.AddField( + model_name="mechanicautoevaluationmodel", + name="tender_contract_file", + field=models.FileField(blank=True, null=True, upload_to="tender_contracts/%Y/%m/"), + ), + migrations.AddField( + model_name="mechanicautoevaluationmodel", + name="tender_contract_number", + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name="mechanicautoevaluationmodel", + name="with_tender_field", + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name="mechanicautoevaluationtexpassportfile", + name="calculated_price", + field=models.DecimalField(decimal_places=2, max_digits=12, null=True), + ), + migrations.AddField( + model_name="mechanicautoevaluationtexpassportfile", + name="status", + field=models.CharField( + choices=[ + ("yaratildi", "Yaratildi"), + ("baxolovchi_biriktirildi", "Baholovchi biriktirildi"), + ("baxolandi", "Baholandi"), + ("rad_etildi", "Rad etildi"), + ("tasdiqlandi", "Tasdiqlandi"), + ("pending", "Pending"), + ], + default="yaratildi", + max_length=50, + verbose_name="status", + ), + ), + migrations.AlterField( + model_name="autoevaluationmodel", + name="status", + field=models.CharField( + choices=[ + ("yaratildi", "Yaratildi"), + ("baxolovchi_biriktirildi", "Baholovchi biriktirildi"), + ("baxolandi", "Baholandi"), + ("rad_etildi", "Rad etildi"), + ("tasdiqlandi", "Tasdiqlandi"), + ("pending", "Pending"), + ], + default="yaratildi", + max_length=50, + verbose_name="status", + ), + ), + migrations.AlterField( + model_name="mechanicautoevaluationmodel", + name="status", + field=models.CharField( + choices=[ + ("yaratildi", "Yaratildi"), + ("baxolovchi_biriktirildi", "Baholovchi biriktirildi"), + ("baxolandi", "Baholandi"), + ("rad_etildi", "Rad etildi"), + ("tasdiqlandi", "Tasdiqlandi"), + ("pending", "Pending"), + ], + default="yaratildi", + max_length=50, + verbose_name="status", + ), + ), + ] diff --git a/core/apps/evaluation/migrations/0049_merge_20260506_1715.py b/core/apps/evaluation/migrations/0049_merge_20260506_1715.py new file mode 100644 index 0000000..60cb17d --- /dev/null +++ b/core/apps/evaluation/migrations/0049_merge_20260506_1715.py @@ -0,0 +1,13 @@ +# Generated by Django 5.2.7 on 2026-05-06 12:15 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("evaluation", "0047_autoevaluationmodel_tender_contract_date_and_more"), + ("evaluation", "0048_autoevaluation_customer_fields"), + ] + + operations = [] diff --git a/core/apps/evaluation/models/auto.py b/core/apps/evaluation/models/auto.py index 8e45621..ecda382 100644 --- a/core/apps/evaluation/models/auto.py +++ b/core/apps/evaluation/models/auto.py @@ -32,6 +32,24 @@ class AutoEvaluationModel(AbstractBaseModel): null=True, blank=True, ) + with_tender_field = models.BooleanField(default=False) + + tender_contract_number = models.CharField( + max_length=255, + null=True, + blank=True + ) + + tender_contract_file = models.FileField( + upload_to="tender_contracts/%Y/%m/", + null=True, + blank=True + ) + + tender_contract_date = models.DateField( + null=True, + blank=True + ) valuation = models.OneToOneField( ValuationModel, on_delete=models.CASCADE, diff --git a/core/apps/evaluation/models/mechanic_auto.py b/core/apps/evaluation/models/mechanic_auto.py index 89dd626..433d76f 100644 --- a/core/apps/evaluation/models/mechanic_auto.py +++ b/core/apps/evaluation/models/mechanic_auto.py @@ -1,4 +1,5 @@ from django.db import models +from django.db.models import DecimalField from django.utils.translation import gettext_lazy as _ from django_core.models import AbstractBaseModel from model_bakery import baker @@ -28,6 +29,25 @@ class MechanicAutoEvaluationModel(AbstractBaseModel): null=True, blank=True, ) + with_tender_field = models.BooleanField(default=False) + + tender_contract_number = models.CharField( + max_length=255, + null=True, + blank=True + ) + + tender_contract_file = models.FileField( + upload_to="tender_contracts/%Y/%m/", + null=True, + blank=True + ) + + tender_contract_date = models.DateField( + null=True, + blank=True + ) + valuation = models.OneToOneField( ValuationModel, on_delete=models.CASCADE, @@ -358,6 +378,14 @@ class MechanicAutoEvaluationTexPassportFile(AbstractBaseModel): verbose_name=_("tech passport file"), upload_to="mechanic_evaluation/tech_passports/%Y/%m/", ) + status = models.CharField( + verbose_name=_("status"), + max_length=50, + choices=AutoEvaluationStatus.choices, + default=AutoEvaluationStatus.CREATED, + ) + + calculated_price = DecimalField(max_digits=12, decimal_places=2, null=True) def __str__(self): return f"Tex passport file for MechanicAutoEvaluation #{self.mechanic_auto_evaluation_id}" diff --git a/core/apps/evaluation/serializers/auto/MechanicAutoEvaluation.py b/core/apps/evaluation/serializers/auto/MechanicAutoEvaluation.py index 58df798..0d1d9a6 100644 --- a/core/apps/evaluation/serializers/auto/MechanicAutoEvaluation.py +++ b/core/apps/evaluation/serializers/auto/MechanicAutoEvaluation.py @@ -539,3 +539,8 @@ class MechanicAutoEvaluationModelSerializer(serializers.ModelSerializer): "created_at", "updated_at", ) + +class MechanicAutoEvaluationTexPassportFileCreateSerializer(serializers.ModelSerializer): + class Meta: + model = MechanicAutoEvaluationTexPassportFile + fields = ['mechanic_auto_evaluation','file', 'calculated_price'] diff --git a/core/apps/evaluation/urls.py b/core/apps/evaluation/urls.py index 756eefa..6b140e0 100644 --- a/core/apps/evaluation/urls.py +++ b/core/apps/evaluation/urls.py @@ -2,7 +2,8 @@ from django.urls import include, path from rest_framework.routers import DefaultRouter from core.apps.evaluation import views - +from core.apps.evaluation.views import MechanicAutoEvaluationTexPassportFileCreateView, \ + MechanicAutoEvaluationTexPassportFileRetrieveAPIView, MechanicAutoEvaluationTexPassportFileListAPIView router = DefaultRouter() router.register("document-category", views.DocumentCategoryView, basename="DocumentCategory") @@ -33,6 +34,7 @@ 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)), @@ -111,4 +113,14 @@ urlpatterns = [ path("calculate_avg_cost/", views.AvgCostAPIView.as_view()), path("vehicle_document/", views.GeneratePDFView.as_view()), + + path("mechanic-auto/", include([ + + path('upload-passport/', MechanicAutoEvaluationTexPassportFileCreateView.as_view()), + path('passport/', MechanicAutoEvaluationTexPassportFileRetrieveAPIView.as_view()), + path('passport/', MechanicAutoEvaluationTexPassportFileListAPIView.as_view()) + + + + ])) ] diff --git a/core/apps/evaluation/views/bonus.py b/core/apps/evaluation/views/bonus.py index 3e294a8..e763028 100644 --- a/core/apps/evaluation/views/bonus.py +++ b/core/apps/evaluation/views/bonus.py @@ -1,9 +1,9 @@ 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 +from core.apps.accounts.permissions import IsAdminRole # core from core.apps.evaluation.models.bonus import BonusCategory, EmployeeBonus, BaseValueBonus from core.apps.evaluation.serializers.bonus.Bonus import BonusCategorySerializer, \ @@ -31,14 +31,14 @@ class BonusTypeView(BaseViewSetMixin, ModelViewSet): } action_permission_classes = { - 'create': [IsAdminUser], - 'update': [IsAdminUser], - 'partial_update': [IsAdminUser], - 'destroy': [IsAdminUser], - 'list': [IsAdminUser], + 'create': [IsAdminRole], + 'update': [IsAdminRole], + 'partial_update': [IsAdminRole], + 'destroy': [IsAdminRole], + 'list': [IsAdminRole], } - +@extend_schema(tags=["Bonus-Employee"]) class BonusEmployeeViewSet(BaseViewSetMixin, ModelViewSet): queryset = EmployeeBonus.objects.all() serializer_class = BonusEmployeeBonusSerializer @@ -52,9 +52,9 @@ class BonusEmployeeViewSet(BaseViewSetMixin, ModelViewSet): } action_permission_classes = { - 'create': [IsAdminUser], - 'update': [IsAdminUser], - 'partial_update': [IsAdminUser], - 'destroy': [IsAdminUser], - 'list': [IsAdminUser], + 'create': [IsAdminRole], + 'update': [IsAdminRole], + 'partial_update': [IsAdminRole], + 'destroy': [IsAdminRole], + 'list': [IsAdminRole], } diff --git a/core/apps/evaluation/views/mechanic_auto.py b/core/apps/evaluation/views/mechanic_auto.py index 81232a6..1512f30 100644 --- a/core/apps/evaluation/views/mechanic_auto.py +++ b/core/apps/evaluation/views/mechanic_auto.py @@ -4,8 +4,10 @@ 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.decorators import action from rest_framework.filters import OrderingFilter, SearchFilter from rest_framework.generics import GenericAPIView, ListAPIView +from rest_framework.parsers import FormParser, MultiPartParser from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView @@ -13,15 +15,17 @@ 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.choices.auto import AutoEvaluationStatus from core.apps.evaluation.filters.mechanic_auto import MechanicAutoevaluationFilter -from core.apps.evaluation.models import MechanicAutoEvaluationModel +from core.apps.evaluation.models import MechanicAutoEvaluationModel, MechanicAutoEvaluationTexPassportFile from core.apps.evaluation.serializers.auto.MechanicAutoEvaluation import ( ListMechanicAutoevaluationSerializer, RetrieveMechanicAutoevaluationSerializer, CreateMechanicAutoevaluationSerializer, UpdateMechanicAutoevaluationSerializer, MechanicAutoEvaluationAppraisersSerializer, - MechanicAutoEvaluationModelSerializer, + MechanicAutoEvaluationModelSerializer, MechanicAutoEvaluationTexPassportFileCreateSerializer, + MechanicAutoEvaluationTexPassportFileSerializer, ) @@ -196,3 +200,28 @@ class AdminMechanicEvaluationsAPIView(generics.GenericAPIView): ).distinct() serializer = MechanicAutoEvaluationModelSerializer(evaluations, many=True) return Response(serializer.data) + + +@extend_schema(tags=["MechanicAutoEvaluationTexPassportFile"]) +class MechanicAutoEvaluationTexPassportFileCreateView(generics.CreateAPIView): + permission_classes = [IsAuthenticated, IsAdminRole] + serializer_class = MechanicAutoEvaluationTexPassportFileCreateSerializer + queryset = MechanicAutoEvaluationTexPassportFile.objects.all() + + def perform_create(self, serializer): + eval_id = self.kwargs.get('eval_id') + serializer.save(mechanic_auto_evaluation_id=eval_id, status=AutoEvaluationStatus.PENDING) + + +@extend_schema(tags=["MechanicAutoEvaluationTexPassportFile"]) +class MechanicAutoEvaluationTexPassportFileRetrieveAPIView(generics.RetrieveAPIView): + permission_classes = [IsAuthenticated, IsAdminRole] + queryset = MechanicAutoEvaluationTexPassportFile.objects.all() + serializer_class = MechanicAutoEvaluationTexPassportFileSerializer + + +@extend_schema(tags=["MechanicAutoEvaluationTexPassportFile"]) +class MechanicAutoEvaluationTexPassportFileListAPIView(generics.ListAPIView): + permission_classes = [IsAuthenticated, IsAdminRole] + queryset = MechanicAutoEvaluationTexPassportFile.objects.all() + serializer_class = MechanicAutoEvaluationTexPassportFileSerializer \ No newline at end of file diff --git a/core/apps/tasks/serializers/task.py b/core/apps/tasks/serializers/task.py index 478dc09..c60ba3b 100644 --- a/core/apps/tasks/serializers/task.py +++ b/core/apps/tasks/serializers/task.py @@ -54,6 +54,14 @@ class TaskCreateSerializer(serializers.ModelSerializer): } + extra_kwargs = { + 'labels' : {'required' : False}, + 'description' : {'required' : False}, + 'from_date' : {'required' : False}, + 'to_date' : {'required' : False}, + + } + def create(self, validated_data): validated_data['created_by'] = self.context['request'].user return super().create(validated_data) diff --git a/stack.yaml b/stack.yaml index 7e8d583..faf8a3c 100644 --- a/stack.yaml +++ b/stack.yaml @@ -84,7 +84,7 @@ services: max-file: "5" web: - image: husanjon/sifatbaho:161 + image: husanjon/sifatbaho:168 env_file: - .env environment: @@ -129,7 +129,7 @@ services: max-file: "5" celery: - image: husanjon/sifatbaho:161 + image: husanjon/sifatbaho:168 env_file: - .env environment: