752 lines
27 KiB
Python
752 lines
27 KiB
Python
"""ViewSets для API модуля закупок."""
|
||
|
||
from datetime import date
|
||
|
||
from django.contrib.contenttypes.models import ContentType
|
||
from django.db import transaction
|
||
from drf_spectacular.utils import extend_schema
|
||
from django.db.models import Q
|
||
from django.utils import timezone
|
||
from django_filters.rest_framework import DjangoFilterBackend
|
||
from rest_framework import filters, permissions, status, viewsets
|
||
from rest_framework.decorators import action
|
||
from rest_framework.response import Response
|
||
|
||
from employees.models import EmployeeDepartment
|
||
from procurement.constants import (
|
||
ProcurementStatus,
|
||
ProcurementItemExecutionStatus,
|
||
)
|
||
from procurement.models import (
|
||
ProcurementItem,
|
||
ProcurementActivityAction,
|
||
ProcurementActivityObjectKind,
|
||
ProcurementRequest,
|
||
ProcurementRequestView,
|
||
)
|
||
from procurement.activity import create_procurement_activity
|
||
from procurement.notifications.handlers import (
|
||
notify_item_comment,
|
||
notify_item_issue_reported,
|
||
notify_item_updated,
|
||
)
|
||
from communications import comments_helpers
|
||
from procurement.services import ProcurementApprovalResolver
|
||
from ..permissions import (
|
||
CanManageProcurementRequest,
|
||
)
|
||
from ..serializers import (
|
||
ProcurementItemSerializer,
|
||
)
|
||
from .mixins import ItemAttachmentsMixin, ItemCommentsMixin, ItemEquipmentMixin
|
||
|
||
|
||
class ProcurementItemViewSet(
|
||
ItemAttachmentsMixin,
|
||
ItemCommentsMixin,
|
||
ItemEquipmentMixin,
|
||
viewsets.ModelViewSet,
|
||
):
|
||
"""ViewSet для позиций заявок."""
|
||
|
||
EXECUTION_FIELDS = {
|
||
"actual_unit_price",
|
||
"execution_status",
|
||
"executor_comment",
|
||
"expected_delivery_dates",
|
||
"links",
|
||
"ordered_quantity",
|
||
"received_quantity",
|
||
}
|
||
|
||
NOTIFICATION_TRACKED_FIELDS = [
|
||
"name",
|
||
"quantity",
|
||
"unit",
|
||
"estimated_unit_price",
|
||
"ordered_quantity",
|
||
"received_quantity",
|
||
"links",
|
||
"expected_delivery_dates",
|
||
"actual_unit_price",
|
||
"execution_status",
|
||
"executor_comment",
|
||
]
|
||
|
||
queryset = ProcurementItem.objects.select_related(
|
||
"request",
|
||
"request__department",
|
||
"request__processing_department",
|
||
)
|
||
serializer_class = ProcurementItemSerializer
|
||
filter_backends = [DjangoFilterBackend, filters.SearchFilter]
|
||
filterset_fields = ["request"]
|
||
search_fields = ["name", "description"]
|
||
|
||
def get_queryset(self):
|
||
"""Annotate item comments count."""
|
||
from django.db.models import Count, IntegerField, OuterRef, Subquery, Value
|
||
from django.db.models.functions import Coalesce
|
||
from communications.models import Chat
|
||
|
||
ct = ContentType.objects.get_for_model(ProcurementItem)
|
||
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 (
|
||
super()
|
||
.get_queryset()
|
||
.annotate(
|
||
comments_count=Coalesce(
|
||
Subquery(comments_sub, output_field=IntegerField()),
|
||
Value(0),
|
||
output_field=IntegerField(),
|
||
)
|
||
)
|
||
)
|
||
|
||
def _is_processing_department_member(self, user, procurement_request):
|
||
return ProcurementApprovalResolver.user_is_processing_department_member(
|
||
user,
|
||
procurement_request,
|
||
)
|
||
|
||
def _can_process_request_items(self, user, procurement_request):
|
||
if user.is_superuser or user.is_staff:
|
||
return True
|
||
if user.has_perm("procurement.change_procurementrequest") or user.has_perm(
|
||
"procurement.execute_procurement"
|
||
):
|
||
return True
|
||
return self._is_processing_department_member(
|
||
user,
|
||
procurement_request,
|
||
)
|
||
|
||
def _can_access_item_comments(self, user, procurement_request):
|
||
if not user or not user.is_authenticated:
|
||
return False
|
||
if user.is_superuser or user.is_staff:
|
||
return True
|
||
if (
|
||
user.has_perm("procurement.view_procurementrequest")
|
||
or user.has_perm("procurement.change_procurementrequest")
|
||
or user.has_perm("procurement.execute_procurement")
|
||
):
|
||
return True
|
||
if procurement_request.requestor_id == user.id:
|
||
return True
|
||
if procurement_request.executor_id == user.id:
|
||
return True
|
||
if procurement_request.department.head_id == user.id:
|
||
return True
|
||
if EmployeeDepartment.objects.filter(
|
||
employee_id=user.id,
|
||
department_id=procurement_request.department_id,
|
||
is_active=True,
|
||
).exists():
|
||
return True
|
||
return self._is_processing_department_member(
|
||
user,
|
||
procurement_request,
|
||
)
|
||
|
||
def get_permissions(self):
|
||
"""Только создатель заявки может редактировать позиции."""
|
||
if self.action in [
|
||
"create",
|
||
"update",
|
||
"partial_update",
|
||
"destroy",
|
||
"create_equipment",
|
||
"link_equipment",
|
||
"comments",
|
||
"mark_comment_notifications_read",
|
||
"delete_comment",
|
||
"report_issue",
|
||
"confirm_ordered",
|
||
"confirm_received",
|
||
"cancel_received",
|
||
"cancel_issue",
|
||
"attachments",
|
||
"attachment_detail",
|
||
"attachment_thumbnail",
|
||
]:
|
||
permission_classes = [permissions.IsAuthenticated]
|
||
else:
|
||
permission_classes = [CanManageProcurementRequest]
|
||
return [permission() for permission in permission_classes]
|
||
|
||
def _check_item_read_permission(self, item):
|
||
from rest_framework.exceptions import PermissionDenied
|
||
|
||
if not self._can_access_item_comments(self.request.user, item.request):
|
||
raise PermissionDenied("Нет доступа к этой позиции")
|
||
|
||
@extend_schema(
|
||
methods=["GET"],
|
||
operation_id="procurement_items_attachments_list",
|
||
)
|
||
def _can_use_item_quick_action(self, user, procurement_request):
|
||
return (
|
||
procurement_request.requestor_id == user.id
|
||
or self._can_process_request_items(user, procurement_request)
|
||
)
|
||
|
||
def _validate_item_quick_action(self, user, procurement_request):
|
||
if not self._can_use_item_quick_action(user, procurement_request):
|
||
return Response(
|
||
{"detail": "Нет прав изменить эту позицию"},
|
||
status=status.HTTP_403_FORBIDDEN,
|
||
)
|
||
|
||
if procurement_request.status in [
|
||
ProcurementStatus.PENDING,
|
||
ProcurementStatus.CANCELLED,
|
||
ProcurementStatus.REJECTED,
|
||
]:
|
||
return Response(
|
||
{
|
||
"detail": (
|
||
"Нельзя изменить позицию в заявке на согласовании, "
|
||
"в отменённой или отклонённой заявке"
|
||
)
|
||
},
|
||
status=status.HTTP_400_BAD_REQUEST,
|
||
)
|
||
return None
|
||
|
||
def _workflow_status_from_quantities(self, item):
|
||
received_quantity = item.received_quantity or 0
|
||
ordered_quantity = item.ordered_quantity or 0
|
||
|
||
if received_quantity > 0:
|
||
return (
|
||
ProcurementItemExecutionStatus.RECEIVED
|
||
if received_quantity >= max(item.quantity, ordered_quantity)
|
||
else ProcurementItemExecutionStatus.ORDERED
|
||
)
|
||
if ordered_quantity > 0:
|
||
return ProcurementItemExecutionStatus.ORDERED
|
||
return ProcurementItemExecutionStatus.PENDING
|
||
|
||
def _record_item_update(self, item, user, changed_fields):
|
||
create_procurement_activity(
|
||
item.request,
|
||
user,
|
||
ProcurementActivityAction.ITEM_UPDATED,
|
||
object_kind=ProcurementActivityObjectKind.ITEM,
|
||
object_id=item.pk,
|
||
metadata={
|
||
"item_name": item.name,
|
||
"fields": list(changed_fields),
|
||
},
|
||
)
|
||
|
||
def _reset_request_view_states(self, procurement_request):
|
||
ProcurementRequestView.objects.filter(
|
||
request_id=procurement_request.pk,
|
||
is_viewed=True,
|
||
).update(
|
||
is_viewed=False,
|
||
viewed_at=None,
|
||
updated_at=timezone.now(),
|
||
)
|
||
|
||
def _reopen_completed_request(self, procurement_request, user):
|
||
if procurement_request.status != ProcurementStatus.COMPLETED:
|
||
return
|
||
|
||
procurement_request.status = (
|
||
ProcurementStatus.IN_PROGRESS
|
||
if procurement_request.executor_id
|
||
else ProcurementStatus.WAITING
|
||
)
|
||
procurement_request.completed_at = None
|
||
procurement_request._notification_actor = user
|
||
procurement_request.save(update_fields=["status", "completed_at", "updated_at"])
|
||
|
||
def perform_create(self, serializer):
|
||
"""Проверяем права при создании позиции.
|
||
|
||
Логика соответствует CanManageProcurementRequest:
|
||
- Админы/Staff могут добавлять в любые заявки в DRAFT
|
||
- Модельные права (change_procurementrequest) → любые заявки в DRAFT
|
||
- Автор заявки → свои заявки в DRAFT
|
||
- Начальник отдела → заявки своего отдела в DRAFT
|
||
"""
|
||
from rest_framework.exceptions import PermissionDenied
|
||
|
||
procurement_request = serializer.validated_data.get("request")
|
||
user = self.request.user
|
||
|
||
if procurement_request.status in [
|
||
ProcurementStatus.PENDING,
|
||
ProcurementStatus.CANCELLED,
|
||
ProcurementStatus.REJECTED,
|
||
]:
|
||
raise PermissionDenied(
|
||
"Нельзя добавлять позиции в заявку со статусом '{}'".format(
|
||
procurement_request.get_status_display()
|
||
)
|
||
)
|
||
|
||
if self._can_process_request_items(user, procurement_request):
|
||
serializer.save()
|
||
return
|
||
|
||
# Автор заявки
|
||
if procurement_request.requestor == user and procurement_request.is_editable:
|
||
serializer.save()
|
||
return
|
||
|
||
# Начальник отдела
|
||
if (
|
||
procurement_request.department.head == user
|
||
and procurement_request.is_editable
|
||
):
|
||
serializer.save()
|
||
return
|
||
|
||
# Нет прав
|
||
raise PermissionDenied("Вы не можете добавлять позиции в эту заявку")
|
||
|
||
def _check_item_edit_permission(self, item):
|
||
"""Проверка прав на редактирование позиции.
|
||
|
||
Вынесена в отдельный метод для переиспользования.
|
||
"""
|
||
from rest_framework.exceptions import PermissionDenied
|
||
|
||
user = self.request.user
|
||
procurement_request = item.request
|
||
|
||
if procurement_request.status in [
|
||
ProcurementStatus.PENDING,
|
||
ProcurementStatus.CANCELLED,
|
||
ProcurementStatus.REJECTED,
|
||
]:
|
||
raise PermissionDenied(
|
||
"Нельзя изменять позиции в заявке со статусом '{}'".format(
|
||
procurement_request.get_status_display()
|
||
)
|
||
)
|
||
|
||
if self._can_process_request_items(user, procurement_request):
|
||
return True
|
||
|
||
# Автор заявки
|
||
if procurement_request.requestor == user and procurement_request.is_editable:
|
||
return True
|
||
|
||
# Начальник отдела
|
||
if (
|
||
procurement_request.department.head == user
|
||
and procurement_request.is_editable
|
||
):
|
||
return True
|
||
|
||
# Нет прав
|
||
raise PermissionDenied("Вы не можете изменять позиции в этой заявке")
|
||
|
||
def _starts_execution(self, item, validated_data):
|
||
"""Проверить, меняются ли данные из блока исполнения позиции."""
|
||
return any(
|
||
field in validated_data and getattr(item, field) != validated_data[field]
|
||
for field in self.EXECUTION_FIELDS
|
||
)
|
||
|
||
def _start_request_for_execution(self, procurement_request):
|
||
"""Назначить исполнителя при первом заполнении исполнения позиции."""
|
||
user = self.request.user
|
||
if (
|
||
procurement_request.status
|
||
not in {ProcurementStatus.APPROVED, ProcurementStatus.WAITING}
|
||
or procurement_request.executor_id
|
||
or not self._can_process_request_items(user, procurement_request)
|
||
or not ProcurementApprovalResolver.user_can_start_work(
|
||
user,
|
||
procurement_request,
|
||
)
|
||
):
|
||
return
|
||
|
||
procurement_request.executor = user
|
||
procurement_request.status = ProcurementStatus.IN_PROGRESS
|
||
if procurement_request.started_at is None:
|
||
procurement_request.started_at = timezone.now()
|
||
procurement_request._notification_actor = user
|
||
procurement_request.save(
|
||
update_fields=[
|
||
"executor",
|
||
"status",
|
||
"started_at",
|
||
"updated_at",
|
||
]
|
||
)
|
||
|
||
@transaction.atomic
|
||
def perform_update(self, serializer):
|
||
"""Проверяем права при обновлении позиции."""
|
||
item = self.get_object()
|
||
procurement_request = ProcurementRequest.objects.select_for_update().get(
|
||
pk=item.request_id,
|
||
)
|
||
item.request = procurement_request
|
||
serializer.instance.request = procurement_request
|
||
self._check_item_edit_permission(item)
|
||
before = {
|
||
field: getattr(item, field) for field in self.NOTIFICATION_TRACKED_FIELDS
|
||
}
|
||
if self._starts_execution(item, serializer.validated_data):
|
||
self._start_request_for_execution(procurement_request)
|
||
procurement_request._notification_actor = self.request.user
|
||
updated_item = serializer.save()
|
||
after = {
|
||
field: getattr(updated_item, field)
|
||
for field in self.NOTIFICATION_TRACKED_FIELDS
|
||
}
|
||
changed_fields = [
|
||
field
|
||
for field in self.NOTIFICATION_TRACKED_FIELDS
|
||
if before[field] != after[field]
|
||
]
|
||
if changed_fields:
|
||
notify_item_updated(
|
||
updated_item,
|
||
actor=self.request.user,
|
||
changed_fields=changed_fields,
|
||
)
|
||
if (
|
||
before["execution_status"] != ProcurementItemExecutionStatus.DEFECTIVE
|
||
and after["execution_status"]
|
||
== ProcurementItemExecutionStatus.DEFECTIVE
|
||
):
|
||
self._reset_request_view_states(procurement_request)
|
||
notify_item_issue_reported(
|
||
updated_item,
|
||
actor=self.request.user,
|
||
)
|
||
self._record_item_update(
|
||
updated_item,
|
||
self.request.user,
|
||
changed_fields,
|
||
)
|
||
|
||
@action(detail=True, methods=["post"])
|
||
def report_issue(self, request, pk=None):
|
||
"""Отметить позицию как проблемную/бракованную."""
|
||
item = self.get_object()
|
||
procurement_request = item.request
|
||
user = request.user
|
||
|
||
error_response = self._validate_item_quick_action(
|
||
user,
|
||
procurement_request,
|
||
)
|
||
if error_response is not None:
|
||
return error_response
|
||
|
||
item.execution_status = ProcurementItemExecutionStatus.DEFECTIVE
|
||
item.save(update_fields=["execution_status"])
|
||
self._reset_request_view_states(procurement_request)
|
||
|
||
self._reopen_completed_request(procurement_request, user)
|
||
|
||
text = request.data.get("text", "")
|
||
if isinstance(text, str):
|
||
text = text.strip()
|
||
else:
|
||
text = ""
|
||
|
||
message = None
|
||
if text:
|
||
message = comments_helpers.create_comment(
|
||
obj=item,
|
||
author=user,
|
||
content=text,
|
||
)
|
||
notify_item_comment(item, message, actor=user)
|
||
|
||
notify_item_updated(
|
||
item,
|
||
actor=user,
|
||
changed_fields=["execution_status"],
|
||
)
|
||
self._record_item_update(item, user, ["execution_status"])
|
||
notify_item_issue_reported(item, actor=user)
|
||
|
||
item.refresh_from_db()
|
||
serializer = self.get_serializer(item)
|
||
return Response(serializer.data)
|
||
|
||
@action(detail=True, methods=["post"])
|
||
def cancel_received(self, request, pk=None):
|
||
"""Отменить подтверждение получения позиции."""
|
||
item = self.get_object()
|
||
procurement_request = item.request
|
||
user = request.user
|
||
|
||
error_response = self._validate_item_quick_action(
|
||
user,
|
||
procurement_request,
|
||
)
|
||
if error_response is not None:
|
||
return error_response
|
||
|
||
current_received_quantity = item.effective_received_quantity
|
||
if current_received_quantity <= 0:
|
||
return Response(
|
||
{"detail": "По позиции нет подтверждённого получения"},
|
||
status=status.HTTP_400_BAD_REQUEST,
|
||
)
|
||
|
||
target_received_quantity = 0
|
||
if "cancel_quantity" in request.data:
|
||
raw_cancel_quantity = request.data.get("cancel_quantity")
|
||
try:
|
||
cancel_quantity = int(raw_cancel_quantity)
|
||
except (TypeError, ValueError):
|
||
return Response(
|
||
{"cancel_quantity": "Укажите целое число отменяемых единиц"},
|
||
status=status.HTTP_400_BAD_REQUEST,
|
||
)
|
||
|
||
if cancel_quantity < 1:
|
||
return Response(
|
||
{"cancel_quantity": "Количество должно быть больше 0"},
|
||
status=status.HTTP_400_BAD_REQUEST,
|
||
)
|
||
if cancel_quantity > current_received_quantity:
|
||
return Response(
|
||
{
|
||
"cancel_quantity": (
|
||
"Не может быть больше текущего полученного количества"
|
||
)
|
||
},
|
||
status=status.HTTP_400_BAD_REQUEST,
|
||
)
|
||
target_received_quantity = current_received_quantity - cancel_quantity
|
||
|
||
effective_ordered_quantity = item.effective_ordered_quantity
|
||
item.received_quantity = target_received_quantity
|
||
if item.ordered_quantity is None and effective_ordered_quantity > 0:
|
||
item.ordered_quantity = effective_ordered_quantity
|
||
item.execution_status = self._workflow_status_from_quantities(item)
|
||
item.save(
|
||
update_fields=[
|
||
"execution_status",
|
||
"ordered_quantity",
|
||
"received_quantity",
|
||
]
|
||
)
|
||
self._reopen_completed_request(procurement_request, user)
|
||
notify_item_updated(
|
||
item,
|
||
actor=user,
|
||
changed_fields=[
|
||
"execution_status",
|
||
"ordered_quantity",
|
||
"received_quantity",
|
||
],
|
||
)
|
||
self._record_item_update(
|
||
item,
|
||
user,
|
||
["execution_status", "ordered_quantity", "received_quantity"],
|
||
)
|
||
|
||
item.refresh_from_db()
|
||
serializer = self.get_serializer(item)
|
||
return Response(serializer.data)
|
||
|
||
@action(detail=True, methods=["post"])
|
||
def cancel_issue(self, request, pk=None):
|
||
"""Снять отметку брака/перезаказа с позиции."""
|
||
item = self.get_object()
|
||
procurement_request = item.request
|
||
user = request.user
|
||
|
||
error_response = self._validate_item_quick_action(
|
||
user,
|
||
procurement_request,
|
||
)
|
||
if error_response is not None:
|
||
return error_response
|
||
|
||
if item.execution_status != ProcurementItemExecutionStatus.DEFECTIVE:
|
||
return Response(
|
||
{"detail": "Позиция не отмечена как брак"},
|
||
status=status.HTTP_400_BAD_REQUEST,
|
||
)
|
||
|
||
item.execution_status = self._workflow_status_from_quantities(item)
|
||
item.save(update_fields=["execution_status"])
|
||
notify_item_updated(
|
||
item,
|
||
actor=user,
|
||
changed_fields=["execution_status"],
|
||
)
|
||
self._record_item_update(item, user, ["execution_status"])
|
||
|
||
item.refresh_from_db()
|
||
serializer = self.get_serializer(item)
|
||
return Response(serializer.data)
|
||
|
||
@action(detail=True, methods=["post"])
|
||
def confirm_ordered(self, request, pk=None):
|
||
"""Подтвердить заказ дополнительного количества позиции."""
|
||
item = self.get_object()
|
||
procurement_request = item.request
|
||
user = request.user
|
||
|
||
error_response = self._validate_item_quick_action(
|
||
user,
|
||
procurement_request,
|
||
)
|
||
if error_response is not None:
|
||
return error_response
|
||
|
||
current_ordered_quantity = item.effective_ordered_quantity
|
||
raw_ordered_quantity = request.data.get("ordered_quantity")
|
||
try:
|
||
target_ordered_quantity = int(raw_ordered_quantity)
|
||
except (TypeError, ValueError):
|
||
return Response(
|
||
{"ordered_quantity": "Укажите целое число заказанных единиц"},
|
||
status=status.HTTP_400_BAD_REQUEST,
|
||
)
|
||
|
||
if target_ordered_quantity <= current_ordered_quantity:
|
||
return Response(
|
||
{
|
||
"ordered_quantity": (
|
||
"Должно быть больше текущего заказанного количества"
|
||
)
|
||
},
|
||
status=status.HTTP_400_BAD_REQUEST,
|
||
)
|
||
|
||
expected_delivery_dates = list(item.expected_delivery_dates or [])
|
||
delivery_date_added = False
|
||
raw_delivery_date = request.data.get("expected_delivery_date")
|
||
if raw_delivery_date not in (None, ""):
|
||
try:
|
||
delivery_date = date.fromisoformat(str(raw_delivery_date))
|
||
except ValueError:
|
||
return Response(
|
||
{"expected_delivery_date": "Укажите корректную дату"},
|
||
status=status.HTTP_400_BAD_REQUEST,
|
||
)
|
||
delivery_date_value = delivery_date.isoformat()
|
||
if delivery_date_value not in expected_delivery_dates:
|
||
expected_delivery_dates.append(delivery_date_value)
|
||
delivery_date_added = True
|
||
|
||
item.ordered_quantity = target_ordered_quantity
|
||
item.expected_delivery_dates = expected_delivery_dates
|
||
item.execution_status = self._workflow_status_from_quantities(item)
|
||
changed_fields = ["execution_status", "ordered_quantity"]
|
||
update_fields = ["execution_status", "ordered_quantity"]
|
||
if delivery_date_added:
|
||
changed_fields.append("expected_delivery_dates")
|
||
update_fields.append("expected_delivery_dates")
|
||
item.save(update_fields=update_fields)
|
||
self._reopen_completed_request(procurement_request, user)
|
||
notify_item_updated(
|
||
item,
|
||
actor=user,
|
||
changed_fields=changed_fields,
|
||
)
|
||
self._record_item_update(item, user, changed_fields)
|
||
|
||
item.refresh_from_db()
|
||
serializer = self.get_serializer(item)
|
||
return Response(serializer.data)
|
||
|
||
@action(detail=True, methods=["post"])
|
||
def confirm_received(self, request, pk=None):
|
||
"""Подтвердить получение позиции."""
|
||
item = self.get_object()
|
||
procurement_request = item.request
|
||
user = request.user
|
||
|
||
error_response = self._validate_item_quick_action(
|
||
user,
|
||
procurement_request,
|
||
)
|
||
if error_response is not None:
|
||
return error_response
|
||
|
||
current_received_quantity = item.effective_received_quantity
|
||
target_received_quantity = item.fulfillment_target_quantity
|
||
|
||
if "received_quantity" in request.data:
|
||
raw_received_quantity = request.data.get("received_quantity")
|
||
try:
|
||
target_received_quantity = int(raw_received_quantity)
|
||
except (TypeError, ValueError):
|
||
return Response(
|
||
{"received_quantity": "Укажите целое число полученных единиц"},
|
||
status=status.HTTP_400_BAD_REQUEST,
|
||
)
|
||
|
||
if target_received_quantity < 1:
|
||
return Response(
|
||
{"received_quantity": "Количество должно быть больше 0"},
|
||
status=status.HTTP_400_BAD_REQUEST,
|
||
)
|
||
if target_received_quantity <= current_received_quantity:
|
||
return Response(
|
||
{
|
||
"received_quantity": (
|
||
"Должно быть больше текущего полученного количества"
|
||
)
|
||
},
|
||
status=status.HTTP_400_BAD_REQUEST,
|
||
)
|
||
|
||
item.received_quantity = target_received_quantity
|
||
item.ordered_quantity = max(
|
||
item.effective_ordered_quantity,
|
||
target_received_quantity,
|
||
)
|
||
item.execution_status = self._workflow_status_from_quantities(item)
|
||
item.save(
|
||
update_fields=[
|
||
"execution_status",
|
||
"ordered_quantity",
|
||
"received_quantity",
|
||
]
|
||
)
|
||
notify_item_updated(
|
||
item,
|
||
actor=user,
|
||
changed_fields=[
|
||
"execution_status",
|
||
"ordered_quantity",
|
||
"received_quantity",
|
||
],
|
||
)
|
||
self._record_item_update(
|
||
item,
|
||
user,
|
||
["execution_status", "ordered_quantity", "received_quantity"],
|
||
)
|
||
|
||
item.refresh_from_db()
|
||
serializer = self.get_serializer(item)
|
||
return Response(serializer.data)
|
||
|
||
def perform_destroy(self, instance):
|
||
"""Проверяем права при удалении позиции."""
|
||
self._check_item_edit_permission(instance)
|
||
instance.delete()
|