Реализована элегантная блокировка витринных букетов при добавлении в корзину, предотвращающая многократную продажу одного физического комплекта. ## Изменения в БД: - Добавлены поля в Reservation: cart_lock_expires_at, locked_by_user, cart_session_id - Созданы индексы для оптимизации запросов блокировок - Миграция 0006: добавление полей Soft Lock ## Backend (pos/views.py): - add_showcase_kit_to_cart: создание блокировки на 30 минут с проверкой конфликтов - remove_showcase_kit_from_cart: снятие блокировки при удалении из корзины - get_showcase_kits_api: возврат статусов блокировок (is_locked, locked_by_me) ## Frontend (terminal.js): - addToCart: AJAX запрос для создания блокировки, запрет qty > 1 - removeFromCart: автоматическое снятие блокировки - renderCart: желтый фон, badge "1 шт (витрина)", скрыты кнопки +/− - UI индикация: зеленый badge "В корзине" (свой), красный "Занят" (чужой) ## Автоматизация (inventory/tasks.py): - cleanup_expired_cart_locks: Celery periodic task (каждые 5 минут) - Автоматическое освобождение истекших блокировок (30 минут timeout) - Логирование очистки для мониторинга ## Маршруты (pos/urls.py): - POST /api/showcase-kits/<id>/add-to-cart/ - создание блокировки - POST /api/showcase-kits/<id>/remove-from-cart/ - снятие блокировки ## Документация: - ЗАПУСК.md: инструкция по запуску Celery Beat Преимущества: ✓ Предотвращает конфликты между кассирами ✓ Автоматическое освобождение при таймауте ✓ Понятный UX с визуальной индикацией ✓ Совместимость с существующей логикой резервирования 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
34 lines
2.7 KiB
Python
34 lines
2.7 KiB
Python
# -*- coding: utf-8 -*-
|
||
from django.urls import path
|
||
from . import views
|
||
|
||
app_name = 'pos'
|
||
|
||
urlpatterns = [
|
||
# POS терминал: главная страница (рендер HTML) [GET]
|
||
path('', views.pos_terminal, name='terminal'),
|
||
# Установить текущий склад для POS (сохранение в сессии) [POST]
|
||
path('api/set-warehouse/<int:warehouse_id>/', views.set_warehouse, name='set-warehouse'),
|
||
# Установить текущего клиента для POS (сохранение в Redis с TTL 2 часа) [POST]
|
||
path('api/set-customer/<int:customer_id>/', views.set_customer, name='set-customer'),
|
||
# Сохранить корзину POS (сохранение в Redis с TTL 2 часа) [POST]
|
||
path('api/save-cart/', views.save_cart, name='save-cart'),
|
||
# Получить товары и комплекты (пагинация, поиск, сортировка) [GET]
|
||
path('api/items/', views.get_items_api, name='items-api'),
|
||
# Получить список активных витрин [GET]
|
||
path('api/get-showcases/', views.get_showcases_api, name='get-showcases-api'),
|
||
# Получить актуальные витринные временные комплекты [GET]
|
||
path('api/showcase-kits/', views.get_showcase_kits_api, name='showcase-kits-api'),
|
||
# Добавить витринный комплект в корзину с блокировкой [POST]
|
||
path('api/showcase-kits/<int:kit_id>/add-to-cart/', views.add_showcase_kit_to_cart, name='add-showcase-kit-to-cart'),
|
||
# Снять блокировку витринного комплекта при удалении из корзины [POST]
|
||
path('api/showcase-kits/<int:kit_id>/remove-from-cart/', views.remove_showcase_kit_from_cart, name='remove-showcase-kit-from-cart'),
|
||
# Получить детали комплекта для редактирования [GET]
|
||
path('api/product-kits/<int:kit_id>/', views.get_product_kit_details, name='get-product-kit-details'),
|
||
# Обновить временный комплект (состав, фото, цены) [POST]
|
||
path('api/product-kits/<int:kit_id>/update/', views.update_product_kit, name='update-product-kit'),
|
||
# Разобрать витринный комплект (освободить резервы, установить статус discontinued) [POST]
|
||
path('api/product-kits/<int:kit_id>/disassemble/', views.disassemble_product_kit, name='disassemble-product-kit'),
|
||
# Создать временный комплект и зарезервировать на витрину [POST]
|
||
path('api/create-temp-kit/', views.create_temp_kit_to_showcase, name='create-temp-kit-api'),
|
||
] |