Унификация генерации номеров документов и оптимизация кода
- Унифицирован формат номеров документов: IN-XXXXXX (6 цифр), как WO-XXXXXX и MOVE-XXXXXX - Убрано дублирование функции _extract_number_from_document_number - Оптимизирована инициализация счетчика incoming: быстрая проверка перед полной инициализацией - Удален неиспользуемый файл utils.py (функциональность перенесена в document_generator.py) - Все функции генерации номеров используют единый подход через DocumentCounter.get_next_value()
This commit is contained in:
@@ -27,6 +27,16 @@ from .inventory_ops import (
|
||||
InventoryLineCreateBulkView
|
||||
)
|
||||
from .writeoff import WriteOffListView, WriteOffCreateView, WriteOffUpdateView, WriteOffDeleteView
|
||||
from .writeoff_document import (
|
||||
WriteOffDocumentListView, WriteOffDocumentCreateView, WriteOffDocumentDetailView,
|
||||
WriteOffDocumentAddItemView, WriteOffDocumentUpdateItemView, WriteOffDocumentRemoveItemView,
|
||||
WriteOffDocumentConfirmView, WriteOffDocumentCancelView
|
||||
)
|
||||
from .incoming_document import (
|
||||
IncomingDocumentListView, IncomingDocumentCreateView, IncomingDocumentDetailView,
|
||||
IncomingDocumentAddItemView, IncomingDocumentUpdateItemView, IncomingDocumentRemoveItemView,
|
||||
IncomingDocumentConfirmView, IncomingDocumentCancelView
|
||||
)
|
||||
from .transfer import TransferListView, TransferBulkCreateView, TransferDetailView, TransferDeleteView, GetProductStockView
|
||||
from .reservation import ReservationListView
|
||||
from .stock import StockListView, StockDetailView
|
||||
@@ -57,6 +67,14 @@ __all__ = [
|
||||
'InventoryListView', 'InventoryCreateView', 'InventoryDetailView', 'InventoryLineCreateBulkView',
|
||||
# WriteOff
|
||||
'WriteOffListView', 'WriteOffCreateView', 'WriteOffUpdateView', 'WriteOffDeleteView',
|
||||
# WriteOffDocument
|
||||
'WriteOffDocumentListView', 'WriteOffDocumentCreateView', 'WriteOffDocumentDetailView',
|
||||
'WriteOffDocumentAddItemView', 'WriteOffDocumentUpdateItemView', 'WriteOffDocumentRemoveItemView',
|
||||
'WriteOffDocumentConfirmView', 'WriteOffDocumentCancelView',
|
||||
# IncomingDocument
|
||||
'IncomingDocumentListView', 'IncomingDocumentCreateView', 'IncomingDocumentDetailView',
|
||||
'IncomingDocumentAddItemView', 'IncomingDocumentUpdateItemView', 'IncomingDocumentRemoveItemView',
|
||||
'IncomingDocumentConfirmView', 'IncomingDocumentCancelView',
|
||||
# Transfer
|
||||
'TransferListView', 'TransferBulkCreateView', 'TransferDetailView', 'TransferDeleteView', 'GetProductStockView',
|
||||
# Reservation
|
||||
|
||||
217
myproject/inventory/views/incoming_document.py
Normal file
217
myproject/inventory/views/incoming_document.py
Normal file
@@ -0,0 +1,217 @@
|
||||
"""
|
||||
Views для работы с документами поступления (IncomingDocument).
|
||||
"""
|
||||
|
||||
from django.views.generic import ListView, CreateView, DetailView, View
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.urls import reverse
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.contrib import messages
|
||||
from django.http import JsonResponse
|
||||
from django.db import transaction
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from inventory.models import IncomingDocument, IncomingDocumentItem
|
||||
from inventory.forms import IncomingDocumentForm, IncomingDocumentItemForm
|
||||
from inventory.services.incoming_document_service import IncomingDocumentService
|
||||
|
||||
|
||||
class IncomingDocumentListView(LoginRequiredMixin, ListView):
|
||||
"""Список документов поступления"""
|
||||
model = IncomingDocument
|
||||
template_name = 'inventory/incoming_document/incoming_document_list.html'
|
||||
context_object_name = 'documents'
|
||||
paginate_by = 20
|
||||
|
||||
def get_queryset(self):
|
||||
return IncomingDocument.objects.select_related(
|
||||
'warehouse', 'created_by', 'confirmed_by'
|
||||
).prefetch_related('items').order_by('-date', '-created_at')
|
||||
|
||||
|
||||
class IncomingDocumentCreateView(LoginRequiredMixin, CreateView):
|
||||
"""Создание документа поступления"""
|
||||
model = IncomingDocument
|
||||
form_class = IncomingDocumentForm
|
||||
template_name = 'inventory/incoming_document/incoming_document_form.html'
|
||||
|
||||
def form_valid(self, form):
|
||||
document = IncomingDocumentService.create_document(
|
||||
warehouse=form.cleaned_data['warehouse'],
|
||||
date=form.cleaned_data['date'],
|
||||
receipt_type=form.cleaned_data['receipt_type'],
|
||||
supplier_name=form.cleaned_data.get('supplier_name'),
|
||||
notes=form.cleaned_data.get('notes'),
|
||||
created_by=self.request.user
|
||||
)
|
||||
messages.success(self.request, f'Документ {document.document_number} создан')
|
||||
return redirect('inventory:incoming-document-detail', pk=document.pk)
|
||||
|
||||
|
||||
class IncomingDocumentDetailView(LoginRequiredMixin, DetailView):
|
||||
"""Детальный просмотр документа поступления"""
|
||||
model = IncomingDocument
|
||||
template_name = 'inventory/incoming_document/incoming_document_detail.html'
|
||||
context_object_name = 'document'
|
||||
|
||||
def get_queryset(self):
|
||||
return IncomingDocument.objects.select_related(
|
||||
'warehouse', 'created_by', 'confirmed_by'
|
||||
).prefetch_related('items__product')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['item_form'] = IncomingDocumentItemForm(document=self.object)
|
||||
|
||||
# Добавляем категории и теги для компонента поиска товаров
|
||||
from products.models import ProductCategory, ProductTag
|
||||
context['categories'] = ProductCategory.objects.filter(is_active=True).order_by('name')
|
||||
context['tags'] = ProductTag.objects.filter(is_active=True).order_by('name')
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class IncomingDocumentAddItemView(LoginRequiredMixin, View):
|
||||
"""Добавление позиции в документ поступления"""
|
||||
|
||||
@transaction.atomic
|
||||
def post(self, request, pk):
|
||||
document = get_object_or_404(IncomingDocument, pk=pk)
|
||||
form = IncomingDocumentItemForm(request.POST, document=document)
|
||||
|
||||
if form.is_valid():
|
||||
try:
|
||||
item = IncomingDocumentService.add_item(
|
||||
document=document,
|
||||
product=form.cleaned_data['product'],
|
||||
quantity=form.cleaned_data['quantity'],
|
||||
cost_price=form.cleaned_data['cost_price'],
|
||||
notes=form.cleaned_data.get('notes')
|
||||
)
|
||||
|
||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'item_id': item.id,
|
||||
'message': f'Добавлено: {item.product.name}'
|
||||
})
|
||||
|
||||
messages.success(request, f'Добавлено: {item.product.name}')
|
||||
|
||||
except ValidationError as e:
|
||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return JsonResponse({'success': False, 'error': str(e)}, status=400)
|
||||
messages.error(request, str(e))
|
||||
else:
|
||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
errors = '; '.join([f"{k}: {v[0]}" for k, v in form.errors.items()])
|
||||
return JsonResponse({'success': False, 'error': errors}, status=400)
|
||||
for field, errors in form.errors.items():
|
||||
for error in errors:
|
||||
messages.error(request, f'{field}: {error}')
|
||||
|
||||
return redirect('inventory:incoming-document-detail', pk=pk)
|
||||
|
||||
|
||||
class IncomingDocumentUpdateItemView(LoginRequiredMixin, View):
|
||||
"""Обновление позиции документа поступления"""
|
||||
|
||||
@transaction.atomic
|
||||
def post(self, request, pk, item_pk):
|
||||
document = get_object_or_404(IncomingDocument, pk=pk)
|
||||
item = get_object_or_404(IncomingDocumentItem, pk=item_pk, document=document)
|
||||
|
||||
try:
|
||||
quantity = request.POST.get('quantity')
|
||||
cost_price = request.POST.get('cost_price')
|
||||
notes = request.POST.get('notes')
|
||||
|
||||
IncomingDocumentService.update_item(
|
||||
item,
|
||||
quantity=quantity if quantity else None,
|
||||
cost_price=cost_price if cost_price else None,
|
||||
notes=notes if notes else None
|
||||
)
|
||||
|
||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'message': f'Обновлено: {item.product.name}'
|
||||
})
|
||||
|
||||
messages.success(request, f'Обновлено: {item.product.name}')
|
||||
|
||||
except ValidationError as e:
|
||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return JsonResponse({'success': False, 'error': str(e)}, status=400)
|
||||
messages.error(request, str(e))
|
||||
|
||||
return redirect('inventory:incoming-document-detail', pk=pk)
|
||||
|
||||
|
||||
class IncomingDocumentRemoveItemView(LoginRequiredMixin, View):
|
||||
"""Удаление позиции из документа поступления"""
|
||||
|
||||
@transaction.atomic
|
||||
def post(self, request, pk, item_pk):
|
||||
document = get_object_or_404(IncomingDocument, pk=pk)
|
||||
item = get_object_or_404(IncomingDocumentItem, pk=item_pk, document=document)
|
||||
|
||||
try:
|
||||
product_name = item.product.name
|
||||
IncomingDocumentService.remove_item(item)
|
||||
|
||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'message': f'Удалено: {product_name}'
|
||||
})
|
||||
|
||||
messages.success(request, f'Удалено: {product_name}')
|
||||
|
||||
except ValidationError as e:
|
||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return JsonResponse({'success': False, 'error': str(e)}, status=400)
|
||||
messages.error(request, str(e))
|
||||
|
||||
return redirect('inventory:incoming-document-detail', pk=pk)
|
||||
|
||||
|
||||
class IncomingDocumentConfirmView(LoginRequiredMixin, View):
|
||||
"""Проведение документа поступления"""
|
||||
|
||||
@transaction.atomic
|
||||
def post(self, request, pk):
|
||||
document = get_object_or_404(IncomingDocument, pk=pk)
|
||||
|
||||
try:
|
||||
result = IncomingDocumentService.confirm_document(
|
||||
document,
|
||||
confirmed_by=request.user
|
||||
)
|
||||
messages.success(
|
||||
request,
|
||||
f'Документ {document.document_number} проведён. '
|
||||
f'Оприходовано {result["total_quantity"]} шт на сумму {result["total_cost"]:.2f}'
|
||||
)
|
||||
except ValidationError as e:
|
||||
messages.error(request, str(e))
|
||||
|
||||
return redirect('inventory:incoming-document-detail', pk=pk)
|
||||
|
||||
|
||||
class IncomingDocumentCancelView(LoginRequiredMixin, View):
|
||||
"""Отмена документа поступления"""
|
||||
|
||||
@transaction.atomic
|
||||
def post(self, request, pk):
|
||||
document = get_object_or_404(IncomingDocument, pk=pk)
|
||||
|
||||
try:
|
||||
IncomingDocumentService.cancel_document(document)
|
||||
messages.success(request, f'Документ {document.document_number} отменён')
|
||||
except ValidationError as e:
|
||||
messages.error(request, str(e))
|
||||
|
||||
return redirect('inventory:incoming-document-list')
|
||||
|
||||
Reference in New Issue
Block a user