Реализовано преобразование временных комплектов в постоянные

Views (products/views/productkit_views.py):
- ProductKitMakePermanentView: view для преобразования временного комплекта
  * Доступен только для временных комплектов (is_temporary=True)
  * Позволяет отредактировать название, описание, категории, теги, цену
  * Вызывает метод make_permanent() модели
  * Перенаправляет на детальную страницу комплекта после успеха

URLs (products/urls.py):
- /products/kits/<pk>/make-permanent/ - страница преобразования

Templates:
- productkit_make_permanent.html: форма преобразования с составом и ценой
- order_detail.html: добавлена кнопка "Сделать постоянным" для временных комплектов

Теперь флорист может:
1. Увидеть временный комплект в заказе с badge "Временный"
2. Нажать "Сделать постоянным"
3. Отредактировать название, добавить категории
4. Сохранить - комплект появится в каталоге

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-08 15:19:56 +03:00
parent 0e220ed169
commit a1df188b2a
5 changed files with 225 additions and 0 deletions

View File

@@ -201,6 +201,10 @@
<span class="badge bg-info ms-1">Временный</span>
<br>
<small class="text-muted">Создан специально для этого заказа</small>
<br>
<a href="{% url 'products:productkit-make-permanent' item.product_kit.pk %}" class="btn btn-sm btn-outline-success mt-1">
<i class="bi bi-arrow-right-circle"></i> Сделать постоянным
</a>
{% endif %}
</td>
<td>{{ item.quantity }}</td>

View File

@@ -0,0 +1,35 @@
# Generated by Django 5.0.10 on 2025-11-08 11:52
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('orders', '0004_orderitem_is_custom_price'),
('products', '0005_remove_kititem_notes'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='productkit',
name='is_temporary',
field=models.BooleanField(default=False, help_text='Временные комплекты не показываются в каталоге и создаются для конкретного заказа', verbose_name='Временный комплект'),
),
migrations.AddField(
model_name='productkit',
name='order',
field=models.ForeignKey(blank=True, help_text='Заказ, для которого создан временный комплект', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='temporary_kits', to='orders.order', verbose_name='Заказ'),
),
migrations.AddIndex(
model_name='productkit',
index=models.Index(fields=['is_temporary'], name='products_pr_is_temp_e407a2_idx'),
),
migrations.AddIndex(
model_name='productkit',
index=models.Index(fields=['order'], name='products_pr_order_i_2b5675_idx'),
),
]

View File

