Add Redis-based persistence for POS cart and customer selection
Implemented Redis caching with 2-hour TTL for POS session data: Backend changes: - Added Redis cache configuration in settings.py - Created save_cart() endpoint to persist cart state - Added cart and customer loading from Redis in pos_terminal() - Validates cart items (products/kits) still exist in DB - Added REDIS_HOST, REDIS_PORT, REDIS_DB to .env Frontend changes: - Added saveCartToRedis() with 500ms debounce - Cart auto-saves on add/remove/quantity change - Added cart initialization from Redis on page load - Enhanced customer button with two-line display and reset button - Red X button appears only for non-system customers Features: - Cart persists across page reloads (2 hour TTL) - Customer selection persists (2 hour TTL) - Independent cart per user+warehouse combination - Automatic cleanup of deleted items - Debounced saves to reduce server load 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -158,9 +158,12 @@ def pos_terminal(request):
|
||||
Товары загружаются прогрессивно через API при клике на категорию.
|
||||
Работает только с одним выбранным складом.
|
||||
"""
|
||||
from customers.models import Customer
|
||||
from django.core.cache import cache
|
||||
|
||||
# Получаем текущий склад для POS
|
||||
current_warehouse = get_pos_warehouse(request)
|
||||
|
||||
|
||||
if not current_warehouse:
|
||||
# Нет активных складов - показываем ошибку
|
||||
from django.contrib import messages
|
||||
@@ -174,11 +177,61 @@ def pos_terminal(request):
|
||||
'title': 'POS Terminal',
|
||||
}
|
||||
return render(request, 'pos/terminal.html', context)
|
||||
|
||||
|
||||
# Получаем или создаём системного клиента
|
||||
system_customer, _ = Customer.get_or_create_system_customer()
|
||||
|
||||
# Пытаемся получить сохраненного клиента из Redis
|
||||
selected_customer = None
|
||||
redis_key = f'pos:customer:{request.user.id}:{current_warehouse.id}'
|
||||
cached_customer_data = cache.get(redis_key)
|
||||
|
||||
if cached_customer_data:
|
||||
# Проверяем что клиент еще существует в БД
|
||||
try:
|
||||
customer = Customer.objects.get(id=cached_customer_data['customer_id'])
|
||||
selected_customer = {
|
||||
'id': customer.id,
|
||||
'name': customer.name
|
||||
}
|
||||
except Customer.DoesNotExist:
|
||||
# Клиент был удален - очищаем кэш
|
||||
cache.delete(redis_key)
|
||||
|
||||
# Если нет сохраненного клиента - используем системного
|
||||
if not selected_customer:
|
||||
selected_customer = {
|
||||
'id': system_customer.id,
|
||||
'name': system_customer.name
|
||||
}
|
||||
|
||||
# Пытаемся получить сохраненную корзину из Redis
|
||||
cart_redis_key = f'pos:cart:{request.user.id}:{current_warehouse.id}'
|
||||
cached_cart_data = cache.get(cart_redis_key)
|
||||
cart_data = {}
|
||||
|
||||
if cached_cart_data:
|
||||
# Валидируем товары и комплекты в корзине
|
||||
from products.models import Product, ProductKit
|
||||
|
||||
for cart_key, item in cached_cart_data.items():
|
||||
try:
|
||||
if item['type'] == 'product':
|
||||
# Проверяем что товар существует
|
||||
Product.objects.get(id=item['id'])
|
||||
cart_data[cart_key] = item
|
||||
elif item['type'] in ('kit', 'showcase_kit'):
|
||||
# Проверяем что комплект существует
|
||||
ProductKit.objects.get(id=item['id'])
|
||||
cart_data[cart_key] = item
|
||||
except (Product.DoesNotExist, ProductKit.DoesNotExist):
|
||||
# Товар или комплект удален - пропускаем
|
||||
continue
|
||||
|
||||
# Загружаем только категории
|
||||
categories_qs = ProductCategory.objects.filter(is_active=True)
|
||||
categories = [{'id': c.id, 'name': c.name} for c in categories_qs]
|
||||
|
||||
|
||||
# Список всех активных складов для модалки выбора
|
||||
warehouses = Warehouse.objects.filter(is_active=True).order_by('-is_default', 'name')
|
||||
warehouses_list = [{
|
||||
@@ -196,11 +249,92 @@ def pos_terminal(request):
|
||||
'name': current_warehouse.name
|
||||
},
|
||||
'warehouses': warehouses_list,
|
||||
'system_customer': {
|
||||
'id': system_customer.id,
|
||||
'name': system_customer.name
|
||||
},
|
||||
'selected_customer': selected_customer, # Текущий выбранный клиент (из Redis или системный)
|
||||
'cart_data': json.dumps(cart_data), # Сохраненная корзина из Redis
|
||||
'title': 'POS Terminal',
|
||||
}
|
||||
return render(request, 'pos/terminal.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(["POST"])
|
||||
def save_cart(request):
|
||||
"""
|
||||
Сохранить корзину POS в Redis для текущего пользователя и склада.
|
||||
TTL: 2 часа (7200 секунд)
|
||||
"""
|
||||
from django.core.cache import cache
|
||||
import json
|
||||
|
||||
# Получаем текущий склад
|
||||
current_warehouse = get_pos_warehouse(request)
|
||||
if not current_warehouse:
|
||||
return JsonResponse({'success': False, 'error': 'Не выбран активный склад'}, status=400)
|
||||
|
||||
try:
|
||||
# Получаем данные корзины из тела запроса
|
||||
body = json.loads(request.body)
|
||||
cart_data = body.get('cart', {})
|
||||
|
||||
# Валидация структуры данных корзины
|
||||
if not isinstance(cart_data, dict):
|
||||
return JsonResponse({'success': False, 'error': 'Неверный формат данных корзины'}, status=400)
|
||||
|
||||
# Сохраняем в Redis
|
||||
redis_key = f'pos:cart:{request.user.id}:{current_warehouse.id}'
|
||||
cache.set(redis_key, cart_data, timeout=7200) # 2 часа
|
||||
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'items_count': len(cart_data)
|
||||
})
|
||||
|
||||
except json.JSONDecodeError:
|
||||
return JsonResponse({'success': False, 'error': 'Неверный формат JSON'}, status=400)
|
||||
except Exception as e:
|
||||
return JsonResponse({'success': False, 'error': str(e)}, status=500)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(["POST"])
|
||||
def set_customer(request, customer_id):
|
||||
"""
|
||||
Сохранить выбранного клиента в Redis для текущего пользователя и склада.
|
||||
TTL: 2 часа (7200 секунд)
|
||||
"""
|
||||
from customers.models import Customer
|
||||
from django.core.cache import cache
|
||||
|
||||
# Получаем текущий склад
|
||||
current_warehouse = get_pos_warehouse(request)
|
||||
if not current_warehouse:
|
||||
return JsonResponse({'success': False, 'error': 'Не выбран активный склад'}, status=400)
|
||||
|
||||
# Проверяем, что клиент существует
|
||||
try:
|
||||
customer = Customer.objects.get(id=customer_id)
|
||||
except Customer.DoesNotExist:
|
||||
return JsonResponse({'success': False, 'error': 'Клиент не найден'}, status=404)
|
||||
|
||||
# Сохраняем в Redis
|
||||
redis_key = f'pos:customer:{request.user.id}:{current_warehouse.id}'
|
||||
customer_data = {
|
||||
'customer_id': customer.id,
|
||||
'customer_name': customer.name
|
||||
}
|
||||
cache.set(redis_key, customer_data, timeout=7200) # 2 часа
|
||||
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'customer_id': customer.id,
|
||||
'customer_name': customer.name
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(["POST"])
|
||||
def set_warehouse(request, warehouse_id):
|
||||
|
||||
Reference in New Issue
Block a user