refactor: Создать базовый класс BaseProductEntity и реструктурировать Product/ProductKit

Основные изменения:

## Модели (models.py)
- Создан абстрактный класс BaseProductEntity с общими полями:
  * Идентификация: name, sku, slug
  * Описания: description, short_description (новое поле)
  * Статус: is_active, timestamps, soft delete
  * Managers: objects, all_objects, active

- Product:
  * Унаследован от BaseProductEntity
  * sale_price переименован в price (основная цена)
  * Добавлено новое поле sale_price (цена со скидкой, nullable)
  * Добавлено property actual_price

- ProductKit:
  * Унаследован от BaseProductEntity
  * fixed_price переименован в price (ручная цена)
  * pricing_method: 'fixed' → 'manual'
  * Добавлено поле sale_price (цена со скидкой)
  * Добавлено поле cost_price (nullable)
  * Добавлены properties: calculated_price, actual_price
  * Обновлен calculate_price_with_substitutions()

## Forms (forms.py)
- ProductForm: добавлено short_description, price, sale_price
- ProductKitForm: добавлено short_description, cost_price, price, sale_price

## Admin (admin.py)
- ProductAdmin: обновлены list_display и fieldsets с новыми полями
- ProductKitAdmin: добавлены fieldsets, метод get_price_display()

## Templates
- product_form.html: добавлены поля price, sale_price, short_description
- product_detail.html: показывает зачеркнутую цену + скидку + бейджик "Акция"
- product_list.html: отображение цен со скидкой и бейджиком "Акция"
- all_products_list.html: единообразное отображение цен
- productkit_detail.html: отображение скидок с бейджиком "Акция"

## API (api_views.py)
- Обновлены все endpoints для использования поля price вместо sale_price

Результат: единообразная архитектура с поддержкой скидок, DRY-принцип,
логичные названия полей, красивое отображение акций в UI.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-31 00:49:01 +03:00
parent aec884240e
commit d92045c4c4
9 changed files with 555 additions and 209 deletions

View File

@@ -141,10 +141,13 @@
{% endif %}
</td>
<td>
{% if item.item_type == 'product' %}
{{ item.sale_price|floatformat:2 }} руб.
{% if item.sale_price %}
<span class="text-decoration-line-through text-muted small">{{ item.price|floatformat:2 }} руб.</span>
<br>
<strong class="text-danger">{{ item.sale_price|floatformat:2 }} руб.</strong>
<span class="badge bg-danger ms-1">Акция</span>
{% else %}
{{ item.get_sale_price|floatformat:2 }} руб.
<strong>{{ item.actual_price|floatformat:2 }} руб.</strong>
{% endif %}
</td>
<td>

View File

@@ -158,8 +158,16 @@
<td>{{ product.cost_price }} руб.</td>
</tr>
<tr>
<th>Цена продажи:</th>
<td>{{ product.sale_price }} руб.</td>
<th>Цена:</th>
<td>
{% if product.sale_price %}
<span class="text-decoration-line-through text-muted">{{ product.price }} руб.</span>
<strong class="text-danger fs-5">{{ product.sale_price }} руб.</strong>
<span class="badge bg-danger ms-2">Акция</span>
{% else %}
<strong class="fs-5">{{ product.price }} руб.</strong>
{% endif %}
</td>
</tr>
<tr>
<th>В наличии:</th>

View File

@@ -79,6 +79,16 @@
{% endif %}
</div>
<!-- Краткое описание -->
<div class="mb-3">
<label for="id_short_description" class="form-label">Краткое описание</label>
{{ form.short_description }}
<small class="form-text text-muted">Используется для карточек товаров, превью и площадок</small>
{% if form.short_description.errors %}
<div class="text-danger">{{ form.short_description.errors }}</div>
{% endif %}
</div>
<!-- Единица измерения и Статус в один ряд -->
<div class="row mb-3">
<div class="col-md-8">
@@ -114,7 +124,7 @@
<div class="row mb-3">
<div class="col-md-6">
{{ form.cost_price.label_tag }}
<label for="id_cost_price" class="form-label">Себестоимость</label>
{{ form.cost_price }}
{% if form.cost_price.help_text %}
<small class="form-text text-muted">{{ form.cost_price.help_text }}</small>
@@ -124,11 +134,20 @@
{% endif %}
</div>
<div class="col-md-6">
{{ form.sale_price.label_tag }}
{{ form.sale_price }}
{% if form.sale_price.help_text %}
<small class="form-text text-muted">{{ form.sale_price.help_text }}</small>
<label for="id_price" class="form-label fw-bold">Основная цена <span class="text-danger">*</span></label>
{{ form.price }}
<small class="form-text text-muted">Цена продажи товара</small>
{% if form.price.errors %}
<div class="text-danger">{{ form.price.errors }}</div>
{% endif %}
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="id_sale_price" class="form-label">Цена со скидкой</label>
{{ form.sale_price }}
<small class="form-text text-muted">Необязательно. Если задана, товар будет продаваться по этой цене (дешевле основной)</small>
{% if form.sale_price.errors %}
<div class="text-danger">{{ form.sale_price.errors }}</div>
{% endif %}

View File

@@ -50,7 +50,16 @@
-
{% endif %}
</td>
<td>{{ product.sale_price }} руб.</td>
<td>
{% if product.sale_price %}
<span class="text-decoration-line-through text-muted small">{{ product.price }} руб.</span>
<br>
<strong class="text-danger">{{ product.sale_price }} руб.</strong>
<span class="badge bg-danger ms-1">Акция</span>
{% else %}
<strong>{{ product.price }} руб.</strong>
{% endif %}
</td>
<td>
{% if product.in_stock %}
<span class="badge bg-success"><i class="bi bi-check-circle"></i> В наличии</span>

View File

@@ -47,9 +47,15 @@
</dd>
{% endif %}
<dt class="col-sm-4">Цена продажи:</dt>
<dt class="col-sm-4">Цена:</dt>
<dd class="col-sm-8">
<strong class="text-success fs-5">{{ kit.get_sale_price|floatformat:2 }} ₽</strong>
{% if kit.sale_price %}
<span class="text-decoration-line-through text-muted">{{ kit.calculated_price|floatformat:2 }} ₽</span>
<strong class="text-danger fs-5">{{ kit.sale_price|floatformat:2 }} ₽</strong>
<span class="badge bg-danger ms-2">Акция</span>
{% else %}
<strong class="text-success fs-5">{{ kit.actual_price|floatformat:2 }} ₽</strong>
{% endif %}
</dd>
<dt class="col-sm-4">Себестоимость:</dt>
@@ -62,9 +68,9 @@
<span class="badge bg-info text-dark">{{ kit.get_pricing_method_display }}</span>
</dd>
{% if kit.fixed_price %}
<dt class="col-sm-4">Фиксированная цена:</dt>
<dd class="col-sm-8">{{ kit.fixed_price }} ₽</dd>
{% if kit.price %}
<dt class="col-sm-4">Ручная цена:</dt>
<dd class="col-sm-8">{{ kit.price }} ₽</dd>
{% endif %}
{% if kit.markup_percent %}