diff --git a/myproject/inventory/admin.py b/myproject/inventory/admin.py index ebb371a..3519fda 100644 --- a/myproject/inventory/admin.py +++ b/myproject/inventory/admin.py @@ -5,11 +5,11 @@ from django.db.models import Sum from decimal import Decimal from inventory.models import ( - Warehouse, StockBatch, Sale, WriteOff, Transfer, - Inventory, InventoryLine, Reservation, Stock, StockMovement, + Warehouse, StockBatch, Sale, WriteOff, + Inventory, InventoryLine, Reservation, Stock, SaleBatchAllocation, Showcase, WriteOffDocument, WriteOffDocumentItem, IncomingDocument, IncomingDocumentItem, Transformation, TransformationInput, - TransformationOutput + TransformationOutput, TransferDocument, TransferDocumentItem ) @@ -166,28 +166,6 @@ class WriteOffAdmin(admin.ModelAdmin): reason_display.short_description = 'Причина' -# ===== TRANSFER ===== -@admin.register(Transfer) -class TransferAdmin(admin.ModelAdmin): - list_display = ('batch', 'from_warehouse', 'to_warehouse', 'quantity', 'date') - list_filter = ('date', 'from_warehouse', 'to_warehouse') - search_fields = ('batch__product__name', 'document_number') - date_hierarchy = 'date' - fieldsets = ( - ('Перемещение', { - 'fields': ('batch', 'from_warehouse', 'to_warehouse', 'quantity', 'new_batch') - }), - ('Документ', { - 'fields': ('document_number',) - }), - ('Дата', { - 'fields': ('date',), - 'classes': ('collapse',) - }), - ) - readonly_fields = ('date', 'new_batch') - - # ===== INVENTORY LINE (INLINE) ===== class InventoryLineInline(admin.TabularInline): model = InventoryLine @@ -317,16 +295,6 @@ class StockAdmin(admin.ModelAdmin): readonly_fields = ('quantity_available', 'quantity_reserved', 'updated_at') -# ===== STOCK MOVEMENT (для аудита) ===== -@admin.register(StockMovement) -class StockMovementAdmin(admin.ModelAdmin): - list_display = ('product', 'change', 'reason', 'order', 'created_at') - list_filter = ('reason', 'created_at') - search_fields = ('product__name', 'order__order_number') - date_hierarchy = 'created_at' - readonly_fields = ('created_at',) - - # ===== WRITEOFF DOCUMENT (документы списания) ===== class WriteOffDocumentItemInline(admin.TabularInline): model = WriteOffDocumentItem diff --git a/myproject/inventory/forms.py b/myproject/inventory/forms.py index 69c59c6..fd2a074 100644 --- a/myproject/inventory/forms.py +++ b/myproject/inventory/forms.py @@ -4,8 +4,8 @@ from django.core.exceptions import ValidationError from decimal import Decimal from .models import ( - Warehouse, Sale, WriteOff, Transfer, Reservation, Inventory, InventoryLine, StockBatch, - TransferBatch, TransferItem, Showcase, WriteOffDocument, WriteOffDocumentItem, Stock, + Warehouse, Sale, WriteOff, Reservation, Inventory, InventoryLine, StockBatch, + TransferDocument, TransferDocumentItem, Showcase, WriteOffDocument, WriteOffDocumentItem, Stock, IncomingDocument, IncomingDocumentItem, Transformation, TransformationInput, TransformationOutput ) from products.models import Product @@ -157,7 +157,7 @@ class TransferHeaderForm(forms.ModelForm): Содержит информацию о складах-источнике и складе-назначении, примечания. """ class Meta: - model = TransferBatch + model = TransferDocument fields = ['from_warehouse', 'to_warehouse', 'notes'] widgets = { 'from_warehouse': forms.Select(attrs={'class': 'form-control'}), diff --git a/myproject/inventory/migrations/0005_refactor_transfer_models.py b/myproject/inventory/migrations/0005_refactor_transfer_models.py new file mode 100644 index 0000000..7de7f1f --- /dev/null +++ b/myproject/inventory/migrations/0005_refactor_transfer_models.py @@ -0,0 +1,38 @@ +# Generated migration for Transfer models refactoring + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0004_remove_incoming_batch_and_incoming'), + ('products', '0001_initial'), + ] + + operations = [ + # 1. Удаление устаревших моделей ПЕРЕД переименованием + migrations.DeleteModel( + name='Transfer', + ), + migrations.DeleteModel( + name='StockMovement', + ), + + # 2. Переименование моделей + migrations.RenameModel( + old_name='TransferBatch', + new_name='TransferDocument', + ), + migrations.RenameModel( + old_name='TransferItem', + new_name='TransferDocumentItem', + ), + + # 3. Переименование поля transfer_batch → transfer_document в TransferDocumentItem + migrations.RenameField( + model_name='transferdocumentitem', + old_name='transfer_batch', + new_name='transfer_document', + ), + ] diff --git a/myproject/inventory/models.py b/myproject/inventory/models.py index 0b5d50f..ce66eb7 100644 --- a/myproject/inventory/models.py +++ b/myproject/inventory/models.py @@ -215,35 +215,6 @@ class WriteOff(models.Model): super().save(*args, **kwargs) -class Transfer(models.Model): - """ - Перемещение товара между складами. Сохраняет партийность. - """ - batch = models.ForeignKey(StockBatch, on_delete=models.CASCADE, - related_name='transfers', verbose_name="Партия") - from_warehouse = models.ForeignKey(Warehouse, on_delete=models.CASCADE, - related_name='transfers_from', verbose_name="Из склада") - to_warehouse = models.ForeignKey(Warehouse, on_delete=models.CASCADE, - related_name='transfers_to', verbose_name="На склад") - quantity = models.DecimalField(max_digits=10, decimal_places=3, verbose_name="Количество") - document_number = models.CharField(max_length=100, blank=True, null=True, - verbose_name="Номер документа") - date = models.DateTimeField(auto_now_add=True, verbose_name="Дата операции") - new_batch = models.ForeignKey(StockBatch, on_delete=models.SET_NULL, null=True, blank=True, - related_name='transfer_sources', verbose_name="Новая партия") - - class Meta: - verbose_name = "Перемещение" - verbose_name_plural = "Перемещения" - ordering = ['-date'] - indexes = [ - models.Index(fields=['from_warehouse', 'to_warehouse']), - models.Index(fields=['date']), - ] - - def __str__(self): - return f"Перемещение {self.batch.product.name} ({self.quantity} шт): {self.from_warehouse} → {self.to_warehouse}" - class Inventory(models.Model): """ @@ -731,38 +702,6 @@ class Stock(models.Model): self.save() -class StockMovement(models.Model): - """ - Журнал всех складских операций (приход, списание, коррекция). - Используется для аудита. - """ - REASON_CHOICES = [ - ('purchase', 'Закупка'), - ('sale', 'Продажа'), - ('write_off', 'Списание'), - ('adjustment', 'Корректировка'), - ] - - product = models.ForeignKey(Product, on_delete=models.CASCADE, - related_name='movements', verbose_name="Товар") - change = models.DecimalField(max_digits=10, decimal_places=3, verbose_name="Изменение") - reason = models.CharField(max_length=20, choices=REASON_CHOICES, verbose_name="Причина") - order = models.ForeignKey('orders.Order', on_delete=models.SET_NULL, null=True, blank=True, - related_name='stock_movements', verbose_name="Заказ") - created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания") - - class Meta: - verbose_name = "Движение товара" - verbose_name_plural = "Движения товаров" - indexes = [ - models.Index(fields=['product']), - models.Index(fields=['created_at']), - ] - - def __str__(self): - return f"{self.product.name}: {self.change} ({self.reason})" - - class DocumentCounter(models.Model): """ Счетчик номеров документов для различных операций. @@ -811,7 +750,7 @@ class DocumentCounter(models.Model): return obj.current_value -class TransferBatch(models.Model): +class TransferDocument(models.Model): """ Документ перемещения товара между складами. Один номер документа = одна операция перемещения множественных товаров. @@ -819,13 +758,13 @@ class TransferBatch(models.Model): from_warehouse = models.ForeignKey( Warehouse, on_delete=models.CASCADE, - related_name='transfer_batches_from', + related_name='transfer_documents_from', verbose_name="Склад-отгрузки" ) to_warehouse = models.ForeignKey( Warehouse, on_delete=models.CASCADE, - related_name='transfer_batches_to', + related_name='transfer_documents_to', verbose_name="Склад-приемки" ) document_number = models.CharField( @@ -866,13 +805,13 @@ class TransferBatch(models.Model): return f"Перемещение {self.document_number}: {total_items} товаров, {total_qty} шт ({self.from_warehouse} → {self.to_warehouse})" -class TransferItem(models.Model): +class TransferDocumentItem(models.Model): """ Строка документа перемещения (товар в перемещении). Связь между документом и товарами. """ - transfer_batch = models.ForeignKey( - TransferBatch, + transfer_document = models.ForeignKey( + TransferDocument, on_delete=models.CASCADE, related_name='items', verbose_name="Документ перемещения" @@ -880,13 +819,13 @@ class TransferItem(models.Model): product = models.ForeignKey( Product, on_delete=models.CASCADE, - related_name='transfer_items', + related_name='transfer_document_items', verbose_name="Товар" ) batch = models.ForeignKey( StockBatch, on_delete=models.CASCADE, - related_name='transfer_items', + related_name='transfer_document_items', verbose_name="Исходная партия (FIFO)" ) quantity = models.DecimalField( @@ -899,17 +838,17 @@ class TransferItem(models.Model): on_delete=models.SET_NULL, null=True, blank=True, - related_name='transfer_items_created', + related_name='transfer_document_items_created', verbose_name="Созданная партия на целевом складе" ) class Meta: verbose_name = "Строка перемещения" verbose_name_plural = "Строки перемещения" - unique_together = [['transfer_batch', 'batch']] + unique_together = [['transfer_document', 'batch']] ordering = ['id'] indexes = [ - models.Index(fields=['transfer_batch']), + models.Index(fields=['transfer_document']), models.Index(fields=['product']), ] diff --git a/myproject/inventory/templates/inventory/transfer/transfer_detail.html b/myproject/inventory/templates/inventory/transfer/transfer_detail.html index 06fef60..6e156a0 100644 --- a/myproject/inventory/templates/inventory/transfer/transfer_detail.html +++ b/myproject/inventory/templates/inventory/transfer/transfer_detail.html @@ -1,6 +1,6 @@ {% extends 'base.html' %} -{% block title %}Документ перемещения {{ transfer_batch.document_number }}{% endblock %} +{% block title %}Документ перемещения {{ transfer_document.document_number }}{% endblock %} {% block content %}
@@ -8,7 +8,7 @@ @@ -18,36 +18,36 @@
- {{ transfer_batch.document_number }} + {{ transfer_document.document_number }}

Склад-отгрузки

-

{{ transfer_batch.from_warehouse.name }}

+

{{ transfer_document.from_warehouse.name }}

Склад-приемки

-

{{ transfer_batch.to_warehouse.name }}

+

{{ transfer_document.to_warehouse.name }}

- {% if transfer_batch.notes %} + {% if transfer_document.notes %}

Примечания

-

{{ transfer_batch.notes }}

+

{{ transfer_document.notes }}

{% endif %}

Дата создания

-

{{ transfer_batch.created_at|date:"d.m.Y H:i" }}

+

{{ transfer_document.created_at|date:"d.m.Y H:i" }}

Последнее обновление

-

{{ transfer_batch.updated_at|date:"d.m.Y H:i" }}

+

{{ transfer_document.updated_at|date:"d.m.Y H:i" }}

@@ -132,7 +132,7 @@ Вернуться к списку - + Удалить
diff --git a/myproject/inventory/urls.py b/myproject/inventory/urls.py index fb9146d..0428e57 100644 --- a/myproject/inventory/urls.py +++ b/myproject/inventory/urls.py @@ -21,8 +21,6 @@ from .views import ( StockBatchListView, StockBatchDetailView, # SaleBatchAllocation SaleBatchAllocationListView, - # StockMovement - StockMovementListView, ) # Showcase views from .views.showcase import ShowcaseListView, ShowcaseCreateView, ShowcaseUpdateView, ShowcaseDeleteView, SetDefaultShowcaseView @@ -131,9 +129,6 @@ urlpatterns = [ # ==================== ALLOCATION (READ ONLY) ==================== path('allocations/', SaleBatchAllocationListView.as_view(), name='allocation-list'), - # ==================== MOVEMENT (READ ONLY) ==================== - path('movements/', StockMovementListView.as_view(), name='movement-list'), - # ==================== SHOWCASE ==================== path('showcases/', ShowcaseListView.as_view(), name='showcase-list'), path('showcases/create/', ShowcaseCreateView.as_view(), name='showcase-create'), diff --git a/myproject/inventory/views/__init__.py b/myproject/inventory/views/__init__.py index bcf2bca..198356f 100644 --- a/myproject/inventory/views/__init__.py +++ b/myproject/inventory/views/__init__.py @@ -13,7 +13,6 @@ Inventory Views Package - stock.py: Справочник остатков (view-only) - batch.py: Справочник партий товара (view-only) - allocation.py: Распределение продаж по партиям (view-only) -- movements.py: Журнал складских операций (view-only) """ from django.shortcuts import render from django.contrib.auth.decorators import login_required @@ -41,7 +40,6 @@ from .transfer import TransferListView, TransferBulkCreateView, TransferDetailVi from .reservation import ReservationListView from .stock import StockListView, StockDetailView from .allocation import SaleBatchAllocationListView -from .movements import StockMovementListView @login_required diff --git a/myproject/inventory/views/movements.py b/myproject/inventory/views/movements.py deleted file mode 100644 index 0a10037..0000000 --- a/myproject/inventory/views/movements.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -""" -StockMovement (Журнал всех складских операций) views - READ ONLY -GROUP 3: LOW PRIORITY - Аудит логирование -""" -from django.views.generic import ListView -from django.contrib.auth.mixins import LoginRequiredMixin -from ..models import StockMovement - - -class StockMovementListView(LoginRequiredMixin, ListView): - """ - Полный журнал всех складских операций (приход, продажа, списание, корректировка). - Используется для аудита и контроля. - """ - model = StockMovement - template_name = 'inventory/movements/movement_list.html' - context_object_name = 'movements' - paginate_by = 50 - - def get_queryset(self): - return StockMovement.objects.select_related( - 'product', 'order' - ).order_by('-created_at') diff --git a/myproject/inventory/views/transfer.py b/myproject/inventory/views/transfer.py index 7c885ce..c310572 100644 --- a/myproject/inventory/views/transfer.py +++ b/myproject/inventory/views/transfer.py @@ -13,7 +13,7 @@ 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 ..models import TransferDocument, TransferDocumentItem, Stock from ..forms import TransferBulkForm from inventory.utils.document_generator import generate_transfer_document_number from inventory.services.batch_manager import StockBatchManager @@ -24,19 +24,19 @@ class TransferListView(LoginRequiredMixin, ListView): """ View для просмотра списка документов перемещений товаров. """ - model = TransferBatch + model = TransferDocument template_name = 'inventory/transfer/transfer_list.html' context_object_name = 'transfers' paginate_by = 20 def get_queryset(self): - return TransferBatch.objects.select_related( + return TransferDocument.objects.select_related( 'from_warehouse', 'to_warehouse' ).order_by('-created_at') # ============================================================================ -# VIEWS ДЛЯ ПЕРЕМЕЩЕНИЯ ТОВАРОВ (TransferBatch + TransferItem) +# VIEWS ДЛЯ ПЕРЕМЕЩЕНИЯ ТОВАРОВ (TransferDocument + TransferDocumentItem) # ============================================================================ class TransferBulkCreateView(LoginRequiredMixin, View): @@ -100,8 +100,8 @@ class TransferBulkCreateView(LoginRequiredMixin, View): # Начинаем транзакцию try: with transaction.atomic(): - # 1. Создаем документ TransferBatch - transfer_batch = TransferBatch.objects.create( + # 1. Создаем документ TransferDocument + transfer_document = TransferDocument.objects.create( from_warehouse=from_warehouse, to_warehouse=to_warehouse, document_number=generate_transfer_document_number(), @@ -119,10 +119,10 @@ class TransferBulkCreateView(LoginRequiredMixin, View): quantity=quantity ) - # Создаем TransferItem для каждого использованного batch + # Создаем TransferDocumentItem для каждого использованного batch for source_batch, qty_transferred, new_batch in transfers: - TransferItem.objects.create( - transfer_batch=transfer_batch, + TransferDocumentItem.objects.create( + transfer_document=transfer_document, product=product, batch=source_batch, quantity=qty_transferred, @@ -136,11 +136,11 @@ class TransferBulkCreateView(LoginRequiredMixin, View): # 3. Успешно создали документ messages.success( request, - f'Документ перемещения {transfer_batch.document_number} успешно создан. ' + f'Документ перемещения {transfer_document.document_number} успешно создан. ' f'Перемещено {len(products)} видов товаров.' ) - return redirect('inventory:transfer-detail', pk=transfer_batch.id) + return redirect('inventory:transfer-detail', pk=transfer_document.id) except Exception as e: messages.error(request, f'Ошибка при создании документа перемещения: {str(e)}') @@ -151,12 +151,12 @@ class TransferDetailView(LoginRequiredMixin, DetailView): """ View для просмотра деталей документа перемещения. """ - model = TransferBatch + model = TransferDocument template_name = 'inventory/transfer/transfer_detail.html' - context_object_name = 'transfer_batch' + context_object_name = 'transfer_document' def get_queryset(self): - return TransferBatch.objects.select_related( + return TransferDocument.objects.select_related( 'from_warehouse', 'to_warehouse' ).prefetch_related( 'items__product', 'items__batch', 'items__new_batch' @@ -164,10 +164,10 @@ class TransferDetailView(LoginRequiredMixin, DetailView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - transfer_batch = self.object + transfer_document = self.object # Собираем статистику по документу - items = transfer_batch.items.all() + items = transfer_document.items.all() total_items = items.count() total_qty = sum(Decimal(str(item.quantity)) for item in items) @@ -182,13 +182,13 @@ class TransferDeleteView(LoginRequiredMixin, DeleteView): """ View для удаления документа перемещения. """ - model = TransferBatch + model = TransferDocument 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} удалён.') + transfer_document = self.get_object() + messages.success(self.request, f'Документ перемещения {transfer_document.document_number} удалён.') return super().form_valid(form)