From c61b62969ae95edf44b820fac7c8c7f10a0da81f Mon Sep 17 00:00:00 2001 From: lizerk Date: Fri, 27 Mar 2026 18:14:55 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9C=D0=B5=D0=BB=D0=BA=D0=B8=D0=B5=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B0=D0=B2=D0=BA=D0=B8:=20=D0=BE=D0=B1=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20serializers.py=20=D0=B8=20view?= =?UTF-8?q?s.py=20=D0=B4=D0=BB=D1=8F=20procurement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/api/v1/procurement/serializers.py | 12 ++- backend/api/v1/procurement/views.py | 116 +++++++++++++++++++++- 2 files changed, 124 insertions(+), 4 deletions(-) diff --git a/backend/api/v1/procurement/serializers.py b/backend/api/v1/procurement/serializers.py index 49592dbb..99d41dcf 100644 --- a/backend/api/v1/procurement/serializers.py +++ b/backend/api/v1/procurement/serializers.py @@ -362,6 +362,7 @@ class EquipmentListSerializer(serializers.ModelSerializer): read_only=True ) is_under_warranty = serializers.BooleanField(read_only=True) + comments_count = serializers.IntegerField(read_only=True, default=0) class Meta: model = Equipment @@ -369,6 +370,7 @@ class EquipmentListSerializer(serializers.ModelSerializer): 'id', 'name', 'inventory_number', + 'serial_number', 'category', 'category_name', 'category_icon', @@ -378,19 +380,27 @@ class EquipmentListSerializer(serializers.ModelSerializer): 'department_name', 'responsible_person', 'responsible_name', + 'location', 'purchase_date', 'purchase_cost', + 'notes', 'is_under_warranty', + 'comments_count', + 'created_at', + 'updated_at', ] read_only_fields = [ 'id', - 'inventory_number', # Генерируется автоматически + 'inventory_number', 'category_name', 'category_icon', 'department_name', 'responsible_name', 'status_display', 'is_under_warranty', + 'comments_count', + 'created_at', + 'updated_at', ] def create(self, validated_data): diff --git a/backend/api/v1/procurement/views.py b/backend/api/v1/procurement/views.py index 4d1e5d4f..e4534362 100644 --- a/backend/api/v1/procurement/views.py +++ b/backend/api/v1/procurement/views.py @@ -24,6 +24,8 @@ from procurement.models import ( ProcurementRequest, Supplier, ) +from communications import comments_helpers +from communications.models import Message from procurement.services import QRCodeGenerator from .permissions import ( CanApproveProcurementRequest, @@ -875,15 +877,43 @@ class EquipmentViewSet(viewsets.ModelViewSet): filters.SearchFilter, filters.OrderingFilter, ] - filterset_fields = ['status', 'category', 'department'] + filterset_fields = { + 'status': ['exact'], + 'category': ['exact'], + 'department': ['exact'], + 'responsible_person': ['exact'], + 'purchase_date': ['gte', 'lte'], + } search_fields = [ 'name', 'inventory_number', 'serial_number', 'location', ] - ordering_fields = ['purchase_date', 'name'] - ordering = ['-purchase_date'] + ordering_fields = ['purchase_date', 'name', 'created_at'] + ordering = ['-created_at'] + + def get_queryset(self): + """Annotate comments_count.""" + from django.contrib.contenttypes.models import ContentType + from django.db.models import Count, Subquery, OuterRef, IntegerField + from communications.models import Chat + + ct = ContentType.objects.get_for_model(Equipment) + qs = super().get_queryset() + + comments_sub = ( + Chat.objects.filter( + type='comments', + context_content_type=ct, + context_object_id=OuterRef('pk'), + ) + .annotate(msg_count=Count('messages', filter=Q(messages__is_deleted=False))) + .values('msg_count')[:1] + ) + return qs.annotate( + comments_count=Subquery(comments_sub, output_field=IntegerField(default=0)) + ) def get_serializer_class(self): """Выбрать сериализатор.""" @@ -1240,6 +1270,86 @@ class EquipmentViewSet(viewsets.ModelViewSet): return Response(data) + @action(detail=True, methods=['get', 'post']) + def comments(self, request, pk=None): + """Список/создание комментариев для оборудования.""" + from api.v1.employees.serializers.employee import ( + EmployeeBriefSerializer, + ) + + equipment = self.get_object() + + if request.method in ('GET', 'HEAD'): + messages = comments_helpers.get_comments(equipment) + comments_data = [] + for msg in messages: + author_ser = EmployeeBriefSerializer(msg.author) + comments_data.append({ + 'id': msg.id, + 'equipment': equipment.id, + 'author': author_ser.data, + 'text': msg.content, + 'created_at': msg.created_at, + }) + return Response(comments_data) + + text = request.data.get('text', '').strip() + if not text: + return Response( + {'text': ['Это поле не может быть пустым.']}, + status=status.HTTP_400_BAD_REQUEST, + ) + + message = comments_helpers.create_comment( + obj=equipment, + author=request.user, + content=text, + ) + author_ser = EmployeeBriefSerializer(message.author) + return Response( + { + 'id': message.id, + 'equipment': equipment.id, + 'author': author_ser.data, + 'text': message.content, + 'created_at': message.created_at, + }, + status=status.HTTP_201_CREATED, + ) + + @action( + detail=True, + methods=['delete'], + url_path='comments/(?P[^/.]+)', + ) + def delete_comment(self, request, pk=None, comment_id=None): + """Удаление комментария к оборудованию.""" + equipment = self.get_object() + chat = comments_helpers.get_or_create_comments_chat(equipment) + if isinstance(chat, tuple): + chat = chat[0] + + try: + message = Message.objects.get(id=comment_id, chat=chat) + except Message.DoesNotExist: + return Response( + {'detail': 'Комментарий не найден'}, + status=status.HTTP_404_NOT_FOUND, + ) + + if not (request.user.is_staff or message.author == request.user): + return Response( + {'detail': 'Нет прав на удаление'}, + status=status.HTTP_403_FORBIDDEN, + ) + + comments_helpers.delete_comment( + message=message, + deleted_by=request.user, + soft_delete=True, + ) + return Response(status=status.HTTP_204_NO_CONTENT) + class MaintenanceRecordViewSet(viewsets.ModelViewSet): """ViewSet для записей обслуживания."""