Перенос встроенных стилей из шаблона detail.html в отдельный CSS-файл transformation_detail.css
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
/* Стили для компонентов поиска товаров в трансформациях */
|
||||
|
||||
#transformation-input-picker .card,
|
||||
#transformation-output-picker .card {
|
||||
height: 380px !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
}
|
||||
|
||||
#transformation-input-picker .product-picker-content,
|
||||
#transformation-output-picker .product-picker-content {
|
||||
max-height: 200px !important;
|
||||
min-height: 200px !important;
|
||||
overflow-y: auto !important;
|
||||
flex-shrink: 0 !important;
|
||||
}
|
||||
|
||||
/* Скрываем внутренние кнопки компонента Picker */
|
||||
#transformation-input-picker .product-picker-add-selected,
|
||||
#transformation-output-picker .product-picker-add-selected {
|
||||
display: none !important;
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
{% block content %}
|
||||
<!-- CSS для компонента поиска -->
|
||||
<link rel="stylesheet" href="{% static 'products/css/product-search-picker.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'inventory/transformation_detail.css' %}">
|
||||
|
||||
<div class="container-fluid px-4 py-3">
|
||||
<!-- Breadcrumbs -->
|
||||
@@ -98,34 +99,60 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ОДИН виджет поиска товаров -->
|
||||
<!-- ДВА виджета поиска товаров -->
|
||||
{% if transformation.status == 'draft' %}
|
||||
<div class="card border-0 shadow-sm mb-3">
|
||||
<div class="card-header bg-light py-3">
|
||||
<h6 class="mb-0"><i class="bi bi-search me-2"></i>Найти и добавить товар</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- Компонент поиска товаров -->
|
||||
<div class="mb-3">
|
||||
{% include 'products/components/product_search_picker.html' with container_id='transformation-product-picker' title='Найти товар' warehouse_id=transformation.warehouse.id categories=categories tags=tags add_button_text='Выбрать товар' content_height='250px' %}
|
||||
<div class="row g-3 mb-3 align-items-stretch">
|
||||
<!-- Карточка для входящего товара -->
|
||||
<div class="col-md-6">
|
||||
<div class="card border-0 shadow-sm h-100 d-flex flex-column transformation-picker">
|
||||
<div class="card-header bg-warning bg-opacity-10 py-3">
|
||||
<h6 class="mb-0"><i class="bi bi-box-arrow-in-down me-2"></i>Добавить входящий товар (для списания)</h6>
|
||||
</div>
|
||||
<div class="card-body flex-grow-1 d-flex flex-column overflow-hidden">
|
||||
<!-- Компонент поиска С фильтрацией по наличию -->
|
||||
{% include 'products/components/product_search_picker.html' with container_id='transformation-input-picker' title='Найти товар в наличии' warehouse_id=transformation.warehouse.id categories=categories tags=tags add_button_text='Добавить во входящий' content_height='200px' %}
|
||||
</div>
|
||||
<!-- Поле количества и кнопка всегда видимы внизу карточки -->
|
||||
<div class="card-footer bg-transparent border-top">
|
||||
<div class="row g-3 align-items-end">
|
||||
<div class="col-md-8">
|
||||
<label for="input-quantity" class="form-label">Количество</label>
|
||||
<input type="number" class="form-control" id="input-quantity" value="1" min="0.001" step="0.001">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<button type="button" id="confirm-add-input-btn" class="btn btn-warning w-100">
|
||||
<i class="bi bi-plus-circle me-1"></i>Добавить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ДВЕ кнопки действий (показываются после выбора товара) -->
|
||||
<div id="action-buttons" class="row g-3 align-items-end" style="display:none;">
|
||||
<div class="col-md-4">
|
||||
<label for="product-quantity" class="form-label">Количество</label>
|
||||
<input type="number" class="form-control" id="product-quantity" value="1" min="0.001" step="0.001" placeholder="Введите количество">
|
||||
<small id="quantity-hint" class="form-text text-muted" style="display: none; font-size: 0.75rem;"></small>
|
||||
<!-- Карточка для исходящего товара -->
|
||||
<div class="col-md-6">
|
||||
<div class="card border-0 shadow-sm h-100 d-flex flex-column transformation-picker">
|
||||
<div class="card-header bg-success bg-opacity-10 py-3">
|
||||
<h6 class="mb-0"><i class="bi bi-box-arrow-up me-2"></i>Добавить исходящий товар (для получения)</h6>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<button type="button" id="add-to-input-btn" class="btn btn-warning w-100">
|
||||
<i class="bi bi-box-arrow-in-down me-1"></i>Добавить во входящий
|
||||
</button>
|
||||
<div class="card-body flex-grow-1 d-flex flex-column overflow-hidden">
|
||||
<!-- Компонент поиска БЕЗ фильтрации по наличию (skip_stock_filter=True) -->
|
||||
{% include 'products/components/product_search_picker.html' with container_id='transformation-output-picker' title='Найти товар (даже если нет на складе)' warehouse_id=transformation.warehouse.id skip_stock_filter=True categories=categories tags=tags add_button_text='Добавить в исходящий' content_height='200px' %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<button type="button" id="add-to-output-btn" class="btn btn-success w-100">
|
||||
<i class="bi bi-box-arrow-up me-1"></i>Добавить в исходящий
|
||||
</button>
|
||||
<!-- Поле количества и кнопка всегда видимы внизу карточки -->
|
||||
<div class="card-footer bg-transparent border-top">
|
||||
<div class="row g-3 align-items-end">
|
||||
<div class="col-md-8">
|
||||
<label for="output-quantity" class="form-label">Количество</label>
|
||||
<input type="number" class="form-control" id="output-quantity" value="1" min="0.001" step="0.001">
|
||||
<small id="output-quantity-hint" class="form-text" style="display: none;"></small>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<button type="button" id="confirm-add-output-btn" class="btn btn-success w-100">
|
||||
<i class="bi bi-plus-circle me-1"></i>Добавить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -334,107 +361,122 @@
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var selectedProduct = null;
|
||||
var picker = null;
|
||||
var selectedInputProduct = null;
|
||||
var selectedOutputProduct = null;
|
||||
var inputPicker = null;
|
||||
var outputPicker = null;
|
||||
|
||||
// Инициализация ОДНОГО компонента поиска
|
||||
if (document.getElementById('transformation-product-picker')) {
|
||||
picker = ProductSearchPicker.init('#transformation-product-picker', {
|
||||
// ========== ВХОДЯЩИЙ ТОВАР (с фильтрацией по наличию) ==========
|
||||
if (document.getElementById('transformation-input-picker')) {
|
||||
inputPicker = ProductSearchPicker.init('#transformation-input-picker', {
|
||||
onSelect: function(product, instance) {
|
||||
// Товар выбран - показываем кнопки действий
|
||||
selectedProduct = product;
|
||||
var actionButtons = document.getElementById('action-buttons');
|
||||
actionButtons.style.display = 'block';
|
||||
|
||||
// Автофокус на поле количества и выделяем текст
|
||||
selectedInputProduct = product;
|
||||
// Focus on the quantity input when a product is selected
|
||||
setTimeout(function() {
|
||||
var quantityInput = document.getElementById('product-quantity');
|
||||
if (quantityInput) {
|
||||
quantityInput.focus();
|
||||
quantityInput.select();
|
||||
|
||||
// Обработчик Enter - добавляет в входящие по умолчанию
|
||||
quantityInput.onkeypress = function(e) {
|
||||
var qtyInput = document.getElementById('input-quantity');
|
||||
if (qtyInput) {
|
||||
qtyInput.focus();
|
||||
qtyInput.select();
|
||||
qtyInput.onkeypress = function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
document.getElementById('add-to-input-btn').click();
|
||||
document.getElementById('confirm-add-input-btn').click();
|
||||
}
|
||||
};
|
||||
|
||||
// Обработчик изменения количества для подсказки при добавлении в выходные
|
||||
quantityInput.addEventListener('input', function() {
|
||||
updateQuantityHint();
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
|
||||
// Функция обновления подсказки количества
|
||||
function updateQuantityHint() {
|
||||
var quantityInput = document.getElementById('product-quantity');
|
||||
var quantityHint = document.getElementById('quantity-hint');
|
||||
var totalInputEl = document.getElementById('total-input-quantity');
|
||||
var totalOutputEl = document.getElementById('total-output-quantity');
|
||||
|
||||
if (!quantityInput || !quantityHint || !totalInputEl || !totalOutputEl) return;
|
||||
|
||||
var quantity = parseFloat(quantityInput.value) || 0;
|
||||
var totalInput = parseFloat(totalInputEl.textContent.replace(/\s/g, '').replace(',', '.'));
|
||||
var totalOutput = parseFloat(totalOutputEl.textContent.replace(/\s/g, '').replace(',', '.'));
|
||||
var newTotalOutput = totalOutput + quantity;
|
||||
|
||||
if (quantity > 0 && totalInput > 0) {
|
||||
if (newTotalOutput > totalInput) {
|
||||
var maxAllowed = totalInput - totalOutput;
|
||||
quantityHint.textContent = 'Максимум: ' + maxAllowed.toFixed(3) + ' (превышение!)';
|
||||
quantityHint.className = 'form-text text-danger';
|
||||
quantityHint.style.display = 'block';
|
||||
} else if (newTotalOutput === totalInput) {
|
||||
quantityHint.textContent = 'Количества будут равны ✓';
|
||||
quantityHint.className = 'form-text text-success';
|
||||
quantityHint.style.display = 'block';
|
||||
} else {
|
||||
var remaining = totalInput - newTotalOutput;
|
||||
quantityHint.textContent = 'Остаток после добавления: ' + remaining.toFixed(3);
|
||||
quantityHint.className = 'form-text text-info';
|
||||
quantityHint.style.display = 'block';
|
||||
}
|
||||
} else {
|
||||
quantityHint.style.display = 'none';
|
||||
}
|
||||
}
|
||||
},
|
||||
onDeselect: function(instance) {
|
||||
// Товар отменен - скрываем кнопки
|
||||
selectedProduct = null;
|
||||
document.getElementById('action-buttons').style.display = 'none';
|
||||
selectedInputProduct = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Кнопка "Добавить во входящий товар"
|
||||
var addToInputBtn = document.getElementById('add-to-input-btn');
|
||||
if (addToInputBtn) {
|
||||
addToInputBtn.addEventListener('click', function() {
|
||||
if (!selectedProduct) return;
|
||||
// ========== ИСХОДЯЩИЙ ТОВАР (БЕЗ фильтрации по наличию) ==========
|
||||
if (document.getElementById('transformation-output-picker')) {
|
||||
outputPicker = ProductSearchPicker.init('#transformation-output-picker', {
|
||||
onSelect: function(product, instance) {
|
||||
selectedOutputProduct = product;
|
||||
updateOutputQuantityHint();
|
||||
setTimeout(function() {
|
||||
var qtyInput = document.getElementById('output-quantity');
|
||||
if (qtyInput) {
|
||||
qtyInput.focus();
|
||||
qtyInput.select();
|
||||
qtyInput.onkeypress = function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
document.getElementById('confirm-add-output-btn').click();
|
||||
}
|
||||
};
|
||||
qtyInput.addEventListener('input', updateOutputQuantityHint);
|
||||
}
|
||||
}, 100);
|
||||
},
|
||||
onDeselect: function(instance) {
|
||||
selectedOutputProduct = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Получаем количество из поля ввода
|
||||
var quantityInput = document.getElementById('product-quantity');
|
||||
var quantity = parseFloat(quantityInput.value);
|
||||
// Функция обновления подсказки количества для исходящего товара
|
||||
function updateOutputQuantityHint() {
|
||||
var qtyInput = document.getElementById('output-quantity');
|
||||
var hint = document.getElementById('output-quantity-hint');
|
||||
var totalInputEl = document.getElementById('total-input-quantity');
|
||||
var totalOutputEl = document.getElementById('total-output-quantity');
|
||||
|
||||
if (!qtyInput || !hint || !totalInputEl || !totalOutputEl) return;
|
||||
|
||||
var qty = parseFloat(qtyInput.value) || 0;
|
||||
var totalInput = parseFloat(totalInputEl.textContent.replace(/\s/g, '').replace(',', '.'));
|
||||
var totalOutput = parseFloat(totalOutputEl.textContent.replace(/\s/g, '').replace(',', '.'));
|
||||
var newTotalOutput = totalOutput + qty;
|
||||
|
||||
if (qty > 0 && totalInput > 0) {
|
||||
if (newTotalOutput > totalInput) {
|
||||
var maxAllowed = totalInput - totalOutput;
|
||||
hint.textContent = 'Максимум: ' + maxAllowed.toFixed(3) + ' (превышение!)';
|
||||
hint.className = 'form-text text-danger';
|
||||
hint.style.display = 'block';
|
||||
} else if (newTotalOutput === totalInput) {
|
||||
hint.textContent = 'Количества будут равны ✓';
|
||||
hint.className = 'form-text text-success';
|
||||
hint.style.display = 'block';
|
||||
} else {
|
||||
var remaining = totalInput - newTotalOutput;
|
||||
hint.textContent = 'Остаток после добавления: ' + remaining.toFixed(3);
|
||||
hint.className = 'form-text text-info';
|
||||
hint.style.display = 'block';
|
||||
}
|
||||
} else {
|
||||
hint.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Кнопка "Добавить входящий товар" ==========
|
||||
var confirmAddInputBtn = document.getElementById('confirm-add-input-btn');
|
||||
if (confirmAddInputBtn) {
|
||||
confirmAddInputBtn.addEventListener('click', function() {
|
||||
if (!selectedInputProduct) {
|
||||
alert('Выберите товар для добавления');
|
||||
return;
|
||||
}
|
||||
|
||||
var qtyInput = document.getElementById('input-quantity');
|
||||
var quantity = parseFloat(qtyInput.value);
|
||||
|
||||
// Валидация количества
|
||||
if (!quantity || quantity <= 0) {
|
||||
alert('Введите корректное количество (больше 0)');
|
||||
quantityInput.focus();
|
||||
qtyInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// Заполняем скрытую форму
|
||||
var productSelect = document.getElementById('hidden-input-product');
|
||||
productSelect.innerHTML = '<option value="' + selectedProduct.id + '">' +
|
||||
selectedProduct.text + '</option>';
|
||||
productSelect.value = selectedProduct.id;
|
||||
|
||||
// Устанавливаем количество
|
||||
productSelect.innerHTML = '<option value="' + selectedInputProduct.id + '">' +
|
||||
selectedInputProduct.text + '</option>';
|
||||
productSelect.value = selectedInputProduct.id;
|
||||
document.getElementById('hidden-input-quantity').value = quantity;
|
||||
|
||||
// Отправляем форму через AJAX
|
||||
@@ -452,7 +494,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Перезагружаем страницу чтобы обновить список
|
||||
window.location.reload();
|
||||
} else {
|
||||
alert('Ошибка: ' + data.error);
|
||||
@@ -463,31 +504,28 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
alert('Ошибка при добавлении товара');
|
||||
});
|
||||
|
||||
// Очищаем выбор в пикере
|
||||
if (picker) {
|
||||
picker.clearSelection();
|
||||
}
|
||||
selectedProduct = null;
|
||||
document.getElementById('action-buttons').style.display = 'none';
|
||||
// Сбрасываем количество на 1
|
||||
document.getElementById('product-quantity').value = 1;
|
||||
// Очищаем выбор
|
||||
if (inputPicker) inputPicker.clearSelection();
|
||||
selectedInputProduct = null;
|
||||
qtyInput.value = 1;
|
||||
});
|
||||
}
|
||||
|
||||
// Кнопка "Добавить в исходящий товар"
|
||||
var addToOutputBtn = document.getElementById('add-to-output-btn');
|
||||
if (addToOutputBtn) {
|
||||
addToOutputBtn.addEventListener('click', function() {
|
||||
if (!selectedProduct) return;
|
||||
// ========== Кнопка "Добавить исходящий товар" ==========
|
||||
var confirmAddOutputBtn = document.getElementById('confirm-add-output-btn');
|
||||
if (confirmAddOutputBtn) {
|
||||
confirmAddOutputBtn.addEventListener('click', function() {
|
||||
if (!selectedOutputProduct) {
|
||||
alert('Выберите товар для добавления');
|
||||
return;
|
||||
}
|
||||
|
||||
// Получаем количество из поля ввода
|
||||
var quantityInput = document.getElementById('product-quantity');
|
||||
var quantity = parseFloat(quantityInput.value);
|
||||
var qtyInput = document.getElementById('output-quantity');
|
||||
var quantity = parseFloat(qtyInput.value);
|
||||
|
||||
// Валидация количества
|
||||
if (!quantity || quantity <= 0) {
|
||||
alert('Введите корректное количество (больше 0)');
|
||||
quantityInput.focus();
|
||||
qtyInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -498,26 +536,24 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
var totalInput = parseFloat(totalInputEl.textContent.replace(/\s/g, '').replace(',', '.'));
|
||||
var totalOutput = parseFloat(totalOutputEl.textContent.replace(/\s/g, '').replace(',', '.'));
|
||||
var newTotalOutput = totalOutput + quantity;
|
||||
|
||||
|
||||
if (newTotalOutput > totalInput) {
|
||||
var maxAllowed = totalInput - totalOutput;
|
||||
alert('Сумма выходных количеств не может превышать сумму входных количеств.\n' +
|
||||
'Вход: ' + totalInput + '\n' +
|
||||
'Выход (текущий): ' + totalOutput + '\n' +
|
||||
'Максимально можно добавить: ' + maxAllowed.toFixed(3));
|
||||
quantityInput.focus();
|
||||
quantityInput.select();
|
||||
qtyInput.focus();
|
||||
qtyInput.select();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Заполняем скрытую форму
|
||||
var productSelect = document.getElementById('hidden-output-product');
|
||||
productSelect.innerHTML = '<option value="' + selectedProduct.id + '">' +
|
||||
selectedProduct.text + '</option>';
|
||||
productSelect.value = selectedProduct.id;
|
||||
|
||||
// Устанавливаем количество
|
||||
productSelect.innerHTML = '<option value="' + selectedOutputProduct.id + '">' +
|
||||
selectedOutputProduct.text + '</option>';
|
||||
productSelect.value = selectedOutputProduct.id;
|
||||
document.getElementById('hidden-output-quantity').value = quantity;
|
||||
|
||||
// Отправляем форму через AJAX
|
||||
@@ -535,7 +571,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Перезагружаем страницу чтобы обновить список
|
||||
window.location.reload();
|
||||
} else {
|
||||
alert('Ошибка: ' + data.error);
|
||||
@@ -546,14 +581,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
alert('Ошибка при добавлении товара');
|
||||
});
|
||||
|
||||
// Очищаем выбор в пикере
|
||||
if (picker) {
|
||||
picker.clearSelection();
|
||||
}
|
||||
selectedProduct = null;
|
||||
document.getElementById('action-buttons').style.display = 'none';
|
||||
// Сбрасываем количество на 1
|
||||
document.getElementById('product-quantity').value = 1;
|
||||
// Очищаем выбор
|
||||
if (outputPicker) outputPicker.clearSelection();
|
||||
selectedOutputProduct = null;
|
||||
qtyInput.value = 1;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user