diff --git a/myproject/products/forms.py b/myproject/products/forms.py index e6d9b06..c28e70e 100644 --- a/myproject/products/forms.py +++ b/myproject/products/forms.py @@ -139,7 +139,9 @@ class ProductKitForm(forms.ModelForm): def clean(self): """ Валидация формы комплекта. - Проверяет что если выбран тип корректировки, указано значение. + Проверяет: + 1. Что если выбран тип корректировки, указано значение + 2. Что заполнено максимум одно поле корректировки (увеличение или уменьшение) """ cleaned_data = super().clean() diff --git a/myproject/products/templates/products/productkit_create.html b/myproject/products/templates/products/productkit_create.html index 76d9871..e761365 100644 --- a/myproject/products/templates/products/productkit_create.html +++ b/myproject/products/templates/products/productkit_create.html @@ -387,6 +387,25 @@ color: #0d6efd; } +/* Стили для полей корректировки цены */ +#id_increase_percent:disabled, +#id_increase_amount:disabled, +#id_decrease_percent:disabled, +#id_decrease_amount:disabled { + background-color: #e9ecef; + color: #6c757d; + cursor: not-allowed; + opacity: 0.6; +} + +#id_increase_percent.is-invalid, +#id_increase_amount.is-invalid, +#id_decrease_percent.is-invalid, +#id_decrease_amount.is-invalid { + border-color: #dc3545; + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); +} + /* Адаптивность */ @media (max-width: 991px) { .col-lg-8, .col-lg-4 { @@ -584,11 +603,85 @@ document.addEventListener('DOMContentLoaded', function() { finalPriceDisplay.textContent = finalPrice.toFixed(2) + ' руб.'; } + // Функция для управления состоянием полей (только одно может быть заполнено) + function validateSingleAdjustment() { + const increasePercent = parseFloat(increasePercentInput.value) || 0; + const increaseAmount = parseFloat(increaseAmountInput.value) || 0; + const decreasePercent = parseFloat(decreasePercentInput.value) || 0; + const decreaseAmount = parseFloat(decreaseAmountInput.value) || 0; + + // Подсчитываем сколько полей заполнено + const filledCount = (increasePercent > 0 ? 1 : 0) + + (increaseAmount > 0 ? 1 : 0) + + (decreasePercent > 0 ? 1 : 0) + + (decreaseAmount > 0 ? 1 : 0); + + const allInputs = [increasePercentInput, increaseAmountInput, decreasePercentInput, decreaseAmountInput]; + + if (filledCount === 0) { + // Ничего не заполнено - все поля активны + allInputs.forEach(input => { + input.disabled = false; + input.classList.remove('is-invalid'); + }); + } else if (filledCount === 1) { + // Ровно одно поле заполнено - отключаем остальные + allInputs.forEach(input => { + const isCurrentField = ( + input === increasePercentInput && increasePercent > 0 || + input === increaseAmountInput && increaseAmount > 0 || + input === decreasePercentInput && decreasePercent > 0 || + input === decreaseAmountInput && decreaseAmount > 0 + ); + + input.disabled = !isCurrentField; + input.classList.remove('is-invalid'); + }); + } else { + // Несколько полей заполнено - помечаем как ошибку и очищаем лишние + allInputs.forEach(input => { + input.classList.add('is-invalid'); + }); + + // Оставляем только первое заполненное поле, остальные очищаем + let foundFirst = false; + if (increasePercent > 0 && !foundFirst) { + foundFirst = true; + } else if (foundFirst || increasePercent > 0) { + increasePercentInput.value = ''; + } + + if (increaseAmount > 0 && !foundFirst) { + foundFirst = true; + } else if (foundFirst || increaseAmount > 0) { + increaseAmountInput.value = ''; + } + + if (decreasePercent > 0 && !foundFirst) { + foundFirst = true; + } else if (foundFirst || decreasePercent > 0) { + decreasePercentInput.value = ''; + } + + if (decreaseAmount > 0 && !foundFirst) { + foundFirst = true; + } else if (foundFirst || decreaseAmount > 0) { + decreaseAmountInput.value = ''; + } + } + } + // Добавляем обработчики для всех полей цены [increasePercentInput, increaseAmountInput, decreasePercentInput, decreaseAmountInput].forEach(input => { if (input) { - input.addEventListener('input', calculateFinalPrice); - input.addEventListener('change', calculateFinalPrice); + input.addEventListener('input', () => { + validateSingleAdjustment(); + calculateFinalPrice(); + }); + input.addEventListener('change', () => { + validateSingleAdjustment(); + calculateFinalPrice(); + }); } }); diff --git a/myproject/products/templates/products/productkit_edit.html b/myproject/products/templates/products/productkit_edit.html index e690dbb..ade4117 100644 --- a/myproject/products/templates/products/productkit_edit.html +++ b/myproject/products/templates/products/productkit_edit.html @@ -388,6 +388,25 @@ color: #0d6efd; } +/* Стили для полей корректировки цены */ +#id_increase_percent:disabled, +#id_increase_amount:disabled, +#id_decrease_percent:disabled, +#id_decrease_amount:disabled { + background-color: #e9ecef; + color: #6c757d; + cursor: not-allowed; + opacity: 0.6; +} + +#id_increase_percent.is-invalid, +#id_increase_amount.is-invalid, +#id_decrease_percent.is-invalid, +#id_decrease_amount.is-invalid { + border-color: #dc3545; + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); +} + /* Адаптивность */ @media (max-width: 991px) { .col-lg-8, .col-lg-4 { @@ -585,11 +604,85 @@ document.addEventListener('DOMContentLoaded', function() { finalPriceDisplay.textContent = finalPrice.toFixed(2) + ' руб.'; } + // Функция для управления состоянием полей (только одно может быть заполнено) + function validateSingleAdjustment() { + const increasePercent = parseFloat(increasePercentInput.value) || 0; + const increaseAmount = parseFloat(increaseAmountInput.value) || 0; + const decreasePercent = parseFloat(decreasePercentInput.value) || 0; + const decreaseAmount = parseFloat(decreaseAmountInput.value) || 0; + + // Подсчитываем сколько полей заполнено + const filledCount = (increasePercent > 0 ? 1 : 0) + + (increaseAmount > 0 ? 1 : 0) + + (decreasePercent > 0 ? 1 : 0) + + (decreaseAmount > 0 ? 1 : 0); + + const allInputs = [increasePercentInput, increaseAmountInput, decreasePercentInput, decreaseAmountInput]; + + if (filledCount === 0) { + // Ничего не заполнено - все поля активны + allInputs.forEach(input => { + input.disabled = false; + input.classList.remove('is-invalid'); + }); + } else if (filledCount === 1) { + // Ровно одно поле заполнено - отключаем остальные + allInputs.forEach(input => { + const isCurrentField = ( + input === increasePercentInput && increasePercent > 0 || + input === increaseAmountInput && increaseAmount > 0 || + input === decreasePercentInput && decreasePercent > 0 || + input === decreaseAmountInput && decreaseAmount > 0 + ); + + input.disabled = !isCurrentField; + input.classList.remove('is-invalid'); + }); + } else { + // Несколько полей заполнено - помечаем как ошибку и очищаем лишние + allInputs.forEach(input => { + input.classList.add('is-invalid'); + }); + + // Оставляем только первое заполненное поле, остальные очищаем + let foundFirst = false; + if (increasePercent > 0 && !foundFirst) { + foundFirst = true; + } else if (foundFirst || increasePercent > 0) { + increasePercentInput.value = ''; + } + + if (increaseAmount > 0 && !foundFirst) { + foundFirst = true; + } else if (foundFirst || increaseAmount > 0) { + increaseAmountInput.value = ''; + } + + if (decreasePercent > 0 && !foundFirst) { + foundFirst = true; + } else if (foundFirst || decreasePercent > 0) { + decreasePercentInput.value = ''; + } + + if (decreaseAmount > 0 && !foundFirst) { + foundFirst = true; + } else if (foundFirst || decreaseAmount > 0) { + decreaseAmountInput.value = ''; + } + } + } + // Добавляем обработчики для всех полей цены [increasePercentInput, increaseAmountInput, decreasePercentInput, decreaseAmountInput].forEach(input => { if (input) { - input.addEventListener('input', calculateFinalPrice); - input.addEventListener('change', calculateFinalPrice); + input.addEventListener('input', () => { + validateSingleAdjustment(); + calculateFinalPrice(); + }); + input.addEventListener('change', () => { + validateSingleAdjustment(); + calculateFinalPrice(); + }); } });