Improved incoming form validation: require cost price, better error highlighting
This commit is contained in:
@@ -158,6 +158,11 @@
|
||||
font-size: 0.875rem;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.is-invalid {
|
||||
border-color: #dc3545 !important;
|
||||
box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
@@ -277,9 +282,18 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const priceInput = row.querySelector('.price-input');
|
||||
const removeBtn = row.querySelector('.btn-remove-row');
|
||||
|
||||
$(productSelect).on('change', updateTotals);
|
||||
quantityInput.addEventListener('input', updateTotals);
|
||||
priceInput.addEventListener('input', updateTotals);
|
||||
$(productSelect).on('change', function() {
|
||||
productSelect.classList.remove('is-invalid');
|
||||
updateTotals();
|
||||
});
|
||||
quantityInput.addEventListener('input', function() {
|
||||
quantityInput.classList.remove('is-invalid');
|
||||
updateTotals();
|
||||
});
|
||||
priceInput.addEventListener('input', function() {
|
||||
priceInput.classList.remove('is-invalid');
|
||||
updateTotals();
|
||||
});
|
||||
removeBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
$(productSelect).select2('destroy');
|
||||
@@ -304,14 +318,15 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
const productId = productSelect.value;
|
||||
const quantity = parseFloat(quantityInput.value) || 0;
|
||||
const price = parseFloat(priceInput.value) || 0;
|
||||
const priceValue = priceInput.value.trim();
|
||||
const price = parseFloat(priceValue) || 0;
|
||||
const sum = quantity * price;
|
||||
|
||||
// Обновляем дисплей суммы
|
||||
sumDisplay.value = sum.toFixed(2);
|
||||
|
||||
// Только считаем если данные заполнены
|
||||
if (productId && quantity > 0 && price >= 0) {
|
||||
// Только считаем если данные ПОЛНОСТЬЮ заполнены (включая цену!)
|
||||
if (productId && quantity > 0 && priceValue !== '' && price >= 0) {
|
||||
totalItems++;
|
||||
totalQuantity += quantity;
|
||||
totalSum += sum;
|
||||
@@ -462,16 +477,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
}
|
||||
|
||||
const productsData = JSON.parse(productsJsonInput.value);
|
||||
|
||||
if (productsData.length === 0) {
|
||||
e.preventDefault();
|
||||
alert('Пожалуйста, добавьте хотя бы один товар.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Проверяем что все товары корректно заполнены
|
||||
// СНАЧАЛА проверяем корректность заполнения товаров
|
||||
let hasErrors = false;
|
||||
let hasAnyProduct = false;
|
||||
|
||||
productsBody.querySelectorAll('tr').forEach(row => {
|
||||
const productSelect = row.querySelector('.product-select');
|
||||
const quantityInput = row.querySelector('.quantity-input');
|
||||
@@ -483,33 +492,51 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
let rowHasError = false;
|
||||
|
||||
// Проверка товара
|
||||
if (!productSelect.value) {
|
||||
productError.textContent = 'Выберите товар';
|
||||
productError.style.display = 'block';
|
||||
productSelect.classList.add('is-invalid');
|
||||
hasErrors = true;
|
||||
rowHasError = true;
|
||||
} else {
|
||||
productError.style.display = 'none';
|
||||
productSelect.classList.remove('is-invalid');
|
||||
hasAnyProduct = true;
|
||||
}
|
||||
|
||||
// Проверка количества
|
||||
const quantity = parseFloat(quantityInput.value) || 0;
|
||||
if (quantity <= 0) {
|
||||
quantityError.textContent = 'Количество должно быть > 0';
|
||||
quantityError.style.display = 'block';
|
||||
quantityInput.classList.add('is-invalid');
|
||||
hasErrors = true;
|
||||
rowHasError = true;
|
||||
} else {
|
||||
quantityError.style.display = 'none';
|
||||
quantityInput.classList.remove('is-invalid');
|
||||
}
|
||||
|
||||
const price = parseFloat(priceInput.value) || 0;
|
||||
if (price < 0) {
|
||||
// Проверка цены - ОБЯЗАТЕЛЬНОЕ ПОЛЕ!
|
||||
const priceValue = priceInput.value.trim();
|
||||
const price = parseFloat(priceValue);
|
||||
|
||||
if (priceValue === '' || isNaN(price)) {
|
||||
priceError.textContent = 'Укажите цену закупки';
|
||||
priceError.style.display = 'block';
|
||||
priceInput.classList.add('is-invalid');
|
||||
hasErrors = true;
|
||||
rowHasError = true;
|
||||
} else if (price < 0) {
|
||||
priceError.textContent = 'Цена не может быть отрицательной';
|
||||
priceError.style.display = 'block';
|
||||
priceInput.classList.add('is-invalid');
|
||||
hasErrors = true;
|
||||
rowHasError = true;
|
||||
} else {
|
||||
priceError.style.display = 'none';
|
||||
priceInput.classList.remove('is-invalid');
|
||||
}
|
||||
|
||||
if (rowHasError) {
|
||||
@@ -519,9 +546,32 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
});
|
||||
|
||||
// Проверка что есть хотя бы один товар
|
||||
if (!hasAnyProduct) {
|
||||
e.preventDefault();
|
||||
alert('Пожалуйста, добавьте хотя бы один товар.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Если есть ошибки валидации - показываем
|
||||
if (hasErrors) {
|
||||
e.preventDefault();
|
||||
alert('Пожалуйста, исправьте ошибки в форме.');
|
||||
alert('⚠️ Пожалуйста, исправьте ошибки в форме (см. подсвеченные поля).');
|
||||
|
||||
// Прокручиваем к первой ошибке
|
||||
const firstError = productsBody.querySelector('.is-invalid');
|
||||
if (firstError) {
|
||||
firstError.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
firstError.focus();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Проверяем финальные данные
|
||||
const productsData = JSON.parse(productsJsonInput.value);
|
||||
if (productsData.length === 0) {
|
||||
e.preventDefault();
|
||||
alert('⚠️ Необходимо корректно заполнить все поля товаров.');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user