from django.db import transaction from django.utils import timezone from warehouse.models import StockItem, TransferLine, TransferRecord @transaction.atomic def receive_transfer(transfer_id: int, receiver_id: int) -> None: """ Строгое перемещение: принять TransferRecord. Логика: - если уже received -> идемпотентно выходим - блокируем TransferRecord - блокируем связанные StockItem - обновляем location на to_location - ставим receiver/received_at/status """ tr = ( TransferRecord.objects.select_for_update() .select_related('from_location', 'to_location') .get(pk=transfer_id) ) if tr.is_applied: return lines = list(TransferLine.objects.filter(transfer=tr).select_related('stock_item', 'stock_item__location', 'stock_item__material', 'stock_item__entity')) if not lines: raise RuntimeError('В перемещении нет строк.') for ln in lines: if float(ln.quantity) <= 0: continue src = StockItem.objects.select_for_update().get(pk=ln.stock_item_id) if src.location_id != tr.from_location_id: raise RuntimeError('Единица на складе находится не на складе-источнике.') if float(ln.quantity) > float(src.quantity): raise RuntimeError('Недостаточно количества в источнике для перемещения.') if src.unique_id and float(ln.quantity) != float(src.quantity): raise RuntimeError('Нельзя частично перемещать позицию с ID/маркировкой.') if float(ln.quantity) == float(src.quantity): src.location_id = tr.to_location_id src.created_at = timezone.now() src.save(update_fields=['location', 'created_at']) continue src.quantity = float(src.quantity) - float(ln.quantity) src.save(update_fields=['quantity']) # ВАЖНО: не объединяем с существующими позициями на складе-получателе, # чтобы сохранялась история поступлений/дат/партий. StockItem.objects.create( material=src.material, entity=src.entity, deal=src.deal, location_id=tr.to_location_id, quantity=float(ln.quantity), is_remnant=src.is_remnant, is_customer_supplied=src.is_customer_supplied, current_length=src.current_length, current_width=src.current_width, ) tr.status = 'received' tr.receiver_id = receiver_id tr.received_at = timezone.now() tr.is_applied = True tr.save(update_fields=['status', 'receiver_id', 'received_at', 'is_applied'])