feat(discounts, orders): рефакторинг системы скидок - единый источник правды

- Добавлен combine_mode в форму создания/редактирования скидок
- Добавлена колонка "Объединение" в список скидок с иконками
- Добавлен фильтр по режиму объединения скидок
- Добавлена валидация: только одна exclusive скидка на заказ
- Удалены дублирующие поля из Order и OrderItem:
  - applied_discount, applied_promo_code, discount_amount
- Скидки теперь хранятся только в DiscountApplication
- Добавлены свойства для обратной совместимости

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-11 13:46:02 +03:00
parent cd758a0645
commit c070e42cab
9 changed files with 192 additions and 112 deletions

View File

@@ -101,6 +101,22 @@
</div>
</div>
<div class="mb-3">
<label for="id_combine_mode" class="form-label">Режим объединения с другими скидками</label>
<select class="form-select" id="id_combine_mode" name="combine_mode">
<option value="max_only" {% if form.combine_mode.value == 'max_only' or not form.combine_mode.value %}selected{% endif %}>
🏆 Только максимум (применяется лучшая скидка)
</option>
<option value="stack" {% if form.combine_mode.value == 'stack' %}selected{% endif %}>
📚 Складывать (суммировать с другими)
</option>
<option value="exclusive" {% if form.combine_mode.value == 'exclusive' %}selected{% endif %}>
🚫 Исключающая (отменяет остальные скидки)
</option>
</select>
<div class="form-text">Как эта скидка взаимодействует с другими активными скидками</div>
</div>
<!-- Ограничения -->
<h5 class="mb-3 mt-4">Ограничения</h5>
<div class="row mb-3">

View File

@@ -60,6 +60,15 @@
<option value="inactive" {% if current_filters.is_active == 'inactive' %}selected{% endif %}>Неактивные</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label">Объединение</label>
<select name="combine_mode" class="form-select">
<option value="">Все</option>
<option value="stack" {% if current_filters.combine_mode == 'stack' %}selected{% endif %}>Складывать</option>
<option value="max_only" {% if current_filters.combine_mode == 'max_only' %}selected{% endif %}>Максимум</option>
<option value="exclusive" {% if current_filters.combine_mode == 'exclusive' %}selected{% endif %}>Исключающая</option>
</select>
</div>
<div class="col-md-3 d-flex align-items-end">
<button type="submit" class="btn btn-primary me-2">
<i class="bi bi-funnel"></i> Применить
@@ -84,6 +93,7 @@
<th>Значение</th>
<th>Область</th>
<th>Авто</th>
<th>Объединение</th>
<th>Статус</th>
<th>Промокоды</th>
<th>Использований</th>
@@ -129,6 +139,15 @@
<i class="bi bi-dash-circle text-muted"></i>
{% endif %}
</td>
<td>
{% if discount.combine_mode == 'stack' %}
<span class="badge bg-secondary" title="Складывать (суммировать)">📚 Склад.</span>
{% elif discount.combine_mode == 'max_only' %}
<span class="badge bg-info" title="Только максимум">🏆 Макс.</span>
{% elif discount.combine_mode == 'exclusive' %}
<span class="badge bg-danger" title="Исключающая (отменяет остальные)">🚫 Исключ.</span>
{% endif %}
</td>
<td>
{% if discount.is_active %}
<span class="badge bg-success">Активна</span>
@@ -180,10 +199,10 @@
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1{% if current_filters.search %}&search={{ current_filters.search }}{% endif %}{% if current_filters.type %}&type={{ current_filters.type }}{% endif %}{% if current_filters.scope %}&scope={{ current_filters.scope }}{% endif %}{% if current_filters.is_active %}&is_active={{ current_filters.is_active }}{% endif %}">&laquo;</a>
<a class="page-link" href="?page=1{% if current_filters.search %}&search={{ current_filters.search }}{% endif %}{% if current_filters.type %}&type={{ current_filters.type }}{% endif %}{% if current_filters.scope %}&scope={{ current_filters.scope }}{% endif %}{% if current_filters.is_active %}&is_active={{ current_filters.is_active }}{% endif %}{% if current_filters.combine_mode %}&combine_mode={{ current_filters.combine_mode }}{% endif %}">&laquo;</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if current_filters.search %}&search={{ current_filters.search }}{% endif %}{% if current_filters.type %}&type={{ current_filters.type }}{% endif %}{% if current_filters.scope %}&scope={{ current_filters.scope }}{% endif %}{% if current_filters.is_active %}&is_active={{ current_filters.is_active }}{% endif %}">&lsaquo;</a>
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if current_filters.search %}&search={{ current_filters.search }}{% endif %}{% if current_filters.type %}&type={{ current_filters.type }}{% endif %}{% if current_filters.scope %}&scope={{ current_filters.scope }}{% endif %}{% if current_filters.is_active %}&is_active={{ current_filters.is_active }}{% endif %}{% if current_filters.combine_mode %}&combine_mode={{ current_filters.combine_mode }}{% endif %}">&lsaquo;</a>
</li>
{% endif %}
@@ -193,10 +212,10 @@
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if current_filters.search %}&search={{ current_filters.search }}{% endif %}{% if current_filters.type %}&type={{ current_filters.type }}{% endif %}{% if current_filters.scope %}&scope={{ current_filters.scope }}{% endif %}{% if current_filters.is_active %}&is_active={{ current_filters.is_active }}{% endif %}">&rsaquo;</a>
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if current_filters.search %}&search={{ current_filters.search }}{% endif %}{% if current_filters.type %}&type={{ current_filters.type }}{% endif %}{% if current_filters.scope %}&scope={{ current_filters.scope }}{% endif %}{% if current_filters.is_active %}&is_active={{ current_filters.is_active }}{% endif %}{% if current_filters.combine_mode %}&combine_mode={{ current_filters.combine_mode }}{% endif %}">&rsaquo;</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if current_filters.search %}&search={{ current_filters.search }}{% endif %}{% if current_filters.type %}&type={{ current_filters.type }}{% endif %}{% if current_filters.scope %}&scope={{ current_filters.scope }}{% endif %}{% if current_filters.is_active %}&is_active={{ current_filters.is_active }}{% endif %}">&raquo;</a>
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if current_filters.search %}&search={{ current_filters.search }}{% endif %}{% if current_filters.type %}&type={{ current_filters.type }}{% endif %}{% if current_filters.scope %}&scope={{ current_filters.scope }}{% endif %}{% if current_filters.is_active %}&is_active={{ current_filters.is_active }}{% endif %}{% if current_filters.combine_mode %}&combine_mode={{ current_filters.combine_mode }}{% endif %}">&raquo;</a>
</li>
{% endif %}
</ul>