Compare commits
4 Commits
2bc70968c3
...
5a66d492c8
| Author | SHA1 | Date | |
|---|---|---|---|
| 5a66d492c8 | |||
| 6cd0a945de | |||
| 41e6c33683 | |||
| bf399996b8 |
@@ -340,17 +340,6 @@ class ProductKit(BaseProductEntity):
|
||||
self.save(update_fields=['is_temporary', 'order'])
|
||||
return True
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
"""Soft delete вместо hard delete - марк как удаленный"""
|
||||
self.is_deleted = True
|
||||
self.deleted_at = timezone.now()
|
||||
self.save(update_fields=['is_deleted', 'deleted_at'])
|
||||
return 1, {self.__class__._meta.label: 1}
|
||||
|
||||
def hard_delete(self):
|
||||
"""Полное удаление из БД (необратимо!)"""
|
||||
super().delete()
|
||||
|
||||
def create_snapshot(self):
|
||||
"""
|
||||
Создает снимок текущего состояния комплекта.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -506,6 +506,9 @@
|
||||
<a href="{% url 'products:productkit-detail' object.pk %}" class="btn btn-outline-secondary">
|
||||
Отмена
|
||||
</a>
|
||||
<a href="{% url 'products:productkit-create' %}?copy_from={{ object.pk }}" class="btn btn-warning text-white mx-2">
|
||||
<i class="bi bi-files me-1"></i>Копировать комплект
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary px-4">
|
||||
<i class="bi bi-check-circle me-1"></i>Сохранить изменения
|
||||
</button>
|
||||
|
||||
@@ -9,9 +9,10 @@ from django.shortcuts import redirect
|
||||
from django.db import transaction, IntegrityError
|
||||
|
||||
from user_roles.mixins import ManagerOwnerRequiredMixin
|
||||
from ..models import ProductKit, ProductCategory, ProductTag, ProductKitPhoto, Product, ProductVariantGroup, BouquetName
|
||||
from ..models import ProductKit, ProductCategory, ProductTag, ProductKitPhoto, Product, ProductVariantGroup, BouquetName, ProductSalesUnit
|
||||
from ..forms import ProductKitForm, KitItemFormSetCreate, KitItemFormSetUpdate
|
||||
from .utils import handle_photos
|
||||
import os
|
||||
|
||||
|
||||
class ProductKitListView(LoginRequiredMixin, ManagerOwnerRequiredMixin, ListView):
|
||||
@@ -97,6 +98,37 @@ class ProductKitCreateView(LoginRequiredMixin, ManagerOwnerRequiredMixin, Create
|
||||
form_class = ProductKitForm
|
||||
template_name = 'products/productkit_create.html'
|
||||
|
||||
def get_initial(self):
|
||||
initial = super().get_initial()
|
||||
copy_id = self.request.GET.get('copy_from')
|
||||
if copy_id:
|
||||
try:
|
||||
kit = ProductKit.objects.get(pk=copy_id)
|
||||
|
||||
# Generate unique name
|
||||
base_name = f"{kit.name} (Копия)"
|
||||
new_name = base_name
|
||||
counter = 1
|
||||
while ProductKit.objects.filter(name=new_name).exists():
|
||||
counter += 1
|
||||
new_name = f"{base_name} {counter}"
|
||||
|
||||
initial.update({
|
||||
'name': new_name,
|
||||
'description': kit.description,
|
||||
'short_description': kit.short_description,
|
||||
'categories': list(kit.categories.values_list('pk', flat=True)),
|
||||
'tags': list(kit.tags.values_list('pk', flat=True)),
|
||||
'sale_price': kit.sale_price,
|
||||
'price_adjustment_type': kit.price_adjustment_type,
|
||||
'price_adjustment_value': kit.price_adjustment_value,
|
||||
'external_category': kit.external_category,
|
||||
'status': 'active', # Default to active for new kits
|
||||
})
|
||||
except ProductKit.DoesNotExist:
|
||||
pass
|
||||
return initial
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
"""
|
||||
Обрабатываем POST данные и очищаем ID товаров/комплектов от префиксов.
|
||||
@@ -132,7 +164,6 @@ class ProductKitCreateView(LoginRequiredMixin, ManagerOwnerRequiredMixin, Create
|
||||
context['kititem_formset'] = KitItemFormSetCreate(self.request.POST, prefix='kititem')
|
||||
|
||||
# При ошибке валидации: извлекаем выбранные товары для предзагрузки в Select2
|
||||
from ..models import Product, ProductVariantGroup, ProductSalesUnit
|
||||
selected_products = {}
|
||||
selected_variants = {}
|
||||
selected_sales_units = {}
|
||||
@@ -195,7 +226,97 @@ class ProductKitCreateView(LoginRequiredMixin, ManagerOwnerRequiredMixin, Create
|
||||
context['selected_variants'] = selected_variants
|
||||
context['selected_sales_units'] = selected_sales_units
|
||||
else:
|
||||
context['kititem_formset'] = KitItemFormSetCreate(prefix='kititem')
|
||||
# COPY KIT LOGIC
|
||||
copy_id = self.request.GET.get('copy_from')
|
||||
initial_items = []
|
||||
selected_products = {}
|
||||
selected_variants = {}
|
||||
selected_sales_units = {}
|
||||
|
||||
if copy_id:
|
||||
try:
|
||||
source_kit = ProductKit.objects.get(pk=copy_id)
|
||||
for item in source_kit.kit_items.all():
|
||||
item_data = {
|
||||
'quantity': item.quantity,
|
||||
# Delete flag is false by default
|
||||
}
|
||||
|
||||
form_prefix = f"kititem-{len(initial_items)}"
|
||||
|
||||
if item.product:
|
||||
item_data['product'] = item.product
|
||||
# Select2 prefill
|
||||
product = item.product
|
||||
text = product.name
|
||||
if product.sku:
|
||||
text += f" ({product.sku})"
|
||||
actual_price = product.sale_price if product.sale_price else product.price
|
||||
selected_products[f"{form_prefix}-product"] = {
|
||||
'id': product.id,
|
||||
'text': text,
|
||||
'price': str(product.price) if product.price else None,
|
||||
'actual_price': str(actual_price) if actual_price else '0'
|
||||
}
|
||||
|
||||
if item.sales_unit:
|
||||
item_data['sales_unit'] = item.sales_unit
|
||||
# Select2 prefill
|
||||
sales_unit = item.sales_unit
|
||||
text = f"{sales_unit.name} ({sales_unit.product.name})"
|
||||
actual_price = sales_unit.sale_price if sales_unit.sale_price else sales_unit.price
|
||||
selected_sales_units[f"{form_prefix}-sales_unit"] = {
|
||||
'id': sales_unit.id,
|
||||
'text': text,
|
||||
'price': str(sales_unit.price) if sales_unit.price else None,
|
||||
'actual_price': str(actual_price) if actual_price else '0'
|
||||
}
|
||||
|
||||
if item.variant_group:
|
||||
item_data['variant_group'] = item.variant_group
|
||||
# Select2 prefill
|
||||
variant_group = ProductVariantGroup.objects.prefetch_related(
|
||||
'items__product'
|
||||
).get(id=item.variant_group.id)
|
||||
variant_price = variant_group.price or 0
|
||||
count = variant_group.items.count()
|
||||
selected_variants[f"{form_prefix}-variant_group"] = {
|
||||
'id': variant_group.id,
|
||||
'text': f"{variant_group.name} ({count} вариантов)",
|
||||
'price': str(variant_price),
|
||||
'actual_price': str(variant_price),
|
||||
'type': 'variant',
|
||||
'count': count
|
||||
}
|
||||
|
||||
initial_items.append(item_data)
|
||||
except ProductKit.DoesNotExist:
|
||||
pass
|
||||
|
||||
if initial_items:
|
||||
context['kititem_formset'] = KitItemFormSetCreate(
|
||||
prefix='kititem',
|
||||
initial=initial_items
|
||||
)
|
||||
context['kititem_formset'].extra = len(initial_items)
|
||||
else:
|
||||
context['kititem_formset'] = KitItemFormSetCreate(prefix='kititem')
|
||||
|
||||
# Pass Select2 data to context
|
||||
context['selected_products'] = selected_products
|
||||
context['selected_variants'] = selected_variants
|
||||
context['selected_sales_units'] = selected_sales_units
|
||||
|
||||
# Pass source photos if copying
|
||||
if copy_id:
|
||||
try:
|
||||
source_kit = ProductKit.objects.prefetch_related('photos').get(pk=copy_id)
|
||||
photos = source_kit.photos.all().order_by('order')
|
||||
print(f"DEBUG: Found {photos.count()} source photos for kit {copy_id}")
|
||||
context['source_photos'] = photos
|
||||
except ProductKit.DoesNotExist:
|
||||
print(f"DEBUG: Source kit {copy_id} not found")
|
||||
pass
|
||||
|
||||
# Количество названий букетов в базе
|
||||
context['bouquet_names_count'] = BouquetName.objects.count()
|
||||
@@ -235,6 +356,48 @@ class ProductKitCreateView(LoginRequiredMixin, ManagerOwnerRequiredMixin, Create
|
||||
# Обработка фотографий
|
||||
handle_photos(self.request, self.object, ProductKitPhoto, 'kit')
|
||||
|
||||
# Handle copied photos
|
||||
copied_photo_ids = self.request.POST.getlist('copied_photos')
|
||||
print(f"DEBUG: copied_photo_ids in POST: {copied_photo_ids}")
|
||||
|
||||
if copied_photo_ids:
|
||||
from django.core.files.base import ContentFile
|
||||
original_photos = ProductKitPhoto.objects.filter(id__in=copied_photo_ids)
|
||||
print(f"DEBUG: Found {original_photos.count()} original photos to copy")
|
||||
|
||||
# Get max order from existing photos (uploaded via handle_photos)
|
||||
from django.db.models import Max
|
||||
max_order = self.object.photos.aggregate(Max('order'))['order__max']
|
||||
next_order = 0 if max_order is None else max_order + 1
|
||||
print(f"DEBUG: Starting order for copies: {next_order}")
|
||||
|
||||
for photo in original_photos:
|
||||
try:
|
||||
# Open the original image file
|
||||
if photo.image:
|
||||
print(f"DEBUG: Processing photo {photo.id}: {photo.image.name}")
|
||||
with photo.image.open('rb') as f:
|
||||
image_content = f.read()
|
||||
|
||||
# Create a new ContentFile
|
||||
new_image_name = f"copy_{self.object.id}_{os.path.basename(photo.image.name)}"
|
||||
print(f"DEBUG: New image name: {new_image_name}")
|
||||
|
||||
# Create new photo instance
|
||||
new_photo = ProductKitPhoto(kit=self.object, order=next_order)
|
||||
# Save the image file (this also saves the model instance)
|
||||
new_photo.image.save(new_image_name, ContentFile(image_content))
|
||||
print(f"DEBUG: Successfully saved copy for photo {photo.id}")
|
||||
|
||||
next_order += 1
|
||||
else:
|
||||
print(f"DEBUG: Photo {photo.id} has no image file")
|
||||
except Exception as e:
|
||||
print(f"Error copying photo {photo.id}: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
continue
|
||||
|
||||
messages.success(
|
||||
self.request,
|
||||
f'Комплект "{self.object.name}" успешно создан!'
|
||||
|
||||
Reference in New Issue
Block a user