Улучшения в моделях заказов и комплектов
## Изменения: ### 1. ProductKit - расчет цены для вариантов товаров - Добавлена обработка variant_group в методах расчета base_price - Теперь учитываются варианты товаров при расчете стоимости комплекта ### 2. DraftOrderService - упрощение логики автосохранения - Удалена проверка is_draft() при обновлении (позволяет обновлять заказы в других статусах) - Улучшена документация метода update_draft ### 3. Шаблоны и скрипты - Обновлены шаблоны форм создания/редактирования комплектов - Обновлены скрипты автосохранения 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -64,7 +64,7 @@ class DraftOrderService:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def update_draft(order_id, user, data):
|
def update_draft(order_id, user, data):
|
||||||
"""
|
"""
|
||||||
Обновляет существующий черновик заказа.
|
Обновляет существующий заказ (автосохранение).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
order_id (int): ID заказа
|
order_id (int): ID заказа
|
||||||
@@ -76,14 +76,11 @@ class DraftOrderService:
|
|||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
Order.DoesNotExist: Если заказ не найден
|
Order.DoesNotExist: Если заказ не найден
|
||||||
ValidationError: Если заказ не является черновиком или данные невалидны
|
ValidationError: Если данные невалидны
|
||||||
"""
|
"""
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
order = Order.objects.select_for_update().get(pk=order_id)
|
order = Order.objects.select_for_update().get(pk=order_id)
|
||||||
|
|
||||||
if not order.is_draft():
|
|
||||||
raise ValidationError("Можно обновлять только черновики заказов")
|
|
||||||
|
|
||||||
# Обновляем только переданные поля
|
# Обновляем только переданные поля
|
||||||
# ForeignKey поля требуют специальной обработки
|
# ForeignKey поля требуют специальной обработки
|
||||||
fk_fields = {
|
fk_fields = {
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
// Состояние модуля
|
// Состояние модуля
|
||||||
let autosaveTimer = null;
|
let autosaveTimer = null;
|
||||||
let isAutosaving = false;
|
let isAutosaving = false;
|
||||||
let isDraft = false;
|
|
||||||
let orderId = null;
|
let orderId = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,12 +42,6 @@
|
|||||||
}
|
}
|
||||||
orderId = urlMatch[1];
|
orderId = urlMatch[1];
|
||||||
|
|
||||||
// Проверяем, является ли заказ черновиком
|
|
||||||
isDraft = checkIfDraft();
|
|
||||||
if (!isDraft) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Инициализируем UI индикатора
|
// Инициализируем UI индикатора
|
||||||
initStatusIndicator();
|
initStatusIndicator();
|
||||||
|
|
||||||
@@ -56,25 +49,6 @@
|
|||||||
attachEventListeners();
|
attachEventListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Проверяет, является ли заказ черновиком
|
|
||||||
*/
|
|
||||||
function checkIfDraft() {
|
|
||||||
// Проверяем через data-атрибут на форме
|
|
||||||
const form = document.getElementById('order-form');
|
|
||||||
if (form && form.dataset.isDraft === 'true') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Проверяем через заголовок страницы
|
|
||||||
const title = document.querySelector('h1');
|
|
||||||
if (title && title.textContent.includes('черновик')) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Создает индикатор статуса автосохранения
|
* Создает индикатор статуса автосохранения
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -102,15 +102,6 @@
|
|||||||
<div class="col">
|
<div class="col">
|
||||||
<h1>{{ title }}</h1>
|
<h1>{{ title }}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto me-3 d-flex align-items-end gap-2">
|
|
||||||
<label for="{{ form.status.id_for_label }}" class="form-label mb-0" style="white-space: nowrap;">Статус:</label>
|
|
||||||
<div style="min-width: 200px;">
|
|
||||||
{{ form.status }}
|
|
||||||
{% if form.status.errors %}
|
|
||||||
<div class="text-danger small">{{ form.status.errors }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<a href="{% url 'orders:order-list' %}" class="btn btn-secondary">
|
<a href="{% url 'orders:order-list' %}" class="btn btn-secondary">
|
||||||
<i class="bi bi-arrow-left"></i> Назад к списку
|
<i class="bi bi-arrow-left"></i> Назад к списку
|
||||||
@@ -139,6 +130,17 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="{{ form.status.id_for_label }}" class="form-label">
|
||||||
|
Статус <span class="text-danger">*</span>
|
||||||
|
</label>
|
||||||
|
{{ form.status }}
|
||||||
|
{% if form.status.errors %}
|
||||||
|
<div class="text-danger">{{ form.status.errors }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1674,12 +1676,12 @@ if (!document.getElementById('notification-styles')) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Скрипты автосохранения -->
|
<!-- Скрипты автосохранения и создания черновиков -->
|
||||||
{% if is_draft %}
|
{% if order %}
|
||||||
<!-- Автосохранение для черновиков -->
|
<!-- Автосохранение при редактировании заказа -->
|
||||||
<script src="{% static 'orders/js/autosave.js' %}"></script>
|
<script src="{% static 'orders/js/autosave.js' %}"></script>
|
||||||
{% else %}
|
{% else %}
|
||||||
<!-- Автосоздание черновика при создании заказа -->
|
<!-- Автосоздание черновика при создании нового заказа -->
|
||||||
<script src="{% static 'orders/js/draft-creator.js' %}"></script>
|
<script src="{% static 'orders/js/draft-creator.js' %}"></script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -252,13 +252,6 @@ def autosave_draft_order(request, pk):
|
|||||||
'error': 'Заказ не найден'
|
'error': 'Заказ не найден'
|
||||||
}, status=404)
|
}, status=404)
|
||||||
|
|
||||||
# Проверяем, что это черновик
|
|
||||||
if not order.is_draft():
|
|
||||||
return JsonResponse({
|
|
||||||
'success': False,
|
|
||||||
'error': 'Можно автосохранять только черновики'
|
|
||||||
}, status=400)
|
|
||||||
|
|
||||||
# Используем DraftOrderService для обновления
|
# Используем DraftOrderService для обновления
|
||||||
order = DraftOrderService.update_draft(
|
order = DraftOrderService.update_draft(
|
||||||
order_id=pk,
|
order_id=pk,
|
||||||
|
|||||||
@@ -135,6 +135,10 @@ class ProductKit(BaseProductEntity):
|
|||||||
actual_price = item.product.actual_price or Decimal('0')
|
actual_price = item.product.actual_price or Decimal('0')
|
||||||
qty = item.quantity or Decimal('1')
|
qty = item.quantity or Decimal('1')
|
||||||
total += actual_price * qty
|
total += actual_price * qty
|
||||||
|
elif item.variant_group:
|
||||||
|
actual_price = item.variant_group.price or Decimal('0')
|
||||||
|
qty = item.quantity or Decimal('1')
|
||||||
|
total += actual_price * qty
|
||||||
|
|
||||||
self.base_price = total
|
self.base_price = total
|
||||||
# Обновляем финальную цену
|
# Обновляем финальную цену
|
||||||
@@ -178,6 +182,10 @@ class ProductKit(BaseProductEntity):
|
|||||||
actual_price = item.product.actual_price or Decimal('0')
|
actual_price = item.product.actual_price or Decimal('0')
|
||||||
qty = item.quantity or Decimal('1')
|
qty = item.quantity or Decimal('1')
|
||||||
total += actual_price * qty
|
total += actual_price * qty
|
||||||
|
elif item.variant_group:
|
||||||
|
actual_price = item.variant_group.price or Decimal('0')
|
||||||
|
qty = item.quantity or Decimal('1')
|
||||||
|
total += actual_price * qty
|
||||||
self.base_price = total
|
self.base_price = total
|
||||||
|
|
||||||
# Устанавливаем финальную цену в поле price
|
# Устанавливаем финальную цену в поле price
|
||||||
|
|||||||
@@ -21,6 +21,16 @@
|
|||||||
function formatSelectSelection(item) {
|
function formatSelectSelection(item) {
|
||||||
if (!item.id) return item.text;
|
if (!item.id) return item.text;
|
||||||
|
|
||||||
|
// Сохраняем данные о цене в атрибуты DOM элемента для доступа через jQuery .data()
|
||||||
|
if (item.element) {
|
||||||
|
if (item.price !== undefined) {
|
||||||
|
$(item.element).attr('data-price', item.price);
|
||||||
|
}
|
||||||
|
if (item.actual_price !== undefined) {
|
||||||
|
$(item.element).attr('data-actual_price', item.actual_price);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Показываем только текст при выборе, цена будет обновляться в JavaScript
|
// Показываем только текст при выборе, цена будет обновляться в JavaScript
|
||||||
return item.text || item.id;
|
return item.text || item.id;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -545,6 +545,67 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
calculateFinalPrice();
|
calculateFinalPrice();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Функция для получения цены группы вариантов
|
||||||
|
async function getVariantGroupPrice(selectElement) {
|
||||||
|
if (!selectElement) {
|
||||||
|
console.warn('getVariantGroupPrice: selectElement is null or undefined');
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const variantGroupId = parseInt(selectElement.value);
|
||||||
|
|
||||||
|
if (!selectElement.value || isNaN(variantGroupId) || variantGroupId <= 0) {
|
||||||
|
console.warn('getVariantGroupPrice: no valid variant group id', selectElement.value);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если уже загружена в кэш - возвращаем
|
||||||
|
const cacheKey = `variant_${variantGroupId}`;
|
||||||
|
if (priceCache[cacheKey] !== undefined) {
|
||||||
|
const cachedPrice = parseFloat(priceCache[cacheKey]) || 0;
|
||||||
|
console.log('getVariantGroupPrice: from cache', variantGroupId, cachedPrice);
|
||||||
|
return cachedPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Пытаемся получить из Select2 option data
|
||||||
|
const selectedOption = $(selectElement).find('option:selected');
|
||||||
|
let priceData = selectedOption.data('actual_price') || selectedOption.data('price');
|
||||||
|
if (priceData) {
|
||||||
|
const price = parseFloat(priceData) || 0;
|
||||||
|
if (price > 0) {
|
||||||
|
priceCache[cacheKey] = price;
|
||||||
|
console.log('getVariantGroupPrice: from select2 data', variantGroupId, price);
|
||||||
|
return price;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если не нашли - загружаем через AJAX
|
||||||
|
try {
|
||||||
|
console.log('getVariantGroupPrice: fetching from API', variantGroupId);
|
||||||
|
const response = await fetch(
|
||||||
|
`{% url "products:api-search-products-variants" %}?id=variant_${variantGroupId}`,
|
||||||
|
{ method: 'GET', headers: { 'Accept': 'application/json' } }
|
||||||
|
);
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.results && data.results.length > 0) {
|
||||||
|
const variantData = data.results[0];
|
||||||
|
const price = parseFloat(variantData.actual_price || variantData.price || 0);
|
||||||
|
if (price > 0) {
|
||||||
|
priceCache[cacheKey] = price;
|
||||||
|
console.log('getVariantGroupPrice: from API', variantGroupId, price);
|
||||||
|
}
|
||||||
|
return price;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching variant group price:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn('getVariantGroupPrice: returning 0 for variant group', variantGroupId);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Функция для расчета финальной цены
|
// Функция для расчета финальной цены
|
||||||
async function calculateFinalPrice() {
|
async function calculateFinalPrice() {
|
||||||
// Получаем базовую цену (сумма всех компонентов)
|
// Получаем базовую цену (сумма всех компонентов)
|
||||||
@@ -555,21 +616,28 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const forms = formsContainer.querySelectorAll('.kititem-form');
|
const forms = formsContainer.querySelectorAll('.kititem-form');
|
||||||
for (const form of forms) {
|
for (const form of forms) {
|
||||||
const productSelect = form.querySelector('[name$="-product"]');
|
const productSelect = form.querySelector('[name$="-product"]');
|
||||||
|
const variantGroupSelect = form.querySelector('[name$="-variant_group"]');
|
||||||
const quantity = parseFloat(form.querySelector('[name$="-quantity"]')?.value || '1');
|
const quantity = parseFloat(form.querySelector('[name$="-quantity"]')?.value || '1');
|
||||||
const deleteCheckbox = form.querySelector('[name$="-DELETE"]');
|
const deleteCheckbox = form.querySelector('[name$="-DELETE"]');
|
||||||
|
|
||||||
if (deleteCheckbox && deleteCheckbox.checked) continue;
|
if (deleteCheckbox && deleteCheckbox.checked) continue;
|
||||||
|
|
||||||
// Пропускаем если товар не выбран
|
|
||||||
if (!productSelect || !productSelect.value) continue;
|
|
||||||
|
|
||||||
// Пропускаем если количество не валидно
|
// Пропускаем если количество не валидно
|
||||||
const validQuantity = quantity > 0 ? quantity : 1;
|
const validQuantity = quantity > 0 ? quantity : 1;
|
||||||
|
|
||||||
// Получаем цену товара (асинхронно)
|
// Проверяем товар
|
||||||
const productPrice = await getProductPrice(productSelect);
|
if (productSelect && productSelect.value) {
|
||||||
if (productPrice > 0) {
|
const productPrice = await getProductPrice(productSelect);
|
||||||
newBasePrice += (productPrice * validQuantity);
|
if (productPrice > 0) {
|
||||||
|
newBasePrice += (productPrice * validQuantity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Проверяем группу вариантов
|
||||||
|
else if (variantGroupSelect && variantGroupSelect.value) {
|
||||||
|
const variantPrice = await getVariantGroupPrice(variantGroupSelect);
|
||||||
|
if (variantPrice > 0) {
|
||||||
|
newBasePrice += (variantPrice * validQuantity);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -526,6 +526,67 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
calculateFinalPrice();
|
calculateFinalPrice();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Функция для получения цены группы вариантов
|
||||||
|
async function getVariantGroupPrice(selectElement) {
|
||||||
|
if (!selectElement) {
|
||||||
|
console.warn('getVariantGroupPrice: selectElement is null or undefined');
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const variantGroupId = parseInt(selectElement.value);
|
||||||
|
|
||||||
|
if (!selectElement.value || isNaN(variantGroupId) || variantGroupId <= 0) {
|
||||||
|
console.warn('getVariantGroupPrice: no valid variant group id', selectElement.value);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если уже загружена в кэш - возвращаем
|
||||||
|
const cacheKey = `variant_${variantGroupId}`;
|
||||||
|
if (priceCache[cacheKey] !== undefined) {
|
||||||
|
const cachedPrice = parseFloat(priceCache[cacheKey]) || 0;
|
||||||
|
console.log('getVariantGroupPrice: from cache', variantGroupId, cachedPrice);
|
||||||
|
return cachedPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Пытаемся получить из Select2 option data
|
||||||
|
const selectedOption = $(selectElement).find('option:selected');
|
||||||
|
let priceData = selectedOption.data('actual_price') || selectedOption.data('price');
|
||||||
|
if (priceData) {
|
||||||
|
const price = parseFloat(priceData) || 0;
|
||||||
|
if (price > 0) {
|
||||||
|
priceCache[cacheKey] = price;
|
||||||
|
console.log('getVariantGroupPrice: from select2 data', variantGroupId, price);
|
||||||
|
return price;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если не нашли - загружаем через AJAX
|
||||||
|
try {
|
||||||
|
console.log('getVariantGroupPrice: fetching from API', variantGroupId);
|
||||||
|
const response = await fetch(
|
||||||
|
`{% url "products:api-search-products-variants" %}?id=variant_${variantGroupId}`,
|
||||||
|
{ method: 'GET', headers: { 'Accept': 'application/json' } }
|
||||||
|
);
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.results && data.results.length > 0) {
|
||||||
|
const variantData = data.results[0];
|
||||||
|
const price = parseFloat(variantData.actual_price || variantData.price || 0);
|
||||||
|
if (price > 0) {
|
||||||
|
priceCache[cacheKey] = price;
|
||||||
|
console.log('getVariantGroupPrice: from API', variantGroupId, price);
|
||||||
|
}
|
||||||
|
return price;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching variant group price:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn('getVariantGroupPrice: returning 0 for variant group', variantGroupId);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Функция для расчета финальной цены
|
// Функция для расчета финальной цены
|
||||||
async function calculateFinalPrice() {
|
async function calculateFinalPrice() {
|
||||||
// Получаем базовую цену (сумма всех компонентов)
|
// Получаем базовую цену (сумма всех компонентов)
|
||||||
@@ -536,21 +597,28 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const forms = formsContainer.querySelectorAll('.kititem-form');
|
const forms = formsContainer.querySelectorAll('.kititem-form');
|
||||||
for (const form of forms) {
|
for (const form of forms) {
|
||||||
const productSelect = form.querySelector('[name$="-product"]');
|
const productSelect = form.querySelector('[name$="-product"]');
|
||||||
|
const variantGroupSelect = form.querySelector('[name$="-variant_group"]');
|
||||||
const quantity = parseFloat(form.querySelector('[name$="-quantity"]')?.value || '1');
|
const quantity = parseFloat(form.querySelector('[name$="-quantity"]')?.value || '1');
|
||||||
const deleteCheckbox = form.querySelector('[name$="-DELETE"]');
|
const deleteCheckbox = form.querySelector('[name$="-DELETE"]');
|
||||||
|
|
||||||
if (deleteCheckbox && deleteCheckbox.checked) continue;
|
if (deleteCheckbox && deleteCheckbox.checked) continue;
|
||||||
|
|
||||||
// Пропускаем если товар не выбран
|
|
||||||
if (!productSelect || !productSelect.value) continue;
|
|
||||||
|
|
||||||
// Пропускаем если количество не валидно
|
// Пропускаем если количество не валидно
|
||||||
const validQuantity = quantity > 0 ? quantity : 1;
|
const validQuantity = quantity > 0 ? quantity : 1;
|
||||||
|
|
||||||
// Получаем цену товара (асинхронно)
|
// Проверяем товар
|
||||||
const productPrice = await getProductPrice(productSelect);
|
if (productSelect && productSelect.value) {
|
||||||
if (productPrice > 0) {
|
const productPrice = await getProductPrice(productSelect);
|
||||||
newBasePrice += (productPrice * validQuantity);
|
if (productPrice > 0) {
|
||||||
|
newBasePrice += (productPrice * validQuantity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Проверяем группу вариантов
|
||||||
|
else if (variantGroupSelect && variantGroupSelect.value) {
|
||||||
|
const variantPrice = await getVariantGroupPrice(variantGroupSelect);
|
||||||
|
if (variantPrice > 0) {
|
||||||
|
newBasePrice += (variantPrice * validQuantity);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -222,7 +222,21 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
window.initProductSelect2(productSelect, 'product', apiUrl);
|
window.initProductSelect2(productSelect, 'product', apiUrl);
|
||||||
|
|
||||||
// Обработчик события при выборе товара
|
// Обработчик события при выборе товара
|
||||||
productSelect.addEventListener('select2:select', function(e) {
|
$(productSelect).on('select2:select', function(e) {
|
||||||
|
// Извлекаем числовой ID из формата "product_123"
|
||||||
|
let selectedValue = e.params.data.id;
|
||||||
|
if (typeof selectedValue === 'string' && selectedValue.startsWith('product_')) {
|
||||||
|
selectedValue = selectedValue.replace('product_', '');
|
||||||
|
|
||||||
|
// Создаем новую опцию с правильным ID
|
||||||
|
const selectedOption = $(this).find('option[value="' + e.params.data.id + '"]');
|
||||||
|
if (selectedOption.length > 0) {
|
||||||
|
// Обновляем существующую опцию
|
||||||
|
selectedOption.val(selectedValue);
|
||||||
|
// Устанавливаем новое значение
|
||||||
|
$(this).val(selectedValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
updateRowData(row);
|
updateRowData(row);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -246,8 +260,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const skuMatch = text.match(/\(([^)]+)\)$/);
|
const skuMatch = text.match(/\(([^)]+)\)$/);
|
||||||
const sku = skuMatch ? skuMatch[1] : '-';
|
const sku = skuMatch ? skuMatch[1] : '-';
|
||||||
|
|
||||||
// Получаем цену и статус через AJAX
|
// Получаем цену и статус через AJAX (используем числовой ID)
|
||||||
fetch(`{% url "products:api-search-products-variants" %}?id=${productSelect.value}`)
|
const numericId = productSelect.value;
|
||||||
|
fetch(`{% url "products:api-search-products-variants" %}?id=${numericId}`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.results && data.results.length > 0) {
|
if (data.results && data.results.length > 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user