78 lines
3.0 KiB
Python
78 lines
3.0 KiB
Python
from collections.abc import Mapping
|
||
|
||
from django.db import IntegrityError, transaction
|
||
from django.utils import timezone
|
||
|
||
from procurement.constants import EquipmentStatus, ProcurementStatus
|
||
from procurement.models import Equipment, ProcurementItem
|
||
|
||
|
||
class EquipmentCreationError(Exception):
|
||
"""Expected business-rule failure while creating equipment from an item."""
|
||
|
||
|
||
class ProcurementItemUnavailable(EquipmentCreationError):
|
||
"""The procurement item disappeared before the command acquired its lock."""
|
||
|
||
|
||
@transaction.atomic
|
||
def create_equipment_from_procurement_item(
|
||
*,
|
||
item_id: int,
|
||
attributes: Mapping[str, object],
|
||
) -> Equipment:
|
||
"""Create and link equipment as one concurrency-safe transaction."""
|
||
try:
|
||
item = (
|
||
ProcurementItem.objects.select_for_update()
|
||
.select_related("request", "equipment")
|
||
.get(pk=item_id)
|
||
)
|
||
except ProcurementItem.DoesNotExist as exc:
|
||
raise ProcurementItemUnavailable("Позиция закупки не найдена.") from exc
|
||
|
||
if item.request.status != ProcurementStatus.COMPLETED:
|
||
raise EquipmentCreationError(
|
||
"Оборудование можно создать только из завершённой заявки."
|
||
)
|
||
if item.equipment_id is not None:
|
||
raise EquipmentCreationError("Оборудование для этой позиции уже создано.")
|
||
|
||
purchase_cost = item.actual_unit_price or item.estimated_unit_price
|
||
if purchase_cost is None:
|
||
raise EquipmentCreationError(
|
||
"Для создания оборудования укажите фактическую или ориентировочную "
|
||
"цену позиции."
|
||
)
|
||
|
||
inventory_number = str(attributes["inventory_number"])
|
||
if Equipment.objects.filter(inventory_number=inventory_number).exists():
|
||
raise EquipmentCreationError(
|
||
f'Инвентарный номер "{inventory_number}" уже используется.'
|
||
)
|
||
|
||
try:
|
||
equipment = Equipment.objects.create(
|
||
name=item.name,
|
||
inventory_number=inventory_number,
|
||
serial_number=attributes.get("serial_number", ""),
|
||
category=attributes["category"],
|
||
department=attributes["department"],
|
||
status=EquipmentStatus.AVAILABLE,
|
||
responsible_person=attributes.get("responsible_person"),
|
||
location=attributes.get("location", ""),
|
||
purchase_date=timezone.localdate(),
|
||
warranty_until=attributes.get("warranty_until"),
|
||
purchase_cost=purchase_cost,
|
||
notes=item.description,
|
||
)
|
||
except IntegrityError as exc:
|
||
raise EquipmentCreationError(
|
||
"Не удалось создать оборудование: проверьте уникальность "
|
||
"инвентарного номера."
|
||
) from exc
|
||
|
||
item.equipment = equipment
|
||
item.save(update_fields=["equipment"])
|
||
return equipment
|