refactor: Добавить в_stock в API и улучшить загрузку данных в форме вариантов
Изменения в API: - Добавить поле in_stock в возвращаемые данные товаров - Добавить поддержку запроса товара по ID (параметр ?id=) для получения актуальных данных - Исправить дублирование информации одного товара на остальные в таблице вариантов Изменения в форме: - Добавить автоматическую загрузку данных товара при загрузке страницы для существующих строк - Правильно отображать артикул, цену и наличие для каждого товара в группе 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -61,37 +61,56 @@
|
||||
<table class="table table-sm table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width: 50px;">№</th>
|
||||
<th style="width: 40px; text-align: center;">№</th>
|
||||
<th>Товар</th>
|
||||
<th style="width: 100px;">Артикул</th>
|
||||
<th style="width: 90px;">Цена</th>
|
||||
<th style="width: 100px;">В наличии</th>
|
||||
<th style="width: 110px;">Действия</th>
|
||||
<th style="width: 110px;">Артикул</th>
|
||||
<th style="width: 100px;">Цена</th>
|
||||
<th style="width: 110px;">В наличии</th>
|
||||
<th style="width: 150px; text-align: right;">Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="items-tbody">
|
||||
{% for item_form in items_formset %}
|
||||
<tr class="item-row" data-form-index="{{ forloop.counter0 }}"{% if item_form.DELETE.value %} style="display: none;"{% endif %}>
|
||||
<td class="item-priority text-center fw-bold">{{ forloop.counter }}</td>
|
||||
<td>
|
||||
<!-- Приоритет (номер) -->
|
||||
<td class="item-priority text-center fw-bold align-middle">{{ forloop.counter }}</td>
|
||||
|
||||
<!-- Выбор товара -->
|
||||
<td class="align-middle">
|
||||
{{ item_form.product }}
|
||||
{% if item_form.product.errors %}
|
||||
<div class="text-danger small">{{ item_form.product.errors }}</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td><small class="text-muted" data-product-sku="-">-</small></td>
|
||||
<td><small class="text-muted" data-product-price="-">-</small></td>
|
||||
<td><small class="text-muted" data-product-stock="-">-</small></td>
|
||||
<td class="text-end">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary move-up-btn me-1" title="Вверх">
|
||||
|
||||
<!-- Артикул -->
|
||||
<td class="align-middle">
|
||||
<small class="text-muted" data-product-sku="-">-</small>
|
||||
</td>
|
||||
|
||||
<!-- Цена -->
|
||||
<td class="align-middle">
|
||||
<small class="text-muted" data-product-price="-">-</small>
|
||||
</td>
|
||||
|
||||
<!-- Статус наличия -->
|
||||
<td class="align-middle">
|
||||
<small class="text-muted" data-product-stock="-">-</small>
|
||||
</td>
|
||||
|
||||
<!-- Действия (стрелки + корзина) -->
|
||||
<td class="align-middle" style="padding-right: 8px;">
|
||||
<div class="btn-group btn-group-sm gap-1" role="group">
|
||||
<button type="button" class="btn btn-outline-secondary move-up-btn" title="Переместить вверх">
|
||||
<i class="bi bi-arrow-up"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary move-down-btn me-1" title="Вниз">
|
||||
<button type="button" class="btn btn-outline-secondary move-down-btn" title="Переместить вниз">
|
||||
<i class="bi bi-arrow-down"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger delete-btn" title="Удалить">
|
||||
<button type="button" class="btn btn-outline-danger delete-btn" title="Удалить товар">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
{{ item_form.priority }}
|
||||
{{ item_form.id }}
|
||||
{{ item_form.DELETE }}
|
||||
@@ -207,7 +226,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
|
||||
// Инициализируем для существующих строк
|
||||
container.querySelectorAll('.item-row').forEach(initSelect2ForRow);
|
||||
container.querySelectorAll('.item-row').forEach(row => {
|
||||
initSelect2ForRow(row);
|
||||
// Загружаем данные товара при загрузке страницы (с небольшой задержкой)
|
||||
setTimeout(() => updateRowData(row), 100);
|
||||
});
|
||||
|
||||
// ДОБАВЛЕНИЕ НОВОГО ТОВАРА
|
||||
document.getElementById('add-item-btn').addEventListener('click', function(e) {
|
||||
@@ -220,25 +243,33 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const newRow = document.createElement('tr');
|
||||
newRow.className = 'item-row';
|
||||
newRow.innerHTML = `
|
||||
<td class="item-priority text-center fw-bold">${newFormIndex + 1}</td>
|
||||
<td>
|
||||
<td class="item-priority text-center fw-bold align-middle">${newFormIndex + 1}</td>
|
||||
<td class="align-middle">
|
||||
<select name="items-${newFormIndex}-product" id="items-${newFormIndex}-product" class="form-control form-control-sm">
|
||||
<option value="">---------</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><small class="text-muted" data-product-sku="-">-</small></td>
|
||||
<td><small class="text-muted" data-product-price="-">-</small></td>
|
||||
<td><small class="text-muted" data-product-stock="-">-</small></td>
|
||||
<td class="text-end">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary move-up-btn me-1" title="Вверх">
|
||||
<td class="align-middle">
|
||||
<small class="text-muted" data-product-sku="-">-</small>
|
||||
</td>
|
||||
<td class="align-middle">
|
||||
<small class="text-muted" data-product-price="-">-</small>
|
||||
</td>
|
||||
<td class="align-middle">
|
||||
<small class="text-muted" data-product-stock="-">-</small>
|
||||
</td>
|
||||
<td class="align-middle" style="padding-right: 8px;">
|
||||
<div class="btn-group btn-group-sm gap-1" role="group">
|
||||
<button type="button" class="btn btn-outline-secondary move-up-btn" title="Переместить вверх">
|
||||
<i class="bi bi-arrow-up"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary move-down-btn me-1" title="Вниз">
|
||||
<button type="button" class="btn btn-outline-secondary move-down-btn" title="Переместить вниз">
|
||||
<i class="bi bi-arrow-down"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger delete-btn" title="Удалить">
|
||||
<button type="button" class="btn btn-outline-danger delete-btn" title="Удалить товар">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
<input type="hidden" name="items-${newFormIndex}-priority" value="${newFormIndex + 1}">
|
||||
<input type="hidden" name="items-${newFormIndex}-id" value="">
|
||||
<input type="checkbox" name="items-${newFormIndex}-DELETE">
|
||||
|
||||
@@ -15,6 +15,7 @@ def search_products_and_variants(request):
|
||||
|
||||
Параметры GET:
|
||||
- q: строка поиска (term в Select2)
|
||||
- id: ID товара для получения его данных
|
||||
- type: 'product' или 'variant' (опционально)
|
||||
- page: номер страницы для пагинации (по умолчанию 1)
|
||||
|
||||
@@ -25,7 +26,8 @@ def search_products_and_variants(request):
|
||||
"id": 1,
|
||||
"text": "Роза красная Freedom 50см (PROD-000001)",
|
||||
"sku": "PROD-000001",
|
||||
"price": "150.00"
|
||||
"price": "150.00",
|
||||
"in_stock": true
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
@@ -33,6 +35,25 @@ def search_products_and_variants(request):
|
||||
}
|
||||
}
|
||||
"""
|
||||
# Если передан ID товара - получаем его данные напрямую
|
||||
product_id = request.GET.get('id', '').strip()
|
||||
if product_id:
|
||||
try:
|
||||
product = Product.objects.get(id=int(product_id), is_active=True)
|
||||
return JsonResponse({
|
||||
'results': [{
|
||||
'id': product.id,
|
||||
'text': f"{product.name} ({product.sku})" if product.sku else product.name,
|
||||
'sku': product.sku,
|
||||
'price': str(product.sale_price) if product.sale_price else None,
|
||||
'in_stock': product.in_stock,
|
||||
'type': 'product'
|
||||
}],
|
||||
'pagination': {'more': False}
|
||||
})
|
||||
except (Product.DoesNotExist, ValueError):
|
||||
return JsonResponse({'results': [], 'pagination': {'more': False}})
|
||||
|
||||
query = request.GET.get('q', '').strip()
|
||||
search_type = request.GET.get('type', 'all')
|
||||
page = int(request.GET.get('page', 1))
|
||||
@@ -53,7 +74,7 @@ def search_products_and_variants(request):
|
||||
# Показываем последние добавленные активные товары
|
||||
products = Product.objects.filter(is_active=True)\
|
||||
.order_by('-created_at')[:page_size]\
|
||||
.values('id', 'name', 'sku', 'sale_price')
|
||||
.values('id', 'name', 'sku', 'sale_price', 'in_stock')
|
||||
|
||||
for product in products:
|
||||
text = product['name']
|
||||
@@ -64,7 +85,8 @@ def search_products_and_variants(request):
|
||||
'id': product['id'],
|
||||
'text': text,
|
||||
'sku': product['sku'],
|
||||
'price': str(product['sale_price']) if product['sale_price'] else None
|
||||
'price': str(product['sale_price']) if product['sale_price'] else None,
|
||||
'in_stock': product['in_stock']
|
||||
})
|
||||
|
||||
response_data = {
|
||||
@@ -125,7 +147,7 @@ def search_products_and_variants(request):
|
||||
start = (page - 1) * page_size
|
||||
end = start + page_size
|
||||
|
||||
products = products_query[start:end].values('id', 'name', 'sku', 'sale_price')
|
||||
products = products_query[start:end].values('id', 'name', 'sku', 'sale_price', 'in_stock')
|
||||
|
||||
for product in products:
|
||||
text = product['name']
|
||||
@@ -137,6 +159,7 @@ def search_products_and_variants(request):
|
||||
'text': text,
|
||||
'sku': product['sku'],
|
||||
'price': str(product['sale_price']) if product['sale_price'] else None,
|
||||
'in_stock': product['in_stock'],
|
||||
'type': 'product'
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user