Files
octopus/myproject/inventory/views/transfer.py
2025-11-04 11:00:05 +03:00

238 lines
9.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.
# -*- coding: utf-8 -*-
"""
Transfer (Перемещение товара между складами) views
GROUP 2: MEDIUM PRIORITY
"""
import json
from decimal import Decimal
from django.views.generic import ListView, CreateView, UpdateView, DeleteView, DetailView
from django.views import View
from django.urls import reverse_lazy
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib import messages
from django.http import JsonResponse
from django.db import transaction
from django.shortcuts import redirect, render
from ..models import TransferBatch, TransferItem, Stock
from ..forms import TransferBulkForm
from inventory.utils.document_generator import generate_transfer_document_number
from inventory.services.batch_manager import StockBatchManager
from products.models import Product
class TransferListView(LoginRequiredMixin, ListView):
"""
View для просмотра списка документов перемещений товаров.
"""
model = TransferBatch
template_name = 'inventory/transfer/transfer_list.html'
context_object_name = 'transfers'
paginate_by = 20
def get_queryset(self):
return TransferBatch.objects.select_related(
'from_warehouse', 'to_warehouse'
).order_by('-created_at')
# ============================================================================
# VIEWS ДЛЯ ПЕРЕМЕЩЕНИЯ ТОВАРОВ (TransferBatch + TransferItem)
# ============================================================================
class TransferBulkCreateView(LoginRequiredMixin, View):
"""
View для создания документа перемещения товаров между складами с FIFO логикой.
Один документ может содержать несколько товаров.
"""
template_name = 'inventory/transfer/transfer_bulk_form.html'
def get(self, request):
from products.models import Product
form = TransferBulkForm()
products = Product.objects.filter(is_active=True).values('id', 'name', 'sku').order_by('name')
return render(request, self.template_name, {
'form': form,
'products': products
})
def post(self, request):
form = TransferBulkForm(request.POST)
if not form.is_valid():
messages.error(request, 'Ошибка при заполнении формы')
return render(request, self.template_name, {'form': form}, status=400)
# Получаем данные из формы
from_warehouse = form.cleaned_data.get('from_warehouse')
to_warehouse = form.cleaned_data.get('to_warehouse')
notes = form.cleaned_data.get('notes', '')
# Парсим JSON с товарами
products_json = request.POST.get('products_json', '[]')
try:
products_data = json.loads(products_json)
except json.JSONDecodeError:
messages.error(request, 'Ошибка при парсинге товаров. Пожалуйста, проверьте данные.')
return render(request, self.template_name, {'form': form}, status=400)
# Проверяем что товары есть
if not products_data:
messages.error(request, 'Необходимо добавить хотя бы один товар для перемещения.')
return render(request, self.template_name, {'form': form}, status=400)
# Валидируем товары
products = []
for item in products_data:
product_id = item.get('product_id')
quantity = Decimal(str(item.get('quantity', 0)))
if quantity <= 0:
messages.error(request, f'Количество товара должно быть больше нуля')
return render(request, self.template_name, {'form': form}, status=400)
try:
product = Product.objects.get(id=product_id, is_active=True)
products.append((product, quantity))
except Product.DoesNotExist:
messages.error(request, f'Товар с ID {product_id} не найден')
return render(request, self.template_name, {'form': form}, status=400)
# Начинаем транзакцию
try:
with transaction.atomic():
# 1. Создаем документ TransferBatch
transfer_batch = TransferBatch.objects.create(
from_warehouse=from_warehouse,
to_warehouse=to_warehouse,
document_number=generate_transfer_document_number(),
notes=notes
)
# 2. Для каждого товара выполняем FIFO перемещение
for product, quantity in products:
try:
# Получаем список распределений по FIFO
transfers = StockBatchManager.transfer_product_by_fifo(
product=product,
from_warehouse=from_warehouse,
to_warehouse=to_warehouse,
quantity=quantity
)
# Создаем TransferItem для каждого использованного batch
for source_batch, qty_transferred, new_batch in transfers:
TransferItem.objects.create(
transfer_batch=transfer_batch,
product=product,
batch=source_batch,
quantity=qty_transferred,
new_batch=new_batch
)
except ValueError as e:
messages.error(request, f'Ошибка при перемещении товара "{product.name}": {str(e)}')
raise # Откатываем транзакцию
# 3. Успешно создали документ
messages.success(
request,
f'Документ перемещения {transfer_batch.document_number} успешно создан. '
f'Перемещено {len(products)} видов товаров.'
)
return redirect('inventory:transfer-detail', pk=transfer_batch.id)
except Exception as e:
messages.error(request, f'Ошибка при создании документа перемещения: {str(e)}')
return render(request, self.template_name, {'form': form}, status=400)
class TransferDetailView(LoginRequiredMixin, DetailView):
"""
View для просмотра деталей документа перемещения.
"""
model = TransferBatch
template_name = 'inventory/transfer/transfer_detail.html'
context_object_name = 'transfer_batch'
def get_queryset(self):
return TransferBatch.objects.select_related(
'from_warehouse', 'to_warehouse'
).prefetch_related(
'items__product', 'items__batch', 'items__new_batch'
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
transfer_batch = self.object
# Собираем статистику по документу
items = transfer_batch.items.all()
total_items = items.count()
total_qty = sum(Decimal(str(item.quantity)) for item in items)
context['total_items'] = total_items
context['total_qty'] = total_qty
context['items'] = items
return context
class TransferDeleteView(LoginRequiredMixin, DeleteView):
"""
View для удаления документа перемещения.
"""
model = TransferBatch
template_name = 'inventory/transfer/transfer_confirm_delete.html'
success_url = reverse_lazy('inventory:transfer-list')
def form_valid(self, form):
transfer_batch = self.get_object()
messages.success(self.request, f'Документ перемещения {transfer_batch.document_number} удалён.')
return super().form_valid(form)
class GetProductStockView(LoginRequiredMixin, View):
"""
API endpoint для получения доступного количества товара на конкретном складе.
GET параметры: product_id, warehouse_id
Возвращает JSON: {"quantity": "100.000", "warehouse_name": "Основной склад"}
"""
def get(self, request):
product_id = request.GET.get('product_id')
warehouse_id = request.GET.get('warehouse_id')
if not product_id or not warehouse_id:
return JsonResponse({
'error': 'Missing required parameters: product_id, warehouse_id'
}, status=400)
try:
product_id = int(product_id)
warehouse_id = int(warehouse_id)
except ValueError:
return JsonResponse({
'error': 'Invalid parameter values'
}, status=400)
try:
stock = Stock.objects.get(product_id=product_id, warehouse_id=warehouse_id)
return JsonResponse({
'quantity': str(stock.quantity_available),
'warehouse_name': stock.warehouse.name,
'success': True
})
except Stock.DoesNotExist:
return JsonResponse({
'quantity': '0.000',
'warehouse_name': '',
'success': True
})
except Exception as e:
import traceback
traceback.print_exc()
return JsonResponse({
'error': str(e),
'success': False
}, status=500)