Реализовано преобразование временных комплектов в постоянные
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:
@@ -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>
|
||||
|
||||
@@ -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'),
|
||||
),
|
||||
]
|
||||
@@ -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 %}
|
||||
@@ -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'),
|
||||
|
||||
@@ -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})
|
||||
|
||||
Reference in New Issue
Block a user