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