151 lines
4.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Application use cases for creating and editing procurement requests."""
from collections.abc import Mapping, Sequence
from typing import Any
from django.core.exceptions import ValidationError
from django.db import transaction
from django.utils import timezone
from communications import comments_helpers
from procurement.constants import ApprovalStatus, ProcurementStatus
from procurement.models import (
Approval,
ProcurementItem,
ProcurementRequest,
ProcurementSettings,
)
class ProcurementRequestUpdateError(Exception):
"""Expected conflict while applying a procurement request update."""
class ProcurementRequestItemNotFound(ProcurementRequestUpdateError):
"""An update referenced an item that does not belong to the request."""
def _create_initial_comment(*, item, author, content: str) -> None:
if content and author and author.is_authenticated:
comments_helpers.create_comment(
obj=item,
author=author,
content=content,
)
@transaction.atomic
def create_procurement_request(
*,
requestor,
attributes: Mapping[str, Any],
items: Sequence[Mapping[str, Any]],
) -> ProcurementRequest:
"""Create a request and all initial items as one atomic operation."""
request_attributes = dict(attributes)
if request_attributes.get("processing_department"):
request_attributes["status"] = ProcurementStatus.WAITING
procurement_request = ProcurementRequest(
requestor=requestor,
**request_attributes,
)
procurement_request._notification_actor = requestor
if procurement_request.request_type == ProcurementRequest.RequestType.SUBSCRIPTION:
procurement_request._suppress_created_procurement_notifications = True
procurement_request.save()
for raw_item in items:
item_attributes = dict(raw_item)
initial_comment = item_attributes.pop("initial_comment", "").strip()
item = ProcurementItem.objects.create(
request=procurement_request,
**item_attributes,
)
_create_initial_comment(
item=item,
author=requestor,
content=initial_comment,
)
if procurement_request.request_type == ProcurementRequest.RequestType.SUBSCRIPTION:
approver_id = ProcurementSettings.get_solo().subscription_approver_id
if not approver_id:
raise ValidationError(
"В настройках закупок не выбран согласующий заявок на подписки."
)
Approval.objects.create(
request=procurement_request,
approver_id=approver_id,
priority=0,
step_name="Согласование подписки",
status=ApprovalStatus.PENDING,
)
procurement_request.status = ProcurementStatus.PENDING
procurement_request.submitted_at = timezone.now()
procurement_request.save(
update_fields=["status", "submitted_at", "updated_at"],
)
return procurement_request
@transaction.atomic
def update_procurement_request_with_items(
*,
request_id: int,
attributes: Mapping[str, Any],
items: Sequence[Mapping[str, Any]],
author,
) -> ProcurementRequest:
"""Update request fields and synchronize its item collection atomically."""
procurement_request = ProcurementRequest.objects.select_for_update().get(
pk=request_id,
)
projects = attributes.pop("projects", None)
existing_items = {
item.id: item for item in procurement_request.items.select_for_update().all()
}
for field, value in attributes.items():
setattr(procurement_request, field, value)
procurement_request.save()
if projects is not None:
procurement_request.projects.set(projects)
kept_item_ids: set[int] = set()
for raw_item in items:
item_attributes = dict(raw_item)
item_id = item_attributes.pop("id", None)
initial_comment = item_attributes.pop("initial_comment", "").strip()
if item_id is None:
item = ProcurementItem.objects.create(
request=procurement_request,
**item_attributes,
)
else:
item = existing_items.get(item_id)
if item is None:
raise ProcurementRequestItemNotFound(item_id)
for field, value in item_attributes.items():
setattr(item, field, value)
item.save()
kept_item_ids.add(item.id)
_create_initial_comment(
item=item,
author=author,
content=initial_comment,
)
obsolete_item_ids = set(existing_items) - kept_item_ids
if obsolete_item_ids:
ProcurementItem.objects.filter(
request=procurement_request,
id__in=obsolete_item_ids,
).delete()
procurement_request.recalculate_fulfillment_status(save=True)
return procurement_request