Улучшения инвентаризации: автоматическое проведение документов, оптимизация запросов и улучшения UI
- Автоматическое проведение документов списания и оприходования после завершения инвентаризации - Оптимизация SQL-запросов: устранение N+1, bulk-операции для Stock, агрегация для StockBatch и Reservation - Изменение формулы расчета разницы: (quantity_fact + quantity_reserved) - quantity_available - Переименование поля 'По факту' в 'Подсчитано (факт, свободные)' - Добавлены столбцы 'В резервах' и 'Всего на складе' в таблицу инвентаризации - Перемещение столбца 'В системе (свободно)' после 'В резервах' с визуальным выделением - Центральное выравнивание значений в столбцах таблицы - Автоматическое выделение текста при фокусе на поле ввода количества - Исправление форматирования разницы (убраны лишние нули) - Изменение статуса 'Не обработана' на 'Не проведено' - Добавление номера документа для инвентаризаций (INV-XXXXXX) - Отображение всех типов списаний в debug-странице (WriteOff, WriteOffDocument, WriteOffDocumentItem) - Улучшение отображения документов в детальном просмотре инвентаризации с возможностью перехода к ним
This commit is contained in:
@@ -24,7 +24,8 @@ from .batch import IncomingBatchListView, IncomingBatchDetailView, StockBatchLis
|
||||
from .sale import SaleListView, SaleCreateView, SaleUpdateView, SaleDeleteView, SaleDetailView
|
||||
from .inventory_ops import (
|
||||
InventoryListView, InventoryCreateView, InventoryDetailView,
|
||||
InventoryLineCreateBulkView
|
||||
InventoryLineCreateBulkView, InventoryLineAddView, InventoryLineUpdateView,
|
||||
InventoryLineDeleteView, InventoryCompleteView
|
||||
)
|
||||
from .writeoff import WriteOffListView, WriteOffCreateView, WriteOffUpdateView, WriteOffDeleteView
|
||||
from .writeoff_document import (
|
||||
@@ -65,6 +66,8 @@ __all__ = [
|
||||
'SaleListView', 'SaleCreateView', 'SaleUpdateView', 'SaleDeleteView', 'SaleDetailView',
|
||||
# Inventory
|
||||
'InventoryListView', 'InventoryCreateView', 'InventoryDetailView', 'InventoryLineCreateBulkView',
|
||||
'InventoryLineAddView', 'InventoryLineUpdateView', 'InventoryLineDeleteView',
|
||||
'InventoryCompleteView',
|
||||
# WriteOff
|
||||
'WriteOffListView', 'WriteOffCreateView', 'WriteOffUpdateView', 'WriteOffDeleteView',
|
||||
# WriteOffDocument
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
from django.contrib.auth.decorators import login_required, user_passes_test
|
||||
from django.shortcuts import render
|
||||
from django.db.models import Q, Sum, Count
|
||||
from inventory.models import StockBatch, Stock, Reservation, Sale, SaleBatchAllocation
|
||||
from inventory.models import StockBatch, Stock, Reservation, Sale, SaleBatchAllocation, WriteOff, WriteOffDocument, WriteOffDocumentItem
|
||||
from orders.models import Order
|
||||
from products.models import Product
|
||||
from inventory.models import Warehouse
|
||||
@@ -38,6 +38,12 @@ def debug_inventory_page(request):
|
||||
allocations = SaleBatchAllocation.objects.select_related(
|
||||
'sale__product', 'batch'
|
||||
).order_by('-id')
|
||||
# Все списания: из продаж (WriteOff) и из документов списания (WriteOffDocumentItem)
|
||||
writeoffs = WriteOff.objects.select_related('batch__product', 'batch__warehouse').order_by('-date')
|
||||
writeoff_documents = WriteOffDocument.objects.select_related('warehouse').order_by('-date')
|
||||
writeoff_document_items = WriteOffDocumentItem.objects.select_related(
|
||||
'product', 'document__warehouse'
|
||||
).order_by('-id')
|
||||
orders = Order.objects.prefetch_related('items').order_by('-created_at')
|
||||
|
||||
# Применяем фильтры
|
||||
@@ -48,6 +54,8 @@ def debug_inventory_page(request):
|
||||
reservations = reservations.filter(product_id=product_id)
|
||||
sales = sales.filter(product_id=product_id)
|
||||
allocations = allocations.filter(sale__product_id=product_id)
|
||||
writeoffs = writeoffs.filter(batch__product_id=product_id)
|
||||
writeoff_document_items = writeoff_document_items.filter(product_id=product_id)
|
||||
orders = orders.filter(items__product_id=product_id).distinct()
|
||||
else:
|
||||
product = None
|
||||
@@ -85,6 +93,9 @@ def debug_inventory_page(request):
|
||||
stocks = stocks.filter(warehouse_id=warehouse_id)
|
||||
reservations = reservations.filter(warehouse_id=warehouse_id)
|
||||
sales = sales.filter(warehouse_id=warehouse_id)
|
||||
writeoffs = writeoffs.filter(batch__warehouse_id=warehouse_id)
|
||||
writeoff_documents = writeoff_documents.filter(warehouse_id=warehouse_id)
|
||||
writeoff_document_items = writeoff_document_items.filter(document__warehouse_id=warehouse_id)
|
||||
else:
|
||||
warehouse = None
|
||||
|
||||
@@ -94,6 +105,9 @@ def debug_inventory_page(request):
|
||||
reservations = reservations[:100]
|
||||
sales = sales[:100]
|
||||
allocations = allocations[:100]
|
||||
writeoffs = writeoffs[:100]
|
||||
writeoff_documents = writeoff_documents[:50]
|
||||
writeoff_document_items = writeoff_document_items[:100]
|
||||
orders = orders[:50]
|
||||
|
||||
# Списки для фильтров
|
||||
@@ -106,6 +120,9 @@ def debug_inventory_page(request):
|
||||
'reservations': reservations,
|
||||
'sales': sales,
|
||||
'allocations': allocations,
|
||||
'writeoffs': writeoffs,
|
||||
'writeoff_documents': writeoff_documents,
|
||||
'writeoff_document_items': writeoff_document_items,
|
||||
'orders': orders,
|
||||
'products': products,
|
||||
'warehouses': warehouses,
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.shortcuts import render, redirect
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.views.generic import ListView, CreateView, DetailView, View, FormView
|
||||
from django.urls import reverse_lazy
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib import messages
|
||||
from django.http import HttpResponseRedirect
|
||||
from ..models import Inventory, InventoryLine
|
||||
from django.http import HttpResponseRedirect, JsonResponse
|
||||
from django.db import transaction
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from django.utils.decorators import method_decorator
|
||||
from decimal import Decimal
|
||||
import json
|
||||
|
||||
from ..models import Inventory, InventoryLine, Stock
|
||||
from ..forms import InventoryForm, InventoryLineForm
|
||||
from ..services.inventory_processor import InventoryProcessor
|
||||
from ..services.writeoff_document_service import WriteOffDocumentService
|
||||
from ..services.incoming_document_service import IncomingDocumentService
|
||||
from django.core.exceptions import ValidationError
|
||||
from products.models import Product, ProductCategory, ProductTag
|
||||
|
||||
|
||||
class InventoryListView(LoginRequiredMixin, ListView):
|
||||
@@ -44,7 +55,10 @@ class InventoryCreateView(LoginRequiredMixin, CreateView):
|
||||
success_url = reverse_lazy('inventory:inventory-list')
|
||||
|
||||
def form_valid(self, form):
|
||||
from inventory.utils.document_generator import generate_inventory_document_number
|
||||
|
||||
form.instance.status = 'processing'
|
||||
form.instance.document_number = generate_inventory_document_number()
|
||||
messages.success(
|
||||
self.request,
|
||||
f'Инвентаризация склада "{form.instance.warehouse.name}" начата.'
|
||||
@@ -60,13 +74,135 @@ class InventoryDetailView(LoginRequiredMixin, DetailView):
|
||||
model = Inventory
|
||||
template_name = 'inventory/inventory/inventory_detail.html'
|
||||
context_object_name = 'inventory'
|
||||
|
||||
def get_queryset(self):
|
||||
"""Оптимизация: предзагружаем warehouse"""
|
||||
return Inventory.objects.select_related('warehouse')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
# Получаем все строки этой инвентаризации
|
||||
context['lines'] = InventoryLine.objects.filter(
|
||||
from inventory.models import Stock, StockBatch, Reservation
|
||||
from django.db.models import Sum, Q
|
||||
from decimal import Decimal
|
||||
|
||||
# Получаем все строки этой инвентаризации с оптимизацией
|
||||
lines = InventoryLine.objects.filter(
|
||||
inventory=self.object
|
||||
).select_related('product')
|
||||
).select_related('product').order_by('product__name')
|
||||
|
||||
if not lines.exists():
|
||||
context['lines'] = []
|
||||
context['categories'] = ProductCategory.objects.filter(is_active=True).order_by('name')
|
||||
context['tags'] = ProductTag.objects.filter(is_active=True).order_by('name')
|
||||
if self.object.status == 'completed':
|
||||
context['writeoff_document'] = self.object.writeoff_documents.filter(status='draft').first()
|
||||
context['incoming_document'] = self.object.incoming_documents.filter(status='draft').first()
|
||||
return context
|
||||
|
||||
# Получаем все product_id из строк
|
||||
product_ids = [line.product_id for line in lines]
|
||||
warehouse = self.object.warehouse
|
||||
|
||||
# Получаем или создаем все Stock объекты одним запросом
|
||||
stocks = Stock.objects.filter(
|
||||
product_id__in=product_ids,
|
||||
warehouse=warehouse
|
||||
)
|
||||
existing_stocks = {stock.product_id: stock for stock in stocks}
|
||||
|
||||
# Создаем недостающие Stock объекты
|
||||
missing_product_ids = set(product_ids) - set(existing_stocks.keys())
|
||||
if missing_product_ids:
|
||||
new_stocks = [
|
||||
Stock(product_id=pid, warehouse=warehouse)
|
||||
for pid in missing_product_ids
|
||||
]
|
||||
Stock.objects.bulk_create(new_stocks)
|
||||
# Перезагружаем все Stock объекты
|
||||
stocks = Stock.objects.filter(
|
||||
product_id__in=product_ids,
|
||||
warehouse=warehouse
|
||||
)
|
||||
existing_stocks = {stock.product_id: stock for stock in stocks}
|
||||
|
||||
# Bulk запрос для получения всех StockBatch данных
|
||||
stock_batches_qs = StockBatch.objects.filter(
|
||||
product_id__in=product_ids,
|
||||
warehouse=warehouse,
|
||||
is_active=True
|
||||
).values('product_id').annotate(
|
||||
total_qty=Sum('quantity')
|
||||
)
|
||||
stock_batches_dict = {
|
||||
item['product_id']: item['total_qty'] or Decimal('0')
|
||||
for item in stock_batches_qs
|
||||
}
|
||||
|
||||
# Bulk запрос для получения всех Reservation данных
|
||||
reservations_qs = Reservation.objects.filter(
|
||||
product_id__in=product_ids,
|
||||
warehouse=warehouse,
|
||||
status='reserved'
|
||||
).values('product_id').annotate(
|
||||
total_reserved=Sum('quantity')
|
||||
)
|
||||
reservations_dict = {
|
||||
item['product_id']: item['total_reserved'] or Decimal('0')
|
||||
for item in reservations_qs
|
||||
}
|
||||
|
||||
# Обновляем все Stock объекты bulk update
|
||||
stocks_to_update = []
|
||||
for stock in stocks:
|
||||
product_id = stock.product_id
|
||||
stock.quantity_available = stock_batches_dict.get(product_id, Decimal('0'))
|
||||
stock.quantity_reserved = reservations_dict.get(product_id, Decimal('0'))
|
||||
stocks_to_update.append(stock)
|
||||
|
||||
if stocks_to_update:
|
||||
Stock.objects.bulk_update(
|
||||
stocks_to_update,
|
||||
['quantity_available', 'quantity_reserved', 'updated_at'],
|
||||
batch_size=100
|
||||
)
|
||||
|
||||
# Добавляем quantity_reserved и обновляем quantity_system для каждой строки
|
||||
lines_with_reserved = []
|
||||
for line in lines:
|
||||
stock = existing_stocks.get(line.product_id)
|
||||
if not stock:
|
||||
# Fallback на старый способ, если что-то пошло не так
|
||||
stock, _ = Stock.objects.get_or_create(
|
||||
product=line.product,
|
||||
warehouse=warehouse
|
||||
)
|
||||
stock.refresh_from_batches()
|
||||
|
||||
# Для незавершенных инвентаризаций обновляем quantity_system динамически
|
||||
if self.object.status != 'completed':
|
||||
# Используем актуальное свободное количество из Stock
|
||||
line.quantity_system = stock.quantity_free
|
||||
|
||||
# Добавляем quantity_reserved и quantity_available
|
||||
line.quantity_reserved = stock.quantity_reserved
|
||||
line.quantity_available = stock.quantity_available
|
||||
|
||||
# Пересчитываем разницу по новой формуле: (quantity_fact + quantity_reserved) - quantity_available
|
||||
line.difference = (line.quantity_fact + line.quantity_reserved) - line.quantity_available
|
||||
|
||||
lines_with_reserved.append(line)
|
||||
|
||||
context['lines'] = lines_with_reserved
|
||||
|
||||
# Получаем категории и теги для компонента поиска товаров
|
||||
context['categories'] = ProductCategory.objects.filter(is_active=True).order_by('name')
|
||||
context['tags'] = ProductTag.objects.filter(is_active=True).order_by('name')
|
||||
|
||||
# Получаем созданные документы (если инвентаризация завершена)
|
||||
if self.object.status == 'completed':
|
||||
context['writeoff_document'] = self.object.writeoff_documents.first()
|
||||
context['incoming_document'] = self.object.incoming_documents.first()
|
||||
|
||||
return context
|
||||
|
||||
|
||||
@@ -100,3 +236,323 @@ class InventoryLineCreateBulkView(LoginRequiredMixin, View):
|
||||
# TODO: Реализовать обработку формы с множественными строками
|
||||
messages.success(request, 'Результаты инвентаризации добавлены.')
|
||||
return redirect('inventory:inventory-detail', pk=pk)
|
||||
|
||||
|
||||
class InventoryLineAddView(LoginRequiredMixin, View):
|
||||
"""
|
||||
AJAX view для добавления строки инвентаризации.
|
||||
Принимает product_id через POST, создает InventoryLine с quantity_system = quantity_free.
|
||||
"""
|
||||
|
||||
@method_decorator(require_http_methods(["POST"]))
|
||||
@transaction.atomic
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super().dispatch(*args, **kwargs)
|
||||
|
||||
def post(self, request, inventory_id):
|
||||
try:
|
||||
inventory = get_object_or_404(Inventory, id=inventory_id)
|
||||
|
||||
# Проверяем что инвентаризация не завершена
|
||||
if inventory.status == 'completed':
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': 'Нельзя добавлять строки в завершенную инвентаризацию'
|
||||
}, status=400)
|
||||
|
||||
# Получаем product_id из POST
|
||||
product_id = request.POST.get('product_id')
|
||||
if not product_id:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': 'Не указан product_id'
|
||||
}, status=400)
|
||||
|
||||
product = get_object_or_404(Product, id=product_id)
|
||||
|
||||
# Проверяем, нет ли уже такой строки
|
||||
existing_line = InventoryLine.objects.filter(
|
||||
inventory=inventory,
|
||||
product=product
|
||||
).first()
|
||||
|
||||
if existing_line:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': f'Товар "{product.name}" уже добавлен в инвентаризацию'
|
||||
}, status=400)
|
||||
|
||||
# Получаем свободное количество (quantity_free)
|
||||
stock, _ = Stock.objects.get_or_create(
|
||||
product=product,
|
||||
warehouse=inventory.warehouse
|
||||
)
|
||||
stock.refresh_from_batches() # Обновить из партий
|
||||
quantity_system = stock.quantity_free # Свободное незарезервированное количество
|
||||
quantity_reserved = stock.quantity_reserved
|
||||
quantity_available = stock.quantity_available
|
||||
|
||||
# Создаем строку инвентаризации
|
||||
# Передаем quantity_reserved и quantity_available для корректного расчета разницы в save()
|
||||
line = InventoryLine(
|
||||
inventory=inventory,
|
||||
product=product,
|
||||
quantity_system=quantity_system,
|
||||
quantity_fact=Decimal('0'), # Оператор заполнит позже
|
||||
processed=False
|
||||
)
|
||||
line.save(quantity_reserved=quantity_reserved, quantity_available=quantity_available)
|
||||
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'line': {
|
||||
'id': line.id,
|
||||
'product_id': product.id,
|
||||
'product_name': product.name,
|
||||
'quantity_system': str(line.quantity_system),
|
||||
'quantity_reserved': str(quantity_reserved),
|
||||
'quantity_available': str(quantity_available),
|
||||
'quantity_fact': str(line.quantity_fact),
|
||||
'difference': str(line.difference),
|
||||
'processed': line.processed
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}, status=500)
|
||||
|
||||
|
||||
class InventoryLineUpdateView(LoginRequiredMixin, View):
|
||||
"""
|
||||
AJAX view для обновления quantity_fact строки инвентаризации.
|
||||
"""
|
||||
|
||||
@method_decorator(require_http_methods(["POST"]))
|
||||
@transaction.atomic
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super().dispatch(*args, **kwargs)
|
||||
|
||||
def post(self, request, inventory_id, line_id):
|
||||
try:
|
||||
inventory = get_object_or_404(Inventory, id=inventory_id)
|
||||
|
||||
# Проверяем что инвентаризация не завершена
|
||||
if inventory.status == 'completed':
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': 'Нельзя редактировать строки завершенной инвентаризации'
|
||||
}, status=400)
|
||||
|
||||
line = get_object_or_404(InventoryLine, id=line_id, inventory=inventory)
|
||||
|
||||
# Проверяем что строка не обработана
|
||||
if line.processed:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': 'Нельзя редактировать обработанную строку'
|
||||
}, status=400)
|
||||
|
||||
# Получаем quantity_fact из POST
|
||||
quantity_fact_str = request.POST.get('quantity_fact')
|
||||
if quantity_fact_str is None:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': 'Не указано quantity_fact'
|
||||
}, status=400)
|
||||
|
||||
try:
|
||||
quantity_fact = Decimal(str(quantity_fact_str))
|
||||
if quantity_fact < 0:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': 'Количество не может быть отрицательным'
|
||||
}, status=400)
|
||||
except (ValueError, TypeError):
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': 'Некорректное значение quantity_fact'
|
||||
}, status=400)
|
||||
|
||||
# Обновляем quantity_fact
|
||||
line.quantity_fact = quantity_fact
|
||||
|
||||
# Получаем актуальные данные из Stock для расчета разницы
|
||||
stock, _ = Stock.objects.get_or_create(
|
||||
product=line.product,
|
||||
warehouse=inventory.warehouse
|
||||
)
|
||||
stock.refresh_from_batches()
|
||||
|
||||
# Для незавершенных инвентаризаций обновляем quantity_system динамически
|
||||
if inventory.status != 'completed':
|
||||
line.quantity_system = stock.quantity_free
|
||||
|
||||
# Пересчитываем разницу по новой формуле: (quantity_fact + quantity_reserved) - quantity_available
|
||||
line.difference = (line.quantity_fact + stock.quantity_reserved) - stock.quantity_available
|
||||
|
||||
# Сохраняем с передачей quantity_reserved и quantity_available для корректного расчета в save()
|
||||
line.save(quantity_reserved=stock.quantity_reserved, quantity_available=stock.quantity_available)
|
||||
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'line': {
|
||||
'id': line.id,
|
||||
'product_id': line.product.id,
|
||||
'product_name': line.product.name,
|
||||
'quantity_system': str(line.quantity_system),
|
||||
'quantity_reserved': str(stock.quantity_reserved),
|
||||
'quantity_available': str(stock.quantity_available),
|
||||
'quantity_fact': str(line.quantity_fact),
|
||||
'difference': str(line.difference),
|
||||
'processed': line.processed
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}, status=500)
|
||||
|
||||
|
||||
class InventoryLineDeleteView(LoginRequiredMixin, View):
|
||||
"""
|
||||
AJAX view для удаления строки инвентаризации.
|
||||
"""
|
||||
|
||||
@method_decorator(require_http_methods(["POST"]))
|
||||
@transaction.atomic
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super().dispatch(*args, **kwargs)
|
||||
|
||||
def post(self, request, inventory_id, line_id):
|
||||
try:
|
||||
inventory = get_object_or_404(Inventory, id=inventory_id)
|
||||
|
||||
# Проверяем что инвентаризация не завершена
|
||||
if inventory.status == 'completed':
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': 'Нельзя удалять строки завершенной инвентаризации'
|
||||
}, status=400)
|
||||
|
||||
line = get_object_or_404(InventoryLine, id=line_id, inventory=inventory)
|
||||
|
||||
# Проверяем что строка не обработана
|
||||
if line.processed:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': 'Нельзя удалять обработанную строку'
|
||||
}, status=400)
|
||||
|
||||
product_name = line.product.name
|
||||
line.delete()
|
||||
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'message': f'Строка для товара "{product_name}" удалена'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}, status=500)
|
||||
|
||||
|
||||
class InventoryCompleteView(LoginRequiredMixin, View):
|
||||
"""
|
||||
View для завершения инвентаризации.
|
||||
Создает документы списания и оприходования (черновики).
|
||||
"""
|
||||
|
||||
@method_decorator(require_http_methods(["POST"]))
|
||||
@transaction.atomic
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super().dispatch(*args, **kwargs)
|
||||
|
||||
def post(self, request, inventory_id):
|
||||
try:
|
||||
inventory = get_object_or_404(Inventory, id=inventory_id)
|
||||
|
||||
# Проверяем что инвентаризация не завершена
|
||||
if inventory.status == 'completed':
|
||||
messages.warning(request, 'Инвентаризация уже завершена.')
|
||||
return redirect('inventory:inventory-detail', pk=inventory_id)
|
||||
|
||||
# Проверяем что есть строки
|
||||
lines_count = InventoryLine.objects.filter(inventory=inventory).count()
|
||||
if lines_count == 0:
|
||||
messages.error(request, 'Нельзя завершить инвентаризацию без строк.')
|
||||
return redirect('inventory:inventory-detail', pk=inventory_id)
|
||||
|
||||
# Обрабатываем инвентаризацию (создает документы-черновики)
|
||||
result = InventoryProcessor.process_inventory(inventory_id)
|
||||
|
||||
# Автоматически проводим созданные документы
|
||||
writeoff_confirmed = False
|
||||
incoming_confirmed = False
|
||||
confirmation_errors = []
|
||||
|
||||
if result['writeoff_document'] and result['writeoff_document'].status == 'draft':
|
||||
try:
|
||||
WriteOffDocumentService.confirm_document(
|
||||
result['writeoff_document'],
|
||||
confirmed_by=request.user
|
||||
)
|
||||
writeoff_confirmed = True
|
||||
except ValidationError as e:
|
||||
confirmation_errors.append(f'Ошибка проведения документа списания: {str(e)}')
|
||||
|
||||
if result['incoming_document'] and result['incoming_document'].status == 'draft':
|
||||
try:
|
||||
IncomingDocumentService.confirm_document(
|
||||
result['incoming_document'],
|
||||
confirmed_by=request.user
|
||||
)
|
||||
incoming_confirmed = True
|
||||
except ValidationError as e:
|
||||
confirmation_errors.append(f'Ошибка проведения документа оприходования: {str(e)}')
|
||||
|
||||
# Формируем сообщение
|
||||
msg_parts = [f'Инвентаризация завершена. Обработано строк: {result["processed_lines"]}.']
|
||||
|
||||
if result['writeoff_document']:
|
||||
if writeoff_confirmed:
|
||||
msg_parts.append(
|
||||
f'Документ списания {result["writeoff_document"].document_number} создан и автоматически проведен.'
|
||||
)
|
||||
else:
|
||||
msg_parts.append(
|
||||
f'Документ списания: {result["writeoff_document"].document_number}.'
|
||||
)
|
||||
|
||||
if result['incoming_document']:
|
||||
if incoming_confirmed:
|
||||
msg_parts.append(
|
||||
f'Документ оприходования {result["incoming_document"].document_number} создан и автоматически проведен.'
|
||||
)
|
||||
else:
|
||||
msg_parts.append(
|
||||
f'Документ оприходования: {result["incoming_document"].document_number}.'
|
||||
)
|
||||
|
||||
if result['errors']:
|
||||
msg_parts.append(f'Ошибок при обработке: {len(result["errors"])}.')
|
||||
|
||||
if confirmation_errors:
|
||||
for error in confirmation_errors:
|
||||
messages.error(request, error)
|
||||
|
||||
messages.success(request, ' '.join(msg_parts))
|
||||
|
||||
return redirect('inventory:inventory-detail', pk=inventory_id)
|
||||
|
||||
except Exception as e:
|
||||
messages.error(request, f'Ошибка при завершении инвентаризации: {str(e)}')
|
||||
return redirect('inventory:inventory-detail', pk=inventory_id)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user