Добавлена функциональность витрин для POS: модели, сервисы, UI

- Создана модель Showcase (витрина) привязанная к складу
- Расширена Reservation для поддержки витринных резервов
- Добавлены поля в OrderItem для маркировки витринных продаж
- Реализован ShowcaseManager с методами резервирования, продажи и разбора
- Обновлён админ-интерфейс для управления витринами
- Добавлена кнопка Витрина в POS (категории) и API для просмотра
- Добавлена кнопка На витрину в панели действий POS
- Миграции готовы к применению
This commit is contained in:
2025-11-16 21:12:22 +03:00
parent e98bf3cfb4
commit 8f6acfb364
12 changed files with 653 additions and 13 deletions

View File

@@ -359,10 +359,36 @@ class InventoryLine(models.Model):
super().save(*args, **kwargs)
class Showcase(models.Model):
"""
Витрина - место выкладки собранных букетов/комплектов.
Привязана к конкретному складу для учёта резервов.
"""
name = models.CharField(max_length=200, verbose_name="Название")
warehouse = models.ForeignKey(Warehouse, on_delete=models.CASCADE,
related_name='showcases', verbose_name="Склад")
description = models.TextField(blank=True, null=True, verbose_name="Описание")
is_active = models.BooleanField(default=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 = ['warehouse', 'name']
indexes = [
models.Index(fields=['warehouse']),
models.Index(fields=['is_active']),
]
def __str__(self):
return f"{self.name} ({self.warehouse.name})"
class Reservation(models.Model):
"""
Резервирование товара для заказа.
Отслеживает, какой товар зарезервирован за каким заказом.
Резервирование товара для заказа или витрины.
Отслеживает, какой товар зарезервирован за каким заказом или витриной.
"""
STATUS_CHOICES = [
('reserved', 'Зарезервирован'),
@@ -373,6 +399,10 @@ class Reservation(models.Model):
order_item = models.ForeignKey('orders.OrderItem', on_delete=models.CASCADE,
related_name='reservations', verbose_name="Позиция заказа",
null=True, blank=True)
showcase = models.ForeignKey(Showcase, on_delete=models.CASCADE,
related_name='reservations', verbose_name="Витрина",
null=True, blank=True,
help_text="Витрина, на которой выложен букет")
product = models.ForeignKey(Product, on_delete=models.CASCADE,
related_name='reservations', verbose_name="Товар")
warehouse = models.ForeignKey(Warehouse, on_delete=models.CASCADE,
@@ -393,11 +423,17 @@ class Reservation(models.Model):
models.Index(fields=['product', 'warehouse']),
models.Index(fields=['status']),
models.Index(fields=['order_item']),
models.Index(fields=['showcase']),
]
def __str__(self):
order_info = f" (заказ {self.order_item.order.order_number})" if self.order_item else ""
return f"Резерв {self.product.name}: {self.quantity} шт{order_info} [{self.get_status_display()}]"
if self.order_item:
context = f" (заказ {self.order_item.order.order_number})"
elif self.showcase:
context = f" (витрина {self.showcase.name})"
else:
context = ""
return f"Резерв {self.product.name}: {self.quantity} шт{context} [{self.get_status_display()}]"
class Stock(models.Model):