This commit is contained in:
5
warehouse/services/__init__.py
Normal file
5
warehouse/services/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""
|
||||
Сервисный слой приложения warehouse.
|
||||
|
||||
Здесь живут операции складского учёта, требующие транзакций и блокировок.
|
||||
"""
|
||||
71
warehouse/services/transfers.py
Normal file
71
warehouse/services/transfers.py
Normal file
@@ -0,0 +1,71 @@
|
||||
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,
|
||||
location_id=tr.to_location_id,
|
||||
quantity=float(ln.quantity),
|
||||
is_remnant=src.is_remnant,
|
||||
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'])
|
||||
Reference in New Issue
Block a user