diff --git a/myproject/inventory/admin.py b/myproject/inventory/admin.py
index a340bee..ebb371a 100644
--- a/myproject/inventory/admin.py
+++ b/myproject/inventory/admin.py
@@ -5,7 +5,7 @@ from django.db.models import Sum
from decimal import Decimal
from inventory.models import (
- Warehouse, StockBatch, Incoming, IncomingBatch, Sale, WriteOff, Transfer,
+ Warehouse, StockBatch, Sale, WriteOff, Transfer,
Inventory, InventoryLine, Reservation, Stock, StockMovement,
SaleBatchAllocation, Showcase, WriteOffDocument, WriteOffDocumentItem,
IncomingDocument, IncomingDocumentItem, Transformation, TransformationInput,
diff --git a/myproject/inventory/forms.py b/myproject/inventory/forms.py
index 5d169e4..69c59c6 100644
--- a/myproject/inventory/forms.py
+++ b/myproject/inventory/forms.py
@@ -4,7 +4,7 @@ from django.core.exceptions import ValidationError
from decimal import Decimal
from .models import (
- Warehouse, Incoming, Sale, WriteOff, Transfer, Reservation, Inventory, InventoryLine, StockBatch,
+ Warehouse, Sale, WriteOff, Transfer, Reservation, Inventory, InventoryLine, StockBatch,
TransferBatch, TransferItem, Showcase, WriteOffDocument, WriteOffDocumentItem, Stock,
IncomingDocument, IncomingDocumentItem, Transformation, TransformationInput, TransformationOutput
)
diff --git a/myproject/inventory/migrations/0004_remove_incoming_batch_and_incoming.py b/myproject/inventory/migrations/0004_remove_incoming_batch_and_incoming.py
new file mode 100644
index 0000000..9b9a543
--- /dev/null
+++ b/myproject/inventory/migrations/0004_remove_incoming_batch_and_incoming.py
@@ -0,0 +1,23 @@
+# Generated by Django 5.0.10 on 2025-12-26 14:54
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('inventory', '0003_alter_documentcounter_counter_type_and_more'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='incomingbatch',
+ name='warehouse',
+ ),
+ migrations.DeleteModel(
+ name='Incoming',
+ ),
+ migrations.DeleteModel(
+ name='IncomingBatch',
+ ),
+ ]
diff --git a/myproject/inventory/models.py b/myproject/inventory/models.py
index 804880d..0b5d50f 100644
--- a/myproject/inventory/models.py
+++ b/myproject/inventory/models.py
@@ -100,84 +100,9 @@ class StockBatch(models.Model):
return f"{self.product.name} на {self.warehouse.name} - Остаток: {self.quantity} шт @ {self.cost_price} за ед."
-class IncomingBatch(models.Model):
- """
- Партия поступления товара (один номер документа = одна партия).
- Содержит один номер документа и может включать несколько товаров.
- """
- RECEIPT_TYPE_CHOICES = [
- ('supplier', 'Поступление от поставщика'),
- ('inventory', 'Оприходование при инвентаризации'),
- ('adjustment', 'Оприходование без инвентаризации'),
- ]
-
- warehouse = models.ForeignKey(Warehouse, on_delete=models.CASCADE,
- related_name='incoming_batches', verbose_name="Склад")
- document_number = models.CharField(max_length=100, unique=True, db_index=True,
- verbose_name="Номер документа")
- receipt_type = models.CharField(
- max_length=20,
- choices=RECEIPT_TYPE_CHOICES,
- default='supplier',
- db_index=True,
- verbose_name="Тип поступления"
- )
- supplier_name = models.CharField(max_length=200, blank=True, null=True,
- verbose_name="Наименование поставщика")
- notes = models.TextField(blank=True, null=True, verbose_name="Примечания")
- created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания")
- updated_at = models.DateTimeField(auto_now=True, verbose_name="Дата обновления")
-
- class Meta:
- verbose_name = "Партия поступления"
- verbose_name_plural = "Партии поступлений"
- ordering = ['-created_at']
- indexes = [
- models.Index(fields=['document_number']),
- models.Index(fields=['warehouse']),
- models.Index(fields=['receipt_type']),
- models.Index(fields=['-created_at']),
- ]
-
- def __str__(self):
- total_items = self.items.count()
- total_qty = self.items.aggregate(models.Sum('quantity'))['quantity__sum'] or 0
- return f"Партия {self.document_number}: {total_items} товаров, {total_qty} шт"
-
-
-class Incoming(models.Model):
- """
- Товар в партии поступления. Много товаров = одна партия (IncomingBatch).
- """
- batch = models.ForeignKey(IncomingBatch, on_delete=models.CASCADE,
- related_name='items', verbose_name="Партия")
- product = models.ForeignKey(Product, on_delete=models.CASCADE,
- related_name='incomings', verbose_name="Товар")
- quantity = models.DecimalField(max_digits=10, decimal_places=3, verbose_name="Количество")
- cost_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="Закупочная цена")
- notes = models.TextField(blank=True, null=True, verbose_name="Примечания")
- created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания")
- stock_batch = models.ForeignKey(StockBatch, on_delete=models.SET_NULL, null=True, blank=True,
- related_name='incomings', verbose_name="Складская партия")
-
- class Meta:
- verbose_name = "Товар в поступлении"
- verbose_name_plural = "Товары в поступлениях"
- ordering = ['-created_at']
- indexes = [
- models.Index(fields=['batch']),
- models.Index(fields=['product']),
- models.Index(fields=['-created_at']),
- ]
- unique_together = [['batch', 'product']] # Один товар максимум один раз в партии
-
- def __str__(self):
- return f"{self.product.name}: {self.quantity} шт (партия {self.batch.document_number})"
-
- @property
- def can_edit(self):
- """Можно ли редактировать приход"""
- return self.stock_batch is None
+# Модели IncomingBatch и Incoming удалены - заменены на IncomingDocument/IncomingDocumentItem
+# Теперь используется упрощенная архитектура:
+# IncomingDocument → IncomingDocumentItem → StockBatch (напрямую при проведении)
class Sale(models.Model):
@@ -1207,8 +1132,8 @@ class IncomingDocument(models.Model):
Сценарий использования:
1. Создается черновик (draft)
2. В течение дня добавляются товары (IncomingDocumentItem)
- 3. В конце смены документ проводится (confirmed) → создаются IncomingBatch и Incoming
- 4. Сигнал автоматически создает StockBatch и обновляет Stock
+ 3. В конце смены документ проводится (confirmed) → создается StockBatch напрямую
+ 4. Stock автоматически обновляется
"""
STATUS_CHOICES = [
('draft', 'Черновик'),
@@ -1216,6 +1141,12 @@ class IncomingDocument(models.Model):
('cancelled', 'Отменён'),
]
+ RECEIPT_TYPE_CHOICES = [
+ ('supplier', 'Поступление от поставщика'),
+ ('inventory', 'Оприходование при инвентаризации'),
+ ('adjustment', 'Оприходование без инвентаризации'),
+ ]
+
document_number = models.CharField(
max_length=100,
unique=True,
@@ -1245,7 +1176,7 @@ class IncomingDocument(models.Model):
receipt_type = models.CharField(
max_length=20,
- choices=IncomingBatch.RECEIPT_TYPE_CHOICES,
+ choices=RECEIPT_TYPE_CHOICES,
default='supplier',
db_index=True,
verbose_name="Тип поступления"
@@ -1355,9 +1286,8 @@ class IncomingDocumentItem(models.Model):
- Резервирование НЕ создается (товар еще не поступил)
При проведении документа:
- 1. Создается IncomingBatch с номером документа
- 2. Создается Incoming запись для каждого товара
- 3. Сигнал create_stock_batch_on_incoming автоматически создает StockBatch
+ 1. Для каждой позиции напрямую создается StockBatch
+ 2. Stock автоматически обновляется
"""
document = models.ForeignKey(
IncomingDocument,
diff --git a/myproject/inventory/services/incoming_document_service.py b/myproject/inventory/services/incoming_document_service.py
index 4ac7bf8..17ba1d7 100644
--- a/myproject/inventory/services/incoming_document_service.py
+++ b/myproject/inventory/services/incoming_document_service.py
@@ -14,7 +14,7 @@ from django.utils import timezone
from django.core.exceptions import ValidationError
from inventory.models import (
- IncomingDocument, IncomingDocumentItem, IncomingBatch, Incoming
+ IncomingDocument, IncomingDocumentItem, StockBatch, Stock
)
from inventory.utils.document_generator import generate_incoming_document_number
@@ -165,10 +165,9 @@ class IncomingDocumentService:
Процесс:
1. Проверяем что документ - черновик и имеет позиции
- 2. Создаем IncomingBatch с номером документа
- 3. Для каждой позиции создаем Incoming запись
- 4. Сигнал create_stock_batch_on_incoming автоматически создаст StockBatch
- 5. Меняем статус документа на 'confirmed'
+ 2. Для каждой позиции создаем StockBatch напрямую
+ 3. Обновляем Stock
+ 4. Меняем статус документа на 'confirmed'
Args:
document: IncomingDocument
@@ -188,30 +187,30 @@ class IncomingDocumentService:
if not document.items.exists():
raise ValidationError("Нельзя провести пустой документ")
- # Создаем IncomingBatch
- incoming_batch = IncomingBatch.objects.create(
- warehouse=document.warehouse,
- document_number=document.document_number,
- receipt_type=document.receipt_type,
- supplier_name=document.supplier_name if document.receipt_type == 'supplier' else '',
- notes=document.notes
- )
-
- # Создаем Incoming записи для каждого товара
- incomings_created = []
+ # Создаем StockBatch напрямую для каждого товара
+ batches_created = []
total_cost = Decimal('0')
for item in document.items.select_related('product'):
- incoming = Incoming.objects.create(
- batch=incoming_batch,
+ # Создаем партию товара на складе
+ stock_batch = StockBatch.objects.create(
product=item.product,
+ warehouse=document.warehouse,
quantity=item.quantity,
cost_price=item.cost_price,
- notes=item.notes
+ is_active=True
)
- incomings_created.append(incoming)
+ batches_created.append(stock_batch)
total_cost += item.total_cost
+ # Обновляем или создаем запись в Stock
+ stock, _ = Stock.objects.get_or_create(
+ product=item.product,
+ warehouse=document.warehouse
+ )
+ # Пересчитываем остаток из всех активных партий
+ stock.refresh_from_batches()
+
# Обновляем статус документа
document.status = 'confirmed'
document.confirmed_by = confirmed_by
@@ -220,8 +219,7 @@ class IncomingDocumentService:
return {
'document': document,
- 'incoming_batch': incoming_batch,
- 'incomings_created': len(incomings_created),
+ 'batches_created': len(batches_created),
'total_quantity': document.total_quantity,
'total_cost': total_cost
}
diff --git a/myproject/inventory/signals.py b/myproject/inventory/signals.py
index c619720..f30f58d 100644
--- a/myproject/inventory/signals.py
+++ b/myproject/inventory/signals.py
@@ -14,7 +14,7 @@ from decimal import Decimal
from django.core.exceptions import ValidationError
from orders.models import Order, OrderItem
-from inventory.models import Reservation, Warehouse, Incoming, StockBatch, Sale, SaleBatchAllocation, Inventory, WriteOff, Stock, WriteOffDocumentItem, Transformation, TransformationInput, TransformationOutput
+from inventory.models import Reservation, Warehouse, StockBatch, Sale, SaleBatchAllocation, Inventory, WriteOff, Stock, WriteOffDocumentItem, Transformation, TransformationInput, TransformationOutput
from inventory.services import SaleProcessor
from inventory.services.batch_manager import StockBatchManager
# InventoryProcessor больше не используется в сигналах - обработка вызывается явно через view
@@ -1046,131 +1046,7 @@ def update_reservation_on_item_change(sender, instance, created, **kwargs):
)
-@receiver(post_save, sender=Incoming)
-def create_stock_batch_on_incoming(sender, instance, created, **kwargs):
- """
- Сигнал: При создании товара в приходе (Incoming) автоматически создается StockBatch и обновляется Stock.
-
- Архитектура:
- - IncomingBatch: одна партия поступления (IN-0000-0001) содержит несколько товаров
- - Incoming: один товар в партии поступления
- - StockBatch: одна партия товара на складе (создается для каждого товара в приходе)
- Для FIFO: каждый товар имеет свою partия, чтобы можно было списывать отдельно
-
- Процесс:
- 1. Проверяем, новый ли товар в приходе
- 2. Если stock_batch еще не создан - создаем StockBatch для этого товара
- 3. Связываем Incoming с созданной StockBatch
- 4. Обновляем остатки на складе (Stock)
- """
- if not created:
- return # Только для новых приходов
-
- # Если stock_batch уже установлен - не создаем новый
- if instance.stock_batch:
- return
-
- # Получаем данные из партии поступления
- incoming_batch = instance.batch
- warehouse = incoming_batch.warehouse
-
- # Создаем новую партию товара на складе
- # Каждый товар в партии поступления → отдельная StockBatch
- stock_batch = StockBatch.objects.create(
- product=instance.product,
- warehouse=warehouse,
- quantity=instance.quantity,
- cost_price=instance.cost_price,
- is_active=True
- )
-
- # Связываем Incoming с созданной StockBatch
- instance.stock_batch = stock_batch
- instance.save(update_fields=['stock_batch'])
-
- # Обновляем или создаем запись в Stock
- stock, created_stock = Stock.objects.get_or_create(
- product=instance.product,
- warehouse=warehouse
- )
- # Пересчитываем остаток из всех активных партий
- # refresh_from_batches() уже вызывает save(), поэтому не вызываем ещё раз
- stock.refresh_from_batches()
-
-
-@receiver(post_save, sender=Incoming)
-def update_stock_batch_on_incoming_edit(sender, instance, created, **kwargs):
- """
- Сигнал: При редактировании товара в приходе (Incoming) автоматически
- обновляется связанная партия товара на складе (StockBatch).
-
- Это обеспечивает синхронизацию данных между Incoming и StockBatch.
-
- Архитектура:
- - Если Incoming редактируется - обновляем StockBatch с новыми значениями
- - Обновление StockBatch автоматически пересчитывает себестоимость товара (Product.cost_price)
- через сигнал update_product_cost_on_batch_change()
-
- Процесс:
- 1. Проверяем, это редактирование (created=False), а не создание
- 2. Получаем связанный StockBatch
- 3. Проверяем, изменились ли quantity или cost_price
- 4. Если да - обновляем StockBatch
- 5. Сохраняем StockBatch (запускает цепь пересчета себестоимости)
- 6. Обновляем остатки на складе (Stock)
- """
- if created:
- return # Только для редактирования (не для создания)
-
- # Получаем связанный StockBatch
- if not instance.stock_batch:
- return # Если нет связи со StockBatch - нечего обновлять
-
- stock_batch = instance.stock_batch
-
- import logging
- logger = logging.getLogger(__name__)
-
- try:
- # Проверяем, отличаются ли значения в StockBatch от Incoming
- # Это говорит нам о том, что произошло редактирование
- needs_update = (
- stock_batch.quantity != instance.quantity or
- stock_batch.cost_price != instance.cost_price
- )
-
- if not needs_update:
- return # Никаких изменений
-
- # Обновляем StockBatch с новыми значениями из Incoming
- stock_batch.quantity = instance.quantity
- stock_batch.cost_price = instance.cost_price
- stock_batch.save()
-
- logger.info(
- f"✓ StockBatch #{stock_batch.id} обновлён при редактировании Incoming: "
- f"quantity={instance.quantity}, cost_price={instance.cost_price} "
- f"(товар: {instance.product.sku})"
- )
-
- # Обновляем Stock (остатки на складе)
- warehouse = stock_batch.warehouse
- stock, _ = Stock.objects.get_or_create(
- product=instance.product,
- warehouse=warehouse
- )
- stock.refresh_from_batches()
-
- logger.info(
- f"✓ Stock обновлён для товара {instance.product.sku} "
- f"на складе {warehouse.name}"
- )
-
- except Exception as e:
- logger.error(
- f"Ошибка при обновлении StockBatch при редактировании Incoming #{instance.id}: {e}",
- exc_info=True
- )
+# Сигналы для Incoming удалены - теперь StockBatch создается напрямую в IncomingDocumentService
@receiver(post_save, sender=Sale)
diff --git a/myproject/inventory/templates/inventory/debug_page.html b/myproject/inventory/templates/inventory/debug_page.html
index 547ea32..dd7bef9 100644
--- a/myproject/inventory/templates/inventory/debug_page.html
+++ b/myproject/inventory/templates/inventory/debug_page.html
@@ -534,8 +534,100 @@
+
+
+
📥 Документы поступления IncomingDocument ({{ incoming_documents.count }})
+
+
+
+
+ | ID |
+ Номер |
+ Склад |
+ Статус |
+ Тип |
+ Дата |
+ Поставщик |
+ Создал |
+ Провёл |
+
+
+
+ {% for doc in incoming_documents %}
+
+ | {{ doc.id }} |
+ {{ doc.document_number }} |
+ {{ doc.warehouse.name }} |
+
+ {% if doc.status == 'draft' %}
+ Черновик
+ {% elif doc.status == 'confirmed' %}
+ Проведён
+ {% elif doc.status == 'cancelled' %}
+ Отменён
+ {% else %}
+ {{ doc.status }}
+ {% endif %}
+ |
+ {{ doc.get_receipt_type_display }} |
+ {{ doc.date|date:"d.m.Y" }} |
+ {{ doc.supplier_name|default:"-" }} |
+ {{ doc.created_by.username|default:"-" }} |
+
+ {% if doc.confirmed_by %}
+ {{ doc.confirmed_by.username }} ({{ doc.confirmed_at|date:"d.m H:i" }})
+ {% else %}
+ -
+ {% endif %}
+ |
+
+ {% empty %}
+ | Нет документов поступления |
+ {% endfor %}
+
+
+
+
+
+
+
+
📋 Строки документов поступления IncomingDocumentItem ({{ incoming_document_items.count }})
+
+
+
+
+ | ID |
+ Документ |
+ Товар |
+ Склад |
+ Кол-во |
+ Себестоимость |
+ Сумма |
+ Примечания |
+
+
+
+ {% for item in incoming_document_items %}
+
+ | {{ item.id }} |
+ {{ item.document.document_number }} |
+ {{ item.product.name }} ({{ item.product.sku }}) |
+ {{ item.document.warehouse.name }} |
+ {{ item.quantity }} |
+ {{ item.cost_price }} ₽ |
+ {{ item.total_cost }} ₽ |
+ {{ item.notes|default:"-" }} |
+
+ {% empty %}
+ | Нет строк документов поступления |
+ {% endfor %}
+
+
+
+
+
- Примечание: Показаны последние 100 записей для каждой таблицы.
+ Примечание: Показаны последние 100 записей для каждой таблицы.
Используйте фильтры для уточнения результатов.
diff --git a/myproject/inventory/templates/inventory/home.html b/myproject/inventory/templates/inventory/home.html
index cdaaafe..db5c0e5 100644
--- a/myproject/inventory/templates/inventory/home.html
+++ b/myproject/inventory/templates/inventory/home.html
@@ -239,16 +239,16 @@
-
+
-
+
-
Партии поступлений
+ Документы поступлений
История поступлений
diff --git a/myproject/inventory/urls.py b/myproject/inventory/urls.py
index 338d0bc..fb9146d 100644
--- a/myproject/inventory/urls.py
+++ b/myproject/inventory/urls.py
@@ -3,8 +3,6 @@ from django.urls import path
from .views import (
# Warehouse
WarehouseListView, WarehouseCreateView, WarehouseUpdateView, WarehouseDeleteView, SetDefaultWarehouseView,
- # IncomingBatch
- IncomingBatchListView, IncomingBatchDetailView,
# Sale
SaleListView, SaleCreateView, SaleUpdateView, SaleDeleteView, SaleDetailView,
# Inventory
@@ -64,9 +62,8 @@ urlpatterns = [
path('warehouses/
/delete/', WarehouseDeleteView.as_view(), name='warehouse-delete'),
path('warehouses//set-default/', SetDefaultWarehouseView.as_view(), name='warehouse-set-default'),
- # ==================== INCOMING BATCH ====================
- path('incoming-batches/', IncomingBatchListView.as_view(), name='incoming-batch-list'),
- path('incoming-batches//', IncomingBatchDetailView.as_view(), name='incoming-batch-detail'),
+ # ==================== INCOMING BATCH (УДАЛЕНО) ====================
+ # IncomingBatch и Incoming удалены. Используйте IncomingDocument вместо них.
# ==================== SALE ====================
path('sales/', SaleListView.as_view(), name='sale-list'),
diff --git a/myproject/inventory/utils/document_generator.py b/myproject/inventory/utils/document_generator.py
index 37dabde..bd89ccd 100644
--- a/myproject/inventory/utils/document_generator.py
+++ b/myproject/inventory/utils/document_generator.py
@@ -74,40 +74,30 @@ def _initialize_incoming_counter_if_needed():
Вызывается только если счетчик равен 0 (не инициализирован).
Thread-safe через select_for_update.
"""
- from inventory.models import IncomingBatch, IncomingDocument
+ from inventory.models import IncomingDocument
from django.db import transaction
-
+
# Быстрая проверка без блокировки - если счетчик существует и > 0, выходим
if DocumentCounter.objects.filter(
counter_type='incoming',
current_value__gt=0
).exists():
return
-
+
# Только если счетчик не инициализирован - делаем полную проверку с блокировкой
with transaction.atomic():
counter = DocumentCounter.objects.select_for_update().filter(
counter_type='incoming'
).first()
-
+
# Двойная проверка: возможно другой поток уже инициализировал
if counter and counter.current_value > 0:
return
-
- # Собираем все номера документов
- all_numbers = []
-
- # Номера из IncomingBatch
- batch_numbers = IncomingBatch.objects.filter(
+
+ # Собираем все номера документов из IncomingDocument
+ all_numbers = list(IncomingDocument.objects.filter(
document_number__startswith='IN-'
- ).values_list('document_number', flat=True)
- all_numbers.extend(batch_numbers)
-
- # Номера из IncomingDocument
- doc_numbers = IncomingDocument.objects.filter(
- document_number__startswith='IN-'
- ).values_list('document_number', flat=True)
- all_numbers.extend(doc_numbers)
+ ).values_list('document_number', flat=True))
if all_numbers:
# Извлекаем максимальный номер из всех форматов
@@ -135,7 +125,7 @@ def generate_incoming_document_number():
Thread-safe через DocumentCounter.
При первом использовании автоматически инициализирует DocumentCounter
- максимальным номером из существующих документов (IncomingBatch и IncomingDocument).
+ максимальным номером из существующих документов IncomingDocument.
Returns:
str: Сгенерированный номер документа (например, IN-000001)
diff --git a/myproject/inventory/views/__init__.py b/myproject/inventory/views/__init__.py
index 0db6955..bcf2bca 100644
--- a/myproject/inventory/views/__init__.py
+++ b/myproject/inventory/views/__init__.py
@@ -19,7 +19,7 @@ from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from .warehouse import WarehouseListView, WarehouseCreateView, WarehouseUpdateView, WarehouseDeleteView, SetDefaultWarehouseView
-from .batch import IncomingBatchListView, IncomingBatchDetailView, StockBatchListView, StockBatchDetailView
+from .batch import StockBatchListView, StockBatchDetailView
from .sale import SaleListView, SaleCreateView, SaleUpdateView, SaleDeleteView, SaleDetailView
from .inventory_ops import (
InventoryListView, InventoryCreateView, InventoryDetailView,
@@ -57,8 +57,6 @@ __all__ = [
'inventory_home',
# Warehouse
'WarehouseListView', 'WarehouseCreateView', 'WarehouseUpdateView', 'WarehouseDeleteView', 'SetDefaultWarehouseView',
- # IncomingBatch
- 'IncomingBatchListView', 'IncomingBatchDetailView',
# Sale
'SaleListView', 'SaleCreateView', 'SaleUpdateView', 'SaleDeleteView', 'SaleDetailView',
# Inventory
diff --git a/myproject/inventory/views/batch.py b/myproject/inventory/views/batch.py
index ef49b33..dcc8a81 100644
--- a/myproject/inventory/views/batch.py
+++ b/myproject/inventory/views/batch.py
@@ -1,47 +1,13 @@
# -*- coding: utf-8 -*-
"""
Batch views - READ ONLY
-- IncomingBatch (Партии поступлений)
- StockBatch (Партии товара на складе)
+
+ПРИМЕЧАНИЕ: IncomingBatch и Incoming удалены. Используйте IncomingDocument вместо них.
"""
from django.views.generic import ListView, DetailView
from django.contrib.auth.mixins import LoginRequiredMixin
-from ..models import IncomingBatch, Incoming, StockBatch, SaleBatchAllocation, WriteOff
-
-
-class IncomingBatchListView(LoginRequiredMixin, ListView):
- """Список всех партий поступлений товара"""
- model = IncomingBatch
- template_name = 'inventory/incoming_batch/batch_list.html'
- context_object_name = 'batches'
- paginate_by = 30
-
- def get_queryset(self):
- return IncomingBatch.objects.all().select_related('warehouse').order_by('-created_at')
-
- def get_context_data(self, **kwargs):
- context = super().get_context_data(**kwargs)
- # Добавляем количество товаров в каждую партию
- for batch in context['batches']:
- batch.items_count = batch.items.count()
- batch.total_quantity = sum(item.quantity for item in batch.items.all())
- return context
-
-
-class IncomingBatchDetailView(LoginRequiredMixin, DetailView):
- """Детальная информация по партии поступления"""
- model = IncomingBatch
- template_name = 'inventory/incoming_batch/batch_detail.html'
- context_object_name = 'batch'
-
- def get_context_data(self, **kwargs):
- context = super().get_context_data(**kwargs)
- batch = self.get_object()
-
- # Товары в этой партии
- context['items'] = batch.items.all().select_related('product', 'stock_batch')
-
- return context
+from ..models import StockBatch, SaleBatchAllocation, WriteOff
class StockBatchListView(LoginRequiredMixin, ListView):
diff --git a/myproject/inventory/views/debug_views.py b/myproject/inventory/views/debug_views.py
index 455ca64..fee76a4 100644
--- a/myproject/inventory/views/debug_views.py
+++ b/myproject/inventory/views/debug_views.py
@@ -5,7 +5,10 @@
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, WriteOff, WriteOffDocument, WriteOffDocumentItem
+from inventory.models import (
+ StockBatch, Stock, Reservation, Sale, SaleBatchAllocation, WriteOff,
+ WriteOffDocument, WriteOffDocumentItem, IncomingDocument, IncomingDocumentItem
+)
from orders.models import Order
from products.models import Product
from inventory.models import Warehouse
@@ -44,6 +47,9 @@ def debug_inventory_page(request):
writeoff_document_items = WriteOffDocumentItem.objects.select_related(
'product', 'document__warehouse'
).order_by('-id')
+ # Документы поступления
+ incoming_documents = IncomingDocument.objects.select_related('warehouse', 'created_by', 'confirmed_by').order_by('-date', '-created_at')
+ incoming_document_items = IncomingDocumentItem.objects.select_related('product', 'document__warehouse').order_by('-id')
orders = Order.objects.prefetch_related('items').order_by('-created_at')
# Применяем фильтры
@@ -56,6 +62,7 @@ def debug_inventory_page(request):
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)
+ incoming_document_items = incoming_document_items.filter(product_id=product_id)
orders = orders.filter(items__product_id=product_id).distinct()
else:
product = None
@@ -96,6 +103,8 @@ def debug_inventory_page(request):
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)
+ incoming_documents = incoming_documents.filter(warehouse_id=warehouse_id)
+ incoming_document_items = incoming_document_items.filter(document__warehouse_id=warehouse_id)
else:
warehouse = None
@@ -108,6 +117,8 @@ def debug_inventory_page(request):
writeoffs = writeoffs[:100]
writeoff_documents = writeoff_documents[:50]
writeoff_document_items = writeoff_document_items[:100]
+ incoming_documents = incoming_documents[:50]
+ incoming_document_items = incoming_document_items[:100]
orders = orders[:50]
# Списки для фильтров
@@ -123,6 +134,8 @@ def debug_inventory_page(request):
'writeoffs': writeoffs,
'writeoff_documents': writeoff_documents,
'writeoff_document_items': writeoff_document_items,
+ 'incoming_documents': incoming_documents,
+ 'incoming_document_items': incoming_document_items,
'orders': orders,
'products': products,
'warehouses': warehouses,
diff --git a/myproject/УЛУЧШИТЬ ПОСТУПЛЕНИЕ.md b/myproject/УЛУЧШИТЬ ПОСТУПЛЕНИЕ.md
index 91f966c..0299f64 100644
--- a/myproject/УЛУЧШИТЬ ПОСТУПЛЕНИЕ.md
+++ b/myproject/УЛУЧШИТЬ ПОСТУПЛЕНИЕ.md
@@ -56,11 +56,11 @@ def get_queryset(self):
## 🟡 Средний приоритет
-### 4. Рефакторинг модельной избыточности
-**Проблема:** IncomingDocument → IncomingBatch → Incoming создает 3 уровня данных
-**Решение:** Долгосрочная миграция к упрощенной структуре
+### 4. ✅ Рефакторинг модельной избыточности (ВЫПОЛНЕНО)
+**Проблема:** IncomingDocument → IncomingBatch → Incoming создавало 3 уровня данных
+**Решение:** ✅ Миграция к упрощенной структуре завершена
-**Архитектура будущего:**
+**Текущая архитектура:**
```
IncomingDocument (документ)
↓
@@ -69,12 +69,14 @@ IncomingDocumentItem (позиции документа)
StockBatch (напрямую создается из items при подтверждении)
```
-**Преимущества:**
-- Убрать промежуточные Incoming/IncomingBatch
-- Упростить код сигналов
-- Меньше JOIN'ов в запросах
+**Достигнутые результаты:**
+- ✅ Удалены промежуточные модели Incoming/IncomingBatch
+- ✅ Упрощен код сигналов (удалены create_stock_batch_on_incoming и update_stock_batch_on_incoming_edit)
+- ✅ Упрощен IncomingDocumentService.confirm_document() - напрямую создает StockBatch
+- ✅ Меньше JOIN'ов в запросах
+- ✅ Применены миграции БД для удаления таблиц
-**Миграция:** Постепенная, требует переписывания signals и services
+**Дата выполнения:** 2025-12-26
---
@@ -135,7 +137,7 @@ def add_items_bulk(document, items_data):
1. ✅ **Неделя 1:** Безопасность (права доступ, п.1)
2. ✅ **Неделя 2:** Тесты (критические пути, п.2)
3. ✅ **Неделя 3:** Производительность (N+1, п.3)
-4. 📅 **Квартал 2:** Рефакторинг моделей (п.4)
+4. ✅ **26.12.2025:** Рефакторинг моделей (п.4) - избавились от лишних сущностей
5. 📅 **По необходимости:** Bulk операции (п.5), документация (п.6)
---