Исправление ошибок в редактировании комплектов: валидация, верстка, расчет цены
This commit is contained in:
@@ -8,25 +8,33 @@
|
||||
|
||||
<div id="kititem-forms">
|
||||
{% for kititem_form in kititem_formset %}
|
||||
<div class="card mb-2 kititem-form border"
|
||||
data-form-index="{{ forloop.counter0 }}"
|
||||
data-product-id="{% if kititem_form.instance.product %}{{ kititem_form.instance.product.id }}{% endif %}"
|
||||
data-product-price="{% if kititem_form.instance.product %}{{ kititem_form.instance.product.actual_price|default:0 }}{% else %}0{% endif %}">
|
||||
<div class="card mb-2 kititem-form border" data-form-index="{{ forloop.counter0 }}"
|
||||
data-product-id="{% if kititem_form.instance.product %}{{ kititem_form.instance.product.id }}{% endif %}"
|
||||
data-product-price="{% if kititem_form.instance.product %}{{ kititem_form.instance.product.actual_price|default:0 }}{% else %}0{% endif %}">
|
||||
{{ kititem_form.id }}
|
||||
<div class="card-body p-2">
|
||||
{% if kititem_form.non_field_errors %}
|
||||
<div class="alert alert-danger alert-sm mb-2">
|
||||
{% for error in kititem_form.non_field_errors %}{{ error }}{% endfor %}
|
||||
</div>
|
||||
<div class="alert alert-danger alert-sm mb-2">
|
||||
{% for error in kititem_form.non_field_errors %}{{ error }}{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row g-2 align-items-end">
|
||||
<!-- ТОВАР -->
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label small text-muted mb-1">Товар</label>
|
||||
{{ kititem_form.product }}
|
||||
{% if kititem_form.product.errors %}
|
||||
<div class="text-danger small">{{ kititem_form.product.errors }}</div>
|
||||
<div class="text-danger small">{{ kititem_form.product.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- ЕДИНИЦА ПРОДАЖИ -->
|
||||
<div class="col-md-2">
|
||||
<label class="form-label small text-muted mb-1">Единица продажи</label>
|
||||
{{ kititem_form.sales_unit }}
|
||||
{% if kititem_form.sales_unit.errors %}
|
||||
<div class="text-danger small">{{ kititem_form.sales_unit.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -34,19 +42,18 @@
|
||||
<div class="col-md-1 d-flex justify-content-center align-items-center">
|
||||
<div class="kit-item-separator">
|
||||
<span class="separator-text">ИЛИ</span>
|
||||
<i class="bi bi-info-circle separator-help"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="Вы можете выбрать что-то одно: либо товар, либо группу вариантов"></i>
|
||||
<i class="bi bi-info-circle separator-help" data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="Вы можете выбрать что-то одно: либо товар, либо группу вариантов"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ГРУППА ВАРИАНТОВ -->
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label small text-muted mb-1">Группа вариантов</label>
|
||||
{{ kititem_form.variant_group }}
|
||||
{% if kititem_form.variant_group.errors %}
|
||||
<div class="text-danger small">{{ kititem_form.variant_group.errors }}</div>
|
||||
<div class="text-danger small">{{ kititem_form.variant_group.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -55,17 +62,19 @@
|
||||
<label class="form-label small text-muted mb-1">Кол-во</label>
|
||||
{{ kititem_form.quantity|smart_quantity }}
|
||||
{% if kititem_form.quantity.errors %}
|
||||
<div class="text-danger small">{{ kititem_form.quantity.errors }}</div>
|
||||
<div class="text-danger small">{{ kititem_form.quantity.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- УДАЛЕНИЕ -->
|
||||
<div class="col-md-1 text-end">
|
||||
{% if kititem_form.DELETE %}
|
||||
<button type="button" class="btn btn-sm btn-link text-danger p-0" onclick="this.nextElementSibling.checked = true; this.closest('.kititem-form').style.display='none'; if(typeof calculateFinalPrice === 'function') calculateFinalPrice();" title="Удалить">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
{{ kititem_form.DELETE }}
|
||||
<button type="button" class="btn btn-sm btn-link text-danger p-0"
|
||||
onclick="this.nextElementSibling.checked = true; this.closest('.kititem-form').style.display='none'; if(typeof calculateFinalPrice === 'function') calculateFinalPrice();"
|
||||
title="Удалить">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
{{ kititem_form.DELETE }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -81,4 +90,4 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -38,7 +38,7 @@
|
||||
/**
|
||||
* Инициализирует Select2 для элемента с AJAX поиском товаров
|
||||
* @param {Element} element - DOM элемент select
|
||||
* @param {string} type - Тип поиска ('product' или 'variant')
|
||||
* @param {string} type - Тип поиска ('product', 'variant' или 'sales_unit')
|
||||
* @param {string} apiUrl - URL API для поиска
|
||||
* @returns {boolean} - true если инициализация прошла успешно, false иначе
|
||||
*/
|
||||
@@ -70,60 +70,92 @@
|
||||
|
||||
var placeholders = {
|
||||
'product': 'Начните вводить название товара...',
|
||||
'variant': 'Начните вводить название группы...'
|
||||
'variant': 'Начните вводить название группы...',
|
||||
'sales_unit': 'Выберите единицу продажи...'
|
||||
};
|
||||
|
||||
try {
|
||||
$element.select2({
|
||||
theme: 'bootstrap-5',
|
||||
placeholder: placeholders[type] || 'Выберите...',
|
||||
allowClear: true,
|
||||
width: '100%',
|
||||
language: 'ru',
|
||||
minimumInputLength: 0,
|
||||
dropdownAutoWidth: false,
|
||||
ajax: {
|
||||
url: apiUrl,
|
||||
dataType: 'json',
|
||||
delay: 250,
|
||||
data: function (params) {
|
||||
return {
|
||||
q: params.term || '',
|
||||
type: type,
|
||||
page: params.page || 1
|
||||
};
|
||||
// Для единиц продажи используем другой подход - не AJAX, а загрузка при выборе товара
|
||||
if (type === 'sales_unit') {
|
||||
try {
|
||||
$element.select2({
|
||||
theme: 'bootstrap-5',
|
||||
placeholder: placeholders[type] || 'Выберите...',
|
||||
allowClear: true,
|
||||
width: '100%',
|
||||
language: 'ru',
|
||||
minimumInputLength: 0,
|
||||
dropdownAutoWidth: false,
|
||||
// Для единиц продажи не используем AJAX, т.к. они загружаются при выборе товара
|
||||
disabled: true, // Изначально отключен до выбора товара
|
||||
templateResult: formatSelectResult,
|
||||
templateSelection: formatSelectSelection
|
||||
});
|
||||
console.log('initProductSelect2: successfully initialized sales_unit for', element.name);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('initProductSelect2: initialization error for sales_unit', error);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Для товаров и вариантов используем AJAX
|
||||
try {
|
||||
$element.select2({
|
||||
theme: 'bootstrap-5',
|
||||
placeholder: placeholders[type] || 'Выберите...',
|
||||
allowClear: true,
|
||||
width: '100%',
|
||||
language: 'ru',
|
||||
minimumInputLength: 0,
|
||||
dropdownAutoWidth: false,
|
||||
ajax: {
|
||||
url: apiUrl,
|
||||
dataType: 'json',
|
||||
delay: 250,
|
||||
data: function (params) {
|
||||
return {
|
||||
q: params.term || '',
|
||||
type: type,
|
||||
page: params.page || 1
|
||||
};
|
||||
},
|
||||
processResults: function (data) {
|
||||
return {
|
||||
results: data.results,
|
||||
pagination: {
|
||||
more: data.pagination.more
|
||||
}
|
||||
};
|
||||
},
|
||||
cache: true
|
||||
},
|
||||
processResults: function (data) {
|
||||
return {
|
||||
results: data.results,
|
||||
pagination: {
|
||||
more: data.pagination.more
|
||||
}
|
||||
};
|
||||
},
|
||||
cache: true
|
||||
},
|
||||
templateResult: formatSelectResult,
|
||||
templateSelection: formatSelectSelection
|
||||
});
|
||||
console.log('initProductSelect2: successfully initialized for', element.name);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('initProductSelect2: initialization error', error);
|
||||
return false;
|
||||
templateResult: formatSelectResult,
|
||||
templateSelection: formatSelectSelection
|
||||
});
|
||||
console.log('initProductSelect2: successfully initialized for', element.name);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('initProductSelect2: initialization error', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Инициализирует Select2 для всех селектов, совпадающих с паттерном
|
||||
* @param {string} fieldPattern - Паттерн name атрибута (например: 'items-', 'kititem-')
|
||||
* @param {string} type - Тип поиска ('product' или 'variant')
|
||||
* @param {string} type - Тип поиска ('product', 'variant' или 'sales_unit')
|
||||
* @param {string} apiUrl - URL API для поиска
|
||||
*/
|
||||
window.initAllProductSelect2 = function(fieldPattern, type, apiUrl) {
|
||||
document.querySelectorAll('[name*="' + fieldPattern + '"][name*="-product"]').forEach(function(element) {
|
||||
window.initProductSelect2(element, type, apiUrl);
|
||||
});
|
||||
if (type === 'sales_unit') {
|
||||
document.querySelectorAll('[name*="' + fieldPattern + '"][name*="-sales_unit"]').forEach(function(element) {
|
||||
window.initProductSelect2(element, type, apiUrl);
|
||||
});
|
||||
} else {
|
||||
document.querySelectorAll('[name*="' + fieldPattern + '"][name*="-product"]').forEach(function(element) {
|
||||
window.initProductSelect2(element, type, apiUrl);
|
||||
});
|
||||
}
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
|
||||
@@ -362,8 +362,115 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Комплекты, содержащие этот товар как единицу продажи -->
|
||||
{% if kit_items_using_sales_units %}
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<h5>Комплекты, содержащие этот товар как единицу продажи</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Комплект</th>
|
||||
<th>Количество в комплекте</th>
|
||||
<th>Цена за единицу</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for kit_item in kit_items_using_sales_units %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url 'products:productkit-detail' kit_item.kit.pk %}">
|
||||
{{ kit_item.kit.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ kit_item.quantity|default:"1" }}</td>
|
||||
<td>{{ kit_item.sales_unit.actual_price|default:"0.00" }} руб.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Комплекты, содержащие этот товар напрямую -->
|
||||
{% if kit_items_using_products %}
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<h5>Комплекты, содержащие этот товар напрямую</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Комплект</th>
|
||||
<th>Количество в комплекте</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for kit_item in kit_items_using_products %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url 'products:productkit-detail' kit_item.kit.pk %}">
|
||||
{{ kit_item.kit.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ kit_item.quantity|default:"1" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Комплекты, содержащие этот товар как часть группы вариантов -->
|
||||
{% if variant_group_kit_items %}
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<h5>Комплекты, содержащие этот товар как часть группы вариантов</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Комплект</th>
|
||||
<th>Группа вариантов</th>
|
||||
<th>Количество в комплекте</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for variant_group_item in variant_group_kit_items %}
|
||||
{% for kit_item in variant_group_item.variant_group.kit_items.all %}
|
||||
{% if kit_item.product == product %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url 'products:productkit-detail' kit_item.kit.pk %}">
|
||||
{{ kit_item.kit.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ variant_group_item.variant_group.name }}</td>
|
||||
<td>{{ kit_item.quantity|default:"1" }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
|
||||
@@ -539,6 +539,135 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Функция для получения цены единицы продажи
|
||||
async function getSalesUnitPrice(selectElement) {
|
||||
if (!selectElement) {
|
||||
console.warn('getSalesUnitPrice: selectElement is null or undefined');
|
||||
return 0;
|
||||
}
|
||||
|
||||
const rawValue = selectElement.value;
|
||||
if (!rawValue) {
|
||||
console.warn('getSalesUnitPrice: no value');
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Извлекаем числовой ID из значения (может быть "sales_unit_123" или "123")
|
||||
let salesUnitId;
|
||||
if (rawValue.includes('_')) {
|
||||
const parts = rawValue.split('_');
|
||||
salesUnitId = parseInt(parts[1]);
|
||||
} else {
|
||||
salesUnitId = parseInt(rawValue);
|
||||
}
|
||||
|
||||
if (isNaN(salesUnitId) || salesUnitId <= 0) {
|
||||
console.warn('getSalesUnitPrice: invalid sales unit id', rawValue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Если уже загружена в кэш - возвращаем
|
||||
const cacheKey = `sales_unit_${salesUnitId}`;
|
||||
if (priceCache[cacheKey] !== undefined) {
|
||||
const cachedPrice = parseFloat(priceCache[cacheKey]) || 0;
|
||||
console.log('getSalesUnitPrice: from cache', salesUnitId, cachedPrice);
|
||||
return cachedPrice;
|
||||
}
|
||||
|
||||
// Пытаемся получить из Select2 data (приоритет: actual_price > price)
|
||||
const $select = $(selectElement);
|
||||
const selectedData = $select.select2('data');
|
||||
if (selectedData && selectedData.length > 0) {
|
||||
const itemData = selectedData[0];
|
||||
const priceData = itemData.actual_price || itemData.price;
|
||||
if (priceData) {
|
||||
const price = parseFloat(priceData) || 0;
|
||||
if (price > 0) {
|
||||
priceCache[cacheKey] = price;
|
||||
console.log('getSalesUnitPrice: from select2 data', salesUnitId, price);
|
||||
return price;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Загружаем информацию о единице продажи через API
|
||||
try {
|
||||
console.log('getSalesUnitPrice: fetching from API', salesUnitId);
|
||||
const response = await fetch(
|
||||
`{% url "products:api-product-sales-units" product_id=0 %}`.replace('/0/', `/${salesUnitId}/`),
|
||||
{ method: 'GET', headers: { 'Accept': 'application/json' } }
|
||||
);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.sales_units && data.sales_units.length > 0) {
|
||||
const salesUnitData = data.sales_units.find(su => su.id == salesUnitId);
|
||||
if (salesUnitData) {
|
||||
const price = parseFloat(salesUnitData.actual_price || salesUnitData.price || 0);
|
||||
if (price > 0) {
|
||||
priceCache[cacheKey] = price;
|
||||
console.log('getSalesUnitPrice: from API', salesUnitId, price);
|
||||
}
|
||||
return price;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching sales unit price:', error);
|
||||
}
|
||||
|
||||
console.warn('getSalesUnitPrice: returning 0 for sales unit', salesUnitId);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Функция для обновления списка единиц продажи при выборе товара
|
||||
async function updateSalesUnitsOptions(salesUnitSelect, productValue) {
|
||||
// Очищаем текущие опции
|
||||
salesUnitSelect.innerHTML = '<option value="">---------</option>';
|
||||
salesUnitSelect.disabled = true;
|
||||
|
||||
if (!productValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Извлекаем ID товара
|
||||
let productId;
|
||||
if (productValue.includes('_')) {
|
||||
const parts = productValue.split('_');
|
||||
productId = parseInt(parts[1]);
|
||||
} else {
|
||||
productId = parseInt(productValue);
|
||||
}
|
||||
|
||||
if (isNaN(productId) || productId <= 0) {
|
||||
console.warn('updateSalesUnitsOptions: invalid product id', productValue);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Загружаем единицы продажи для выбранного товара
|
||||
const response = await fetch(
|
||||
`{% url "products:api-product-sales-units" product_id=0 %}`.replace('/0/', `/${productId}/`),
|
||||
{ method: 'GET', headers: { 'Accept': 'application/json' } }
|
||||
);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.sales_units && data.sales_units.length > 0) {
|
||||
data.sales_units.forEach(su => {
|
||||
const option = document.createElement('option');
|
||||
option.value = su.id;
|
||||
option.textContent = `${su.name} (${su.actual_price || su.price} руб.)`;
|
||||
option.dataset.price = su.actual_price || su.price;
|
||||
option.dataset.actual_price = su.actual_price;
|
||||
salesUnitSelect.appendChild(option);
|
||||
});
|
||||
salesUnitSelect.disabled = false;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching sales units:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Обновляем data-product-id и загружаем цену при выборе товара
|
||||
$('[name$="-product"]').on('select2:select', async function() {
|
||||
const form = $(this).closest('.kititem-form');
|
||||
@@ -637,14 +766,21 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// Пропускаем если количество не валидно
|
||||
const validQuantity = quantity > 0 ? quantity : 1;
|
||||
|
||||
// Проверяем товар
|
||||
if (productSelect && productSelect.value) {
|
||||
// Проверяем единицу продажи (имеет наивысший приоритет)
|
||||
if (salesUnitSelect && salesUnitSelect.value) {
|
||||
const salesUnitPrice = await getSalesUnitPrice(salesUnitSelect);
|
||||
if (salesUnitPrice > 0) {
|
||||
newBasePrice += (salesUnitPrice * validQuantity);
|
||||
}
|
||||
}
|
||||
// Проверяем товар (если нет единицы продажи)
|
||||
else if (productSelect && productSelect.value) {
|
||||
const productPrice = await getProductPrice(productSelect);
|
||||
if (productPrice > 0) {
|
||||
newBasePrice += (productPrice * validQuantity);
|
||||
}
|
||||
}
|
||||
// Проверяем группу вариантов
|
||||
// Проверяем группу вариантов (если нет ни единицы продажи, ни товара)
|
||||
else if (variantGroupSelect && variantGroupSelect.value) {
|
||||
const variantPrice = await getVariantGroupPrice(variantGroupSelect);
|
||||
if (variantPrice > 0) {
|
||||
@@ -801,6 +937,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
const selectedProducts = {{ selected_products|default:"{}"|safe }};
|
||||
const selectedVariants = {{ selected_variants|default:"{}"|safe }};
|
||||
const selectedSalesUnits = {{ selected_sales_units|default:"{}"|safe }};
|
||||
|
||||
$('[name$="-product"]').each(function() {
|
||||
const fieldName = $(this).attr('name');
|
||||
@@ -817,34 +954,72 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
$(this).on('select2:select select2:unselect', calculateFinalPrice);
|
||||
});
|
||||
|
||||
$('[name$="-sales_unit"]').each(function() {
|
||||
const fieldName = $(this).attr('name');
|
||||
const preloadedData = selectedSalesUnits[fieldName] || null;
|
||||
initSelect2(this, 'sales_unit', preloadedData);
|
||||
$(this).on('select2:select select2:unselect', calculateFinalPrice);
|
||||
});
|
||||
|
||||
// ========== УПРАВЛЕНИЕ КОМПОНЕНТАМИ ==========
|
||||
function updateFieldStatus(form) {
|
||||
const productSelect = form.querySelector('[name$="-product"]');
|
||||
const variantGroupSelect = form.querySelector('[name$="-variant_group"]');
|
||||
const salesUnitSelect = form.querySelector('[name$="-sales_unit"]');
|
||||
|
||||
if (!productSelect || !variantGroupSelect) return;
|
||||
if (!productSelect || !variantGroupSelect || !salesUnitSelect) return;
|
||||
|
||||
const hasProduct = productSelect.value;
|
||||
const hasVariant = variantGroupSelect.value;
|
||||
const hasSalesUnit = salesUnitSelect.value;
|
||||
|
||||
variantGroupSelect.disabled = !!hasProduct;
|
||||
productSelect.disabled = !!hasVariant;
|
||||
// Если выбрана группа вариантов, блокируем товар и единицу продажи
|
||||
if (hasVariant) {
|
||||
productSelect.disabled = true;
|
||||
salesUnitSelect.disabled = true;
|
||||
}
|
||||
// Если выбран товар, разблокируем единицу продажи и блокируем группу вариантов
|
||||
else if (hasProduct) {
|
||||
salesUnitSelect.disabled = false;
|
||||
variantGroupSelect.disabled = true;
|
||||
}
|
||||
// Если выбрана только единица продажи, но не товар - блокируем все остальные
|
||||
else if (hasSalesUnit) {
|
||||
productSelect.disabled = true;
|
||||
variantGroupSelect.disabled = true;
|
||||
}
|
||||
// Если ничего не выбрано - разблокируем товар и группу вариантов, блокируем единицу продажи
|
||||
else {
|
||||
productSelect.disabled = false;
|
||||
variantGroupSelect.disabled = false;
|
||||
salesUnitSelect.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
function initializeForm(form) {
|
||||
updateFieldStatus(form);
|
||||
const productSelect = form.querySelector('[name$="-product"]');
|
||||
const variantGroupSelect = form.querySelector('[name$="-variant_group"]');
|
||||
const salesUnitSelect = form.querySelector('[name$="-sales_unit"]');
|
||||
|
||||
[productSelect, variantGroupSelect].forEach(field => {
|
||||
[productSelect, variantGroupSelect, salesUnitSelect].forEach(field => {
|
||||
if (field) {
|
||||
field.addEventListener('change', () => {
|
||||
updateFieldStatus(form);
|
||||
// Обновляем список единиц продажи при изменении товара
|
||||
if (field === productSelect) {
|
||||
updateSalesUnitsOptions(salesUnitSelect, productSelect.value);
|
||||
}
|
||||
calculateFinalPrice();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Инициализируем список единиц продажи, если товар уже выбран
|
||||
if (productSelect && productSelect.value) {
|
||||
updateSalesUnitsOptions(salesUnitSelect, productSelect.value);
|
||||
}
|
||||
|
||||
const quantityInput = form.querySelector('[name$="-quantity"]');
|
||||
if (quantityInput) {
|
||||
quantityInput.addEventListener('change', calculateFinalPrice);
|
||||
@@ -874,12 +1049,13 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
<option value="">---------</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-1 d-flex justify-content-center">
|
||||
<div class="kit-item-separator">
|
||||
<span class="separator-text">ИЛИ</span>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label small text-muted mb-1">Единица продажи</label>
|
||||
<select class="form-control form-control-sm" name="kititem-${newFormId}-sales_unit">
|
||||
<option value="">---------</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label small text-muted mb-1">Группа вариантов</label>
|
||||
<select class="form-control form-control-sm" name="kititem-${newFormId}-variant_group">
|
||||
<option value="">---------</option>
|
||||
@@ -911,12 +1087,16 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const productSelect = newForm.querySelector('[name$="-product"]');
|
||||
const variantSelect = newForm.querySelector('[name$="-variant_group"]');
|
||||
|
||||
const salesUnitSelect = newForm.querySelector('[name$="-sales_unit"]');
|
||||
|
||||
initSelect2(productSelect, 'product');
|
||||
initSelect2(variantSelect, 'variant');
|
||||
initSelect2(salesUnitSelect, 'sales_unit');
|
||||
|
||||
// Добавляем обработчики для новой формы
|
||||
$(productSelect).on('select2:select select2:unselect', calculateFinalPrice);
|
||||
$(variantSelect).on('select2:select select2:unselect', calculateFinalPrice);
|
||||
$(salesUnitSelect).on('select2:select select2:unselect', calculateFinalPrice);
|
||||
|
||||
initializeForm(newForm);
|
||||
|
||||
@@ -987,9 +1167,20 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
allForms.forEach(form => {
|
||||
const productSelect = form.querySelector('[name$="-product"]');
|
||||
const variantGroupSelect = form.querySelector('[name$="-variant_group"]');
|
||||
const salesUnitSelect = form.querySelector('[name$="-sales_unit"]');
|
||||
const deleteCheckbox = form.querySelector('[name$="-DELETE"]');
|
||||
|
||||
if (!productSelect.value && !variantGroupSelect.value && deleteCheckbox) {
|
||||
// Проверяем, что выбран хотя бы один компонент (товар, группа вариантов или единица продажи)
|
||||
// Если выбрана единица продажи, товар должен быть выбран
|
||||
if (salesUnitSelect && salesUnitSelect.value && (!productSelect || !productSelect.value)) {
|
||||
// Если выбрана единица продажи, но не выбран товар - это ошибка
|
||||
alert('Если выбрана единица продажи, должен быть выбран соответствующий товар.');
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
// Если ничего не выбрано, отмечаем для удаления
|
||||
if (!productSelect.value && !variantGroupSelect.value && !salesUnitSelect.value && deleteCheckbox) {
|
||||
deleteCheckbox.checked = true;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -136,7 +136,13 @@
|
||||
<tr>
|
||||
<td>{{ forloop.counter }}</td>
|
||||
<td>
|
||||
{% if item.product %}
|
||||
{% if item.sales_unit %}
|
||||
<a href="{% url 'products:product-detail' item.sales_unit.product.pk %}">
|
||||
{{ item.sales_unit.name }}
|
||||
</a>
|
||||
<br>
|
||||
<small class="text-muted">Единица продажи: {{ item.sales_unit.product.name }}</small>
|
||||
{% elif item.product %}
|
||||
<a href="{% url 'products:product-detail' item.product.pk %}">
|
||||
{{ item.product.name }}
|
||||
</a>
|
||||
@@ -149,7 +155,9 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if item.product %}
|
||||
{% if item.sales_unit %}
|
||||
<span class="badge bg-info">Единица продажи</span>
|
||||
{% elif item.product %}
|
||||
<span class="badge bg-success">Товар</span>
|
||||
{% else %}
|
||||
<span class="badge bg-primary">Варианты</span>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user