@@ -0,0 +1,148 @@
{% extends 'base.html' %}
{% block title %}Преобразовать в постоянный комплект{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row mb-4">
<div class="col">
<h1>Преобразовать временный комплект в постоянный</h1>
<p class="text-muted">
Этот комплект был создан специально для заказа. Вы можете добавить его в каталог,
отредактировав название, описание и добавив категории.
</p>
</div>
</div>
<div class="row">
<div class="col-md-8">
<!-- Форма редактирования -->
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0">Информация о комплекте</h5>
</div>
<div class="card-body">
<form method="post">
{% csrf_token %}
<div class="mb-3">
<label for="id_name" class="form-label">Название *</label>
{{ form.name }}
{% if form.name.errors %}
<div class="invalid-feedback d-block">
{{ form.name.errors }}
</div>
{% endif %}
</div>
<div class="mb-3">
<label for="id_description" class="form-label">Описание</label>
{{ form.description }}
{% if form.description.errors %}
<div class="invalid-feedback d-block">
{{ form.description.errors }}
</div>
{% endif %}
</div>
<div class="mb-3">
<label for="id_categories" class="form-label">Категории</label>
{{ form.categories }}
{% if form.categories.errors %}
<div class="invalid-feedback d-block">
{{ form.categories.errors }}
</div>
{% endif %}
<small class="form-text text-muted">
Выберите категории, к которым относится этот комплект
</small>
</div>
<div class="mb-3">
<label for="id_tags" class="form-label">Теги</label>
{{ form.tags }}
{% if form.tags.errors %}
<div class="invalid-feedback d-block">
{{ form.tags.errors }}
</div>
{% endif %}
</div>
<div class="mb-3">
<label for="id_sale_price" class="form-label">Цена со скидкой</label>
{{ form.sale_price }}
{% if form.sale_price.errors %}
<div class="invalid-feedback d-block">
{{ form.sale_price.errors }}
</div>
{% endif %}
<small class="form-text text-muted">
Опционально. Если указано, комплект будет продаваться по этой цене.
</small>
</div>
<div class="d-grid gap-2 d-md-flex justify-content-md-between">
<a href="{% url 'orders:order-detail' kit.order.pk %}" class="btn btn-secondary">
<i class="bi bi-arrow-left"></i> Отмена
</a>
<button type="submit" class="btn btn-success">
<i class="bi bi-check-circle"></i> Сделать постоянным
</button>
</div>
</form>
</div>
</div>
</div>
<div class="col-md-4">
<!-- Компоненты комплекта (только для просмотра) -->
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0">Состав комплекта</h5>
</div>
<div class="card-body">
<table class="table table-sm">
<thead>
<tr>
<th>Товар</th>
<th>Количество</th>
</tr>
</thead>
<tbody>
{% for kit_item in kit_items %}
<tr>
<td>
{% if kit_item.variant_group %}
<span class="text-muted">[Варианты]</span> {{ kit_item.variant_group.name }}
{% elif kit_item.product %}
{{ kit_item.product.name }}
{% endif %}
</td>
<td>{{ kit_item.quantity }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- Ценообразование -->
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0">Ценообразование</h5>
</div>
<div class="card-body">
<div class="row mb-2">
<div class="col-6"><strong>Базовая цена:</strong></div>
<div class="col-6 text-end">{{ kit.base_price }} руб.</div>
</div>
<div class="row mb-2">
<div class="col-6"><strong>Итоговая цена:</strong></div>
<div class="col-6 text-end"><strong>{{ kit.actual_price }} руб.</strong></div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -26,6 +26,7 @@ urlpatterns = [
path('kits/<int:pk>/', views.ProductKitDetailView.as_view(), name='productkit-detail'),
path('kits/<int:pk>/update/', views.ProductKitUpdateView.as_view(), name='productkit-update'),
path('kits/<int:pk>/delete/', views.ProductKitDeleteView.as_view(), name='productkit-delete'),
path('kits/<int:pk>/make-permanent/', views.ProductKitMakePermanentView.as_view(), name='productkit-make-permanent'),
# Photo management for ProductKit
path('kits/photo/<int:pk>/delete/', views.productkit_photo_delete, name='productkit-photo-delete'),

View File

@@ -304,3 +304,40 @@ class ProductKitDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteVi
def get_success_url(self):
messages.success(self.request, f'Комплект "{self.object.name}" успешно удален!')
return reverse_lazy('products:productkit-list')
class ProductKitMakePermanentView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
"""
View для преобразования временного комплекта в постоянный.
Позволяет отредактировать название, добавить категории, теги перед сохранением.
"""
model = ProductKit
template_name = 'products/productkit_make_permanent.html'
context_object_name = 'kit'
permission_required = 'products.change_productkit'
fields = ['name', 'description', 'categories', 'tags', 'sale_price']
def get_queryset(self):
# Только временные комплекты можно преобразовать
return super().get_queryset().filter(is_temporary=True)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['kit_items'] = self.object.kit_items.all().select_related('product', 'variant_group')
context['productkit_photos'] = self.object.photos.all().order_by('order', 'created_at')
return context
def form_valid(self, form):
# Преобразуем в постоянный
if self.object.make_permanent():
messages.success(
self.request,
f'Комплект "{self.object.name}" преобразован в постоянный и теперь доступен в каталоге!'
)
else:
messages.warning(self.request, f'Комплект "{self.object.name}" уже является постоянным.')
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy('products:productkit-detail', kwargs={'pk': self.object.pk})