Огромная замена логики
All checks were successful
Deploy MES Core / deploy (push) Successful in 11s

This commit is contained in:
2026-04-06 08:06:37 +03:00
parent 0e8497ab1f
commit e88b861f68
48 changed files with 3833 additions and 175 deletions

View File

@@ -0,0 +1,5 @@
"""
Сервисный слой приложения warehouse.
Здесь живут операции складского учёта, требующие транзакций и блокировок.
"""

View 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'])