Compare commits
26 Commits
ca3c9bfe4a
...
request-ar
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a08c81ff3 | ||
|
|
320f490d23 | ||
| d4e6d80c86 | |||
| 1f0e942be8 | |||
|
|
2af67333e2 | ||
| 81c689e7e9 | |||
|
|
09d2e0954c | ||
| 84a5afb2ee | |||
|
|
cc8fc345d9 | ||
| 03e4387e4d | |||
|
|
64993805af | ||
|
|
01711b5927 | ||
|
|
33aa06f80b | ||
|
|
cb2d83b00d | ||
| 46c9621457 | |||
|
|
8d73fa09a7 | ||
| 3367063707 | |||
|
|
ae926914a5 | ||
|
|
229676be5c | ||
| d2f517baf4 | |||
|
|
6df20f35c2 | ||
|
|
fa676bfa96 | ||
| ca9f12ae32 | |||
|
|
56f693abfd | ||
|
|
5049919865 | ||
|
|
8207b750b8 |
4
.github/workflows/deploy.yaml
vendored
4
.github/workflows/deploy.yaml
vendored
@@ -153,7 +153,7 @@ jobs:
|
||||
update_env \
|
||||
"DB_HOST=postgres" \
|
||||
"DB_NAME=sifatbahodb" \
|
||||
"DB_PORT=5432"
|
||||
|
||||
"DB_PORT=5432" \
|
||||
"DIDOX_TOKEN=${{ secrets.DIDOX_TOKEN }}"
|
||||
export PORT=8085
|
||||
docker stack deploy -c stack.yaml ${{ env.PROJECT_NAME }} --with-registry-auth
|
||||
|
||||
@@ -13,7 +13,7 @@ from config.env import env
|
||||
|
||||
|
||||
def home(request):
|
||||
return HttpResponse("OK: #f66a35ec80028f5b7cd09a5e540a1b6bf91e653e")
|
||||
return HttpResponse("OK: #d4e6d80c86fcf4422f71238c6552dfa4b42f9737")
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
|
||||
@@ -67,27 +67,20 @@ class ChatConsumer(AsyncWebsocketConsumer):
|
||||
|
||||
await self._save_message(user, text)
|
||||
|
||||
# Uz
|
||||
# Bu funksiya ishlatilmayapti, shuning uchun commentga olib qoydim, bu funksiya orniga /core/apps/chat/tasks/message.py ichida rest api yordamida message
|
||||
# yuborilsa ishlatiladigan task bor.
|
||||
|
||||
# En
|
||||
# This function is not used, so I commented it out. Instead, a task is used in /core/apps/chat/tasks/message.py
|
||||
# to send message when message is added from REST API.
|
||||
|
||||
# async def chat_message(self, event):
|
||||
# await self.send(
|
||||
# text_data=json.dumps(
|
||||
# {
|
||||
# "id": event["id"],
|
||||
# "message_type": event["message_type"],
|
||||
# "text": event["text"],
|
||||
# "file_url": event["file_url"],
|
||||
# "sender": event["sender"],
|
||||
# "created_at": event["created_at"],
|
||||
# }
|
||||
# )
|
||||
# )
|
||||
async def chat_message(self, event):
|
||||
await self.send(
|
||||
text_data=json.dumps(
|
||||
{
|
||||
"id": event["id"],
|
||||
"message_type": event["message_type"],
|
||||
"text": event["text"],
|
||||
"file_url": event["file_url"],
|
||||
"sender": event["sender"],
|
||||
"created_at": event["created_at"],
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
@database_sync_to_async
|
||||
def _save_message(self, user, text):
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 6.0.4 on 2026-04-23 07:22
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('evaluation', '0031_remove_autoevaluationmodel_object_location_city_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='evaluationrequestmodel',
|
||||
name='is_archive',
|
||||
field=models.BooleanField(default=False, verbose_name='is archive'),
|
||||
),
|
||||
]
|
||||
@@ -118,9 +118,12 @@ class EvaluationrequestModel(AbstractBaseModel):
|
||||
choices=RequestStatus.choices,
|
||||
default=RequestStatus.PENDING,
|
||||
)
|
||||
|
||||
is_archive = models.BooleanField(
|
||||
verbose_name=_("is archive"),
|
||||
default=False,
|
||||
)
|
||||
def __str__(self):
|
||||
return f"Request #{self.pk} — {self.get_rate_type_display()}"
|
||||
return f"Requests #{self.pk} — {self.get_rate_type_display()}"
|
||||
|
||||
@classmethod
|
||||
def _baker(cls):
|
||||
|
||||
@@ -339,8 +339,9 @@ class CreateAutoevaluationSerializer(serializers.ModelSerializer):
|
||||
user = self.context.get('request').user
|
||||
validated_data['user'] = user
|
||||
evaluation_req = validated_data.get("evaluation_request")
|
||||
evaluation_req.status = RequestStatus.IN_PROGRESS
|
||||
evaluation_req.save()
|
||||
if evaluation_req:
|
||||
evaluation_req.status = RequestStatus.IN_PROGRESS
|
||||
evaluation_req.save()
|
||||
return super().create(validated_data)
|
||||
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ class BaseEvaluationrequestSerializer(serializers.ModelSerializer):
|
||||
"user",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"is_archive",
|
||||
]
|
||||
|
||||
def get_location(self, obj):
|
||||
@@ -183,3 +184,7 @@ class CreateEvaluationrequestSerializer(serializers.ModelSerializer):
|
||||
validated_data["location_name"] = str(location_name)
|
||||
validated_data["user"] = self.context["request"].user
|
||||
return super().create(validated_data)
|
||||
|
||||
class ArchiveEvaluationrequestSerializer(serializers.Serializer):
|
||||
id = serializers.IntegerField(required=True)
|
||||
is_archive = serializers.BooleanField(required=True)
|
||||
@@ -28,6 +28,8 @@ from .views import (
|
||||
AutoEvaluationRemoveAppraisersView,
|
||||
DidoxCompanyInfoAPIView,
|
||||
TechPassportAPIView,
|
||||
EvaluationStatusChange,
|
||||
ArchiveEvaluationrequestView,
|
||||
)
|
||||
|
||||
router = DefaultRouter()
|
||||
@@ -71,4 +73,7 @@ urlpatterns = [
|
||||
TechPassportAPIView.as_view(),
|
||||
name="tech-passport"
|
||||
),
|
||||
path("evaluation-request/<int:pk>/change-status/", EvaluationStatusChange.as_view(),
|
||||
name="evaluation-change-status"),
|
||||
path("evaluation-request/archive/", ArchiveEvaluationrequestView.as_view(), name="evaluation-request-archive"),
|
||||
]
|
||||
|
||||
@@ -31,7 +31,6 @@ class DidoxCompanyInfoAPIView(GenericAPIView):
|
||||
def get(self, request, *args, **kwargs):
|
||||
tin = kwargs.get("tin")
|
||||
|
||||
# 🔥 TYPE CHECK
|
||||
try:
|
||||
tin = int(tin)
|
||||
except (TypeError, ValueError):
|
||||
@@ -48,4 +47,14 @@ class DidoxCompanyInfoAPIView(GenericAPIView):
|
||||
status=status.HTTP_502_BAD_GATEWAY
|
||||
)
|
||||
|
||||
# if both name and personalNum are null/empty -> 404
|
||||
name = data.get("name")
|
||||
personal_num = data.get("personalNum")
|
||||
|
||||
if not name and not personal_num:
|
||||
return Response(
|
||||
{"detail": "Company or person not found"},
|
||||
status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
|
||||
return Response(data, status=status.HTTP_200_OK)
|
||||
@@ -1,3 +1,5 @@
|
||||
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
|
||||
@@ -5,6 +7,9 @@ from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.pagination import PageNumberPagination
|
||||
from rest_framework.permissions import AllowAny, IsAuthenticated
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework import status
|
||||
|
||||
from core.apps.evaluation.filters.request import EvaluationrequestFilter
|
||||
from core.apps.evaluation.models import EvaluationrequestModel
|
||||
@@ -12,7 +17,11 @@ from core.apps.evaluation.serializers.request import (
|
||||
CreateEvaluationrequestSerializer,
|
||||
ListEvaluationrequestSerializer,
|
||||
RetrieveEvaluationrequestSerializer,
|
||||
ArchiveEvaluationrequestSerializer,
|
||||
)
|
||||
from core.apps.evaluation.choices.request import RequestStatus
|
||||
from rest_framework.generics import GenericAPIView
|
||||
from drf_spectacular.utils import OpenApiResponse
|
||||
|
||||
|
||||
# class RequestPagination(PageNumberPagination):
|
||||
@@ -128,3 +137,114 @@ class AdminEvaluationrequestView(BaseViewSetMixin, ModelViewSet):
|
||||
|
||||
def serializer_context(self):
|
||||
return self.serializer_class(context={"request": self.request})
|
||||
|
||||
|
||||
@extend_schema(tags=["EvaluationRequest"])
|
||||
class EvaluationStatusChange(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request, pk):
|
||||
if request.user.role not in [RoleChoice.ADMIN, RoleChoice.SUPERUSER]:
|
||||
return Response({'detail': 'Forbidden'}, status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
evaluation = get_object_or_404(EvaluationrequestModel, pk=pk)
|
||||
|
||||
|
||||
status_value = request.data.get('status')
|
||||
if not status_value:
|
||||
return Response({'detail': 'Status is required'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
valid_statuses = [
|
||||
RequestStatus.PENDING,
|
||||
RequestStatus.IN_PROGRESS,
|
||||
RequestStatus.COMPLETED,
|
||||
RequestStatus.REJECTED
|
||||
]
|
||||
|
||||
if status_value not in valid_statuses:
|
||||
return Response(
|
||||
{'detail': f'Invalid status. Must be one of: {valid_statuses}'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
evaluation.status = status_value
|
||||
evaluation.save()
|
||||
|
||||
return Response({
|
||||
'success': True,
|
||||
'status': evaluation.status,
|
||||
'id': evaluation.pk
|
||||
})
|
||||
|
||||
@extend_schema(tags=["EvaluationRequest"])
|
||||
class ArchiveEvaluationrequestView(GenericAPIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.request.method == "GET":
|
||||
return ListEvaluationrequestSerializer
|
||||
return ArchiveEvaluationrequestSerializer
|
||||
|
||||
@extend_schema(
|
||||
tags=["EvaluationRequest"],
|
||||
summary="Get archived evaluation requests list",
|
||||
description="""
|
||||
Returns only archived evaluation requests.
|
||||
|
||||
This endpoint works like evaluation-request/,
|
||||
but only records with is_archive=True are returned.
|
||||
""",
|
||||
responses={200: ListEvaluationrequestSerializer(many=True)},
|
||||
)
|
||||
def get(self, request, *args, **kwargs):
|
||||
queryset = EvaluationrequestModel.objects.filter(
|
||||
is_archive=True
|
||||
).order_by("-created_at")
|
||||
|
||||
serializer = self.get_serializer(queryset, many=True)
|
||||
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@extend_schema(
|
||||
tags=["EvaluationRequest"],
|
||||
summary="Archive or unarchive evaluation request",
|
||||
description="""
|
||||
Update archive status for evaluation request.
|
||||
|
||||
- is_archive=true → archive
|
||||
- is_archive=false → remove from archive
|
||||
""",
|
||||
request=ArchiveEvaluationrequestSerializer,
|
||||
responses={
|
||||
200: OpenApiResponse(
|
||||
description="Archive status updated successfully"
|
||||
),
|
||||
400: OpenApiResponse(
|
||||
description="Validation error"
|
||||
),
|
||||
404: OpenApiResponse(
|
||||
description="Evaluation request not found"
|
||||
),
|
||||
},
|
||||
)
|
||||
def post(self, request, *args, **kwargs):
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
validated_data = serializer.validated_data
|
||||
|
||||
obj = get_object_or_404(
|
||||
EvaluationrequestModel,
|
||||
id=validated_data["id"]
|
||||
)
|
||||
|
||||
obj.is_archive = validated_data["is_archive"]
|
||||
obj.save(update_fields=["is_archive"])
|
||||
|
||||
return Response(
|
||||
{
|
||||
"success": True,
|
||||
"message": "Archive status updated successfully"
|
||||
},
|
||||
status=status.HTTP_200_OK
|
||||
)
|
||||
@@ -33,18 +33,27 @@ class TechPassportAPIView(GenericAPIView):
|
||||
def post(self, request, *args, **kwargs):
|
||||
serializer = TechPassportSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
data = serializer.validated_data
|
||||
|
||||
try:
|
||||
result = TechPassportService.get_auto_info(
|
||||
autonumber=data["autonumber"],
|
||||
tech_pass_number=data["tech_pass_number"],
|
||||
tech_pass_series=data["tech_pass_series"],
|
||||
)
|
||||
return Response(result, status=status.HTTP_200_OK)
|
||||
result = TechPassportService.get_auto_info(
|
||||
autonumber=data["autonumber"],
|
||||
tech_pass_number=data["tech_pass_number"],
|
||||
tech_pass_series=data["tech_pass_series"],
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
response_data = result["data"]
|
||||
status_code = result["status_code"]
|
||||
|
||||
# success bo‘lsa faqat data ichidagi data qaytariladi
|
||||
if status_code == 200:
|
||||
return Response(
|
||||
{"detail": str(e)},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
response_data.get("data", {}),
|
||||
status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
# error bo‘lsa original response qaytariladi
|
||||
return Response(
|
||||
response_data,
|
||||
status=status_code
|
||||
)
|
||||
@@ -1,10 +1,28 @@
|
||||
from rest_framework import generics
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from drf_spectacular.utils import extend_schema, OpenApiParameter
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
|
||||
from core.apps.shared.serializers.region.district import DistrictSerializer, VillageSerializer, RegionSerializer
|
||||
from core.apps.shared.models import RegionModel, VillageModel, DistrictModel
|
||||
|
||||
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name="region",
|
||||
type=OpenApiTypes.INT,
|
||||
location=OpenApiParameter.QUERY,
|
||||
description="Filter by region ID"
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="name",
|
||||
type=OpenApiTypes.STR,
|
||||
location=OpenApiParameter.QUERY,
|
||||
description="Filter by district name"
|
||||
),
|
||||
]
|
||||
)
|
||||
class DistrictListCreateView(generics.ListCreateAPIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
serializer_class = DistrictSerializer
|
||||
@@ -19,7 +37,22 @@ class DistrictListCreateView(generics.ListCreateAPIView):
|
||||
return self.queryset.filter(region=region)
|
||||
return super().get_queryset()
|
||||
|
||||
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name="district",
|
||||
type=OpenApiTypes.INT,
|
||||
location=OpenApiParameter.QUERY,
|
||||
description="Filter by district ID"
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="name",
|
||||
type=OpenApiTypes.STR,
|
||||
location=OpenApiParameter.QUERY,
|
||||
description="Filter by village name"
|
||||
),
|
||||
]
|
||||
)
|
||||
class VillageListCreateView(generics.ListCreateAPIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
serializer_class = VillageSerializer
|
||||
|
||||
@@ -44,21 +44,28 @@ class TechPassportService:
|
||||
verify=False
|
||||
)
|
||||
|
||||
response.raise_for_status()
|
||||
|
||||
logger.info(
|
||||
f"Tech passport info fetched successfully: {response.status_code}"
|
||||
f"Tech passport response status: {response.status_code}"
|
||||
)
|
||||
|
||||
response_data = response.json()
|
||||
try:
|
||||
response_data = response.json()
|
||||
except ValueError:
|
||||
response_data = {
|
||||
"detail": "Invalid response from external service"
|
||||
}
|
||||
|
||||
return response_data.get("data", {})
|
||||
return {
|
||||
"status_code": response.status_code,
|
||||
"data": response_data
|
||||
}
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error(
|
||||
f"Error while fetching tech passport info: {str(e)}"
|
||||
)
|
||||
logger.error(str(e))
|
||||
|
||||
return {
|
||||
"success": False,
|
||||
"message": str(e)
|
||||
"status_code": 500,
|
||||
"data": {
|
||||
"detail": str(e)
|
||||
}
|
||||
}
|
||||
@@ -84,7 +84,7 @@ services:
|
||||
max-file: "5"
|
||||
|
||||
web:
|
||||
image: husanjon/sifatbaho:98
|
||||
image: husanjon/sifatbaho:107
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
@@ -129,7 +129,7 @@ services:
|
||||
max-file: "5"
|
||||
|
||||
celery:
|
||||
image: husanjon/sifatbaho:98
|
||||
image: husanjon/sifatbaho:107
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
|
||||
Reference in New Issue
Block a user