Улучшения инвентаризации: автоматическое проведение документов, оптимизация запросов и улучшения 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:
@@ -327,6 +327,14 @@ class Inventory(models.Model):
|
||||
|
||||
warehouse = models.ForeignKey(Warehouse, on_delete=models.CASCADE,
|
||||
related_name='inventories', verbose_name="Склад")
|
||||
document_number = models.CharField(
|
||||
max_length=100,
|
||||
unique=True,
|
||||
db_index=True,
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name="Номер документа"
|
||||
)
|
||||
date = models.DateTimeField(auto_now_add=True, verbose_name="Дата инвентаризации")
|
||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES,
|
||||
default='draft', verbose_name="Статус")
|
||||
@@ -338,8 +346,13 @@ class Inventory(models.Model):
|
||||
verbose_name = "Инвентаризация"
|
||||
verbose_name_plural = "Инвентаризации"
|
||||
ordering = ['-date']
|
||||
indexes = [
|
||||
models.Index(fields=['document_number']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
if self.document_number:
|
||||
return f"{self.document_number} - {self.warehouse.name} ({self.date.strftime('%Y-%m-%d')})"
|
||||
return f"Инвентаризация {self.warehouse.name} ({self.date.strftime('%Y-%m-%d')})"
|
||||
|
||||
|
||||
@@ -354,9 +367,11 @@ class InventoryLine(models.Model):
|
||||
quantity_system = models.DecimalField(max_digits=10, decimal_places=3,
|
||||
verbose_name="Количество в системе")
|
||||
quantity_fact = models.DecimalField(max_digits=10, decimal_places=3,
|
||||
verbose_name="Фактическое количество")
|
||||
verbose_name="Подсчитано (факт, свободные)",
|
||||
help_text="Количество свободных товаров, подсчитанных физически")
|
||||
difference = models.DecimalField(max_digits=10, decimal_places=3,
|
||||
default=0, verbose_name="Разница (факт - система)",
|
||||
default=0, verbose_name="Итоговая разница",
|
||||
help_text="(Подсчитано + Зарезервировано) - Всего на складе",
|
||||
editable=False)
|
||||
processed = models.BooleanField(default=False,
|
||||
verbose_name="Обработана (создана операция)")
|
||||
@@ -370,7 +385,32 @@ class InventoryLine(models.Model):
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# Автоматически рассчитываем разницу
|
||||
self.difference = self.quantity_fact - self.quantity_system
|
||||
# Формула: (quantity_fact + quantity_reserved) - quantity_available
|
||||
# Где quantity_fact - подсчитанные свободные товары
|
||||
# Для расчета нужны quantity_reserved и quantity_available из Stock
|
||||
# Если они не переданы в kwargs, получаем из Stock
|
||||
quantity_reserved = kwargs.pop('quantity_reserved', None)
|
||||
quantity_available = kwargs.pop('quantity_available', None)
|
||||
|
||||
if quantity_reserved is None or quantity_available is None:
|
||||
# Получаем из Stock для расчета
|
||||
from inventory.models import Stock
|
||||
stock = Stock.objects.filter(
|
||||
product=self.product,
|
||||
warehouse=self.inventory.warehouse
|
||||
).first()
|
||||
if stock:
|
||||
stock.refresh_from_batches()
|
||||
quantity_reserved = stock.quantity_reserved
|
||||
quantity_available = stock.quantity_available
|
||||
|
||||
# Вычисляем разницу по новой формуле: (quantity_fact + quantity_reserved) - quantity_available
|
||||
if quantity_reserved is not None and quantity_available is not None:
|
||||
self.difference = (self.quantity_fact + quantity_reserved) - quantity_available
|
||||
else:
|
||||
# Fallback на старую формулу, если Stock недоступен (не должно происходить в нормальной работе)
|
||||
self.difference = self.quantity_fact - self.quantity_system
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
@@ -762,6 +802,7 @@ class DocumentCounter(models.Model):
|
||||
('transfer', 'Перемещение товара'),
|
||||
('writeoff', 'Списание товара'),
|
||||
('incoming', 'Поступление товара'),
|
||||
('inventory', 'Инвентаризация'),
|
||||
]
|
||||
|
||||
counter_type = models.CharField(
|
||||
@@ -954,6 +995,16 @@ class WriteOffDocument(models.Model):
|
||||
verbose_name="Примечания"
|
||||
)
|
||||
|
||||
# Связь с инвентаризацией
|
||||
inventory = models.ForeignKey(
|
||||
'Inventory',
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='writeoff_documents',
|
||||
verbose_name="Инвентаризация"
|
||||
)
|
||||
|
||||
# Аудит
|
||||
created_by = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
@@ -1168,6 +1219,16 @@ class IncomingDocument(models.Model):
|
||||
verbose_name="Примечания"
|
||||
)
|
||||
|
||||
# Связь с инвентаризацией
|
||||
inventory = models.ForeignKey(
|
||||
'Inventory',
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='incoming_documents',
|
||||
verbose_name="Инвентаризация"
|
||||
)
|
||||
|
||||
# Аудит
|
||||
created_by = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
|
||||
Reference in New Issue
Block a user