Feat: Add inline price editing for products in catalog

Implemented inline editing functionality for product prices directly in the catalog view with support for both regular and sale prices.

Features:
- Click-to-edit price fields with visual hover indicators
- Separate editing for price and sale_price fields
- Add/remove sale price with validation
- Real-time UI updates without page reload
- Permission-based access control
- Server-side validation for price constraints

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-25 01:23:46 +03:00
parent 0f212bda69
commit 22bf7e137d
4 changed files with 465 additions and 1 deletions

View File

@@ -1081,3 +1081,139 @@ def create_category_api(request):
'success': False,
'error': f'Ошибка при создании категории: {str(e)}'
}, status=500)
def update_product_price_api(request, pk):
"""
AJAX endpoint для изменения цены товара (inline editing в каталоге).
Принимает JSON:
{
"field": "price" | "sale_price",
"value": "150.50" | null
}
Возвращает JSON:
{
"success": true,
"price": "199.00",
"sale_price": "150.00" | null,
"actual_price": "150.00"
}
"""
if request.method != 'POST':
return JsonResponse({
'success': False,
'error': 'Метод не поддерживается'
}, status=405)
# Проверка прав доступа
if not request.user.has_perm('products.change_product'):
return JsonResponse({
'success': False,
'error': 'У вас нет прав для изменения цен товаров'
}, status=403)
try:
import json
from decimal import Decimal, InvalidOperation
data = json.loads(request.body)
field = data.get('field')
value = data.get('value')
# Валидация поля
if field not in ['price', 'sale_price']:
return JsonResponse({
'success': False,
'error': 'Недопустимое поле. Разрешены: price, sale_price'
}, status=400)
# Получаем товар
product = Product.objects.get(pk=pk)
# Обработка значения
if value is None:
# Очистка sale_price
if field == 'sale_price':
product.sale_price = None
else:
return JsonResponse({
'success': False,
'error': 'Основная цена не может быть пустой'
}, status=400)
else:
# Валидация значения
try:
decimal_value = Decimal(str(value))
except (InvalidOperation, ValueError):
return JsonResponse({
'success': False,
'error': 'Некорректное числовое значение'
}, status=400)
# Проверка диапазона
if decimal_value <= 0:
return JsonResponse({
'success': False,
'error': 'Цена должна быть положительной'
}, status=400)
if decimal_value > Decimal('999999.99'):
return JsonResponse({
'success': False,
'error': 'Цена слишком большая (максимум 999999.99)'
}, status=400)
# Проверка десятичных знаков
if decimal_value.as_tuple().exponent < -2:
return JsonResponse({
'success': False,
'error': 'Максимум 2 знака после запятой'
}, status=400)
# Устанавливаем значение
if field == 'price':
product.price = decimal_value
# Проверка: sale_price должна быть меньше price
if product.sale_price and product.sale_price >= decimal_value:
return JsonResponse({
'success': False,
'error': 'Скидочная цена должна быть меньше обычной цены'
}, status=400)
else: # sale_price
# Проверка: sale_price должна быть меньше price
if decimal_value >= product.price:
return JsonResponse({
'success': False,
'error': 'Скидочная цена должна быть меньше обычной цены'
}, status=400)
product.sale_price = decimal_value
# Сохраняем
product.save(update_fields=[field])
# Возвращаем обновлённые данные
return JsonResponse({
'success': True,
'price': str(product.price),
'sale_price': str(product.sale_price) if product.sale_price else None,
'actual_price': str(product.actual_price)
})
except Product.DoesNotExist:
return JsonResponse({
'success': False,
'error': 'Товар не найден'
}, status=404)
except json.JSONDecodeError:
return JsonResponse({
'success': False,
'error': 'Некорректный JSON'
}, status=400)
except Exception as e:
logger.error(f'Ошибка при обновлении цены товара: {str(e)}')
return JsonResponse({
'success': False,
'error': f'Ошибка при обновлении цены: {str(e)}'
}, status=500)