Перенос встроенных стилей из шаблона detail.html в отдельный CSS-файл transformation_detail.css

This commit is contained in:
2026-01-15 15:09:38 +03:00
parent 2ef537fff6
commit ce486f35ca
2 changed files with 189 additions and 136 deletions

View File

@@ -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;
}

View File

@@ -7,6 +7,7 @@
{% block content %} {% block content %}
<!-- CSS для компонента поиска --> <!-- CSS для компонента поиска -->
<link rel="stylesheet" href="{% static 'products/css/product-search-picker.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"> <div class="container-fluid px-4 py-3">
<!-- Breadcrumbs --> <!-- Breadcrumbs -->
@@ -98,34 +99,60 @@
</div> </div>
</div> </div>
<!-- ОДИН виджет поиска товаров --> <!-- ДВА виджета поиска товаров -->
{% if transformation.status == 'draft' %} {% if transformation.status == 'draft' %}
<div class="card border-0 shadow-sm mb-3"> <div class="row g-3 mb-3 align-items-stretch">
<div class="card-header bg-light py-3"> <!-- Карточка для входящего товара -->
<h6 class="mb-0"><i class="bi bi-search me-2"></i>Найти и добавить товар</h6> <div class="col-md-6">
</div> <div class="card border-0 shadow-sm h-100 d-flex flex-column transformation-picker">
<div class="card-body"> <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 class="mb-3"> </div>
{% 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="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>
<!-- ДВЕ кнопки действий (показываются после выбора товара) --> <!-- Карточка для исходящего товара -->
<div id="action-buttons" class="row g-3 align-items-end" style="display:none;"> <div class="col-md-6">
<div class="col-md-4"> <div class="card border-0 shadow-sm h-100 d-flex flex-column transformation-picker">
<label for="product-quantity" class="form-label">Количество</label> <div class="card-header bg-success bg-opacity-10 py-3">
<input type="number" class="form-control" id="product-quantity" value="1" min="0.001" step="0.001" placeholder="Введите количество"> <h6 class="mb-0"><i class="bi bi-box-arrow-up me-2"></i>Добавить исходящий товар (для получения)</h6>
<small id="quantity-hint" class="form-text text-muted" style="display: none; font-size: 0.75rem;"></small>
</div> </div>
<div class="col-md-4"> <div class="card-body flex-grow-1 d-flex flex-column overflow-hidden">
<button type="button" id="add-to-input-btn" class="btn btn-warning w-100"> <!-- Компонент поиска БЕЗ фильтрации по наличию (skip_stock_filter=True) -->
<i class="bi bi-box-arrow-in-down me-1"></i>Добавить во входящий {% 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' %}
</button>
</div> </div>
<div class="col-md-4"> <!-- Поле количества и кнопка всегда видимы внизу карточки -->
<button type="button" id="add-to-output-btn" class="btn btn-success w-100"> <div class="card-footer bg-transparent border-top">
<i class="bi bi-box-arrow-up me-1"></i>Добавить в исходящий <div class="row g-3 align-items-end">
</button> <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> </div>
</div> </div>
@@ -334,107 +361,122 @@
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
var selectedProduct = null; var selectedInputProduct = null;
var picker = null; var selectedOutputProduct = null;
var inputPicker = null;
var outputPicker = null;
// Инициализация ОДНОГО компонента поиска // ========== ВХОДЯЩИЙ ТОВАР (с фильтрацией по наличию) ==========
if (document.getElementById('transformation-product-picker')) { if (document.getElementById('transformation-input-picker')) {
picker = ProductSearchPicker.init('#transformation-product-picker', { inputPicker = ProductSearchPicker.init('#transformation-input-picker', {
onSelect: function(product, instance) { onSelect: function(product, instance) {
// Товар выбран - показываем кнопки действий selectedInputProduct = product;
selectedProduct = product; // Focus on the quantity input when a product is selected
var actionButtons = document.getElementById('action-buttons');
actionButtons.style.display = 'block';
// Автофокус на поле количества и выделяем текст
setTimeout(function() { setTimeout(function() {
var quantityInput = document.getElementById('product-quantity'); var qtyInput = document.getElementById('input-quantity');
if (quantityInput) { if (qtyInput) {
quantityInput.focus(); qtyInput.focus();
quantityInput.select(); qtyInput.select();
qtyInput.onkeypress = function(e) {
// Обработчик Enter - добавляет в входящие по умолчанию
quantityInput.onkeypress = function(e) {
if (e.key === 'Enter') { if (e.key === 'Enter') {
e.preventDefault(); e.preventDefault();
document.getElementById('add-to-input-btn').click(); document.getElementById('confirm-add-input-btn').click();
} }
}; };
// Обработчик изменения количества для подсказки при добавлении в выходные
quantityInput.addEventListener('input', function() {
updateQuantityHint();
});
} }
}, 100); }, 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) { onDeselect: function(instance) {
// Товар отменен - скрываем кнопки selectedInputProduct = null;
selectedProduct = null;
document.getElementById('action-buttons').style.display = 'none';
} }
}); });
} }
// Кнопка "Добавить во входящий товар" // ========== ИСХОДЯЩИЙ ТОВАР (БЕЗ фильтрации по наличию) ==========
var addToInputBtn = document.getElementById('add-to-input-btn'); if (document.getElementById('transformation-output-picker')) {
if (addToInputBtn) { outputPicker = ProductSearchPicker.init('#transformation-output-picker', {
addToInputBtn.addEventListener('click', function() { onSelect: function(product, instance) {
if (!selectedProduct) return; 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'); function updateOutputQuantityHint() {
var quantity = parseFloat(quantityInput.value); 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) { if (!quantity || quantity <= 0) {
alert('Введите корректное количество (больше 0)'); alert('Введите корректное количество (больше 0)');
quantityInput.focus(); qtyInput.focus();
return; return;
} }
// Заполняем скрытую форму // Заполняем скрытую форму
var productSelect = document.getElementById('hidden-input-product'); var productSelect = document.getElementById('hidden-input-product');
productSelect.innerHTML = '<option value="' + selectedProduct.id + '">' + productSelect.innerHTML = '<option value="' + selectedInputProduct.id + '">' +
selectedProduct.text + '</option>'; selectedInputProduct.text + '</option>';
productSelect.value = selectedProduct.id; productSelect.value = selectedInputProduct.id;
// Устанавливаем количество
document.getElementById('hidden-input-quantity').value = quantity; document.getElementById('hidden-input-quantity').value = quantity;
// Отправляем форму через AJAX // Отправляем форму через AJAX
@@ -452,7 +494,6 @@ document.addEventListener('DOMContentLoaded', function() {
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
// Перезагружаем страницу чтобы обновить список
window.location.reload(); window.location.reload();
} else { } else {
alert('Ошибка: ' + data.error); alert('Ошибка: ' + data.error);
@@ -463,31 +504,28 @@ document.addEventListener('DOMContentLoaded', function() {
alert('Ошибка при добавлении товара'); alert('Ошибка при добавлении товара');
}); });
// Очищаем выбор в пикере // Очищаем выбор
if (picker) { if (inputPicker) inputPicker.clearSelection();
picker.clearSelection(); selectedInputProduct = null;
} qtyInput.value = 1;
selectedProduct = null;
document.getElementById('action-buttons').style.display = 'none';
// Сбрасываем количество на 1
document.getElementById('product-quantity').value = 1;
}); });
} }
// Кнопка "Добавить в исходящий товар" // ========== Кнопка "Добавить исходящий товар" ==========
var addToOutputBtn = document.getElementById('add-to-output-btn'); var confirmAddOutputBtn = document.getElementById('confirm-add-output-btn');
if (addToOutputBtn) { if (confirmAddOutputBtn) {
addToOutputBtn.addEventListener('click', function() { confirmAddOutputBtn.addEventListener('click', function() {
if (!selectedProduct) return; if (!selectedOutputProduct) {
alert('Выберите товар для добавления');
return;
}
// Получаем количество из поля ввода var qtyInput = document.getElementById('output-quantity');
var quantityInput = document.getElementById('product-quantity'); var quantity = parseFloat(qtyInput.value);
var quantity = parseFloat(quantityInput.value);
// Валидация количества
if (!quantity || quantity <= 0) { if (!quantity || quantity <= 0) {
alert('Введите корректное количество (больше 0)'); alert('Введите корректное количество (больше 0)');
quantityInput.focus(); qtyInput.focus();
return; return;
} }
@@ -505,19 +543,17 @@ document.addEventListener('DOMContentLoaded', function() {
'Вход: ' + totalInput + '\n' + 'Вход: ' + totalInput + '\n' +
'Выход (текущий): ' + totalOutput + '\n' + 'Выход (текущий): ' + totalOutput + '\n' +
'Максимально можно добавить: ' + maxAllowed.toFixed(3)); 'Максимально можно добавить: ' + maxAllowed.toFixed(3));
quantityInput.focus(); qtyInput.focus();
quantityInput.select(); qtyInput.select();
return; return;
} }
} }
// Заполняем скрытую форму // Заполняем скрытую форму
var productSelect = document.getElementById('hidden-output-product'); var productSelect = document.getElementById('hidden-output-product');
productSelect.innerHTML = '<option value="' + selectedProduct.id + '">' + productSelect.innerHTML = '<option value="' + selectedOutputProduct.id + '">' +
selectedProduct.text + '</option>'; selectedOutputProduct.text + '</option>';
productSelect.value = selectedProduct.id; productSelect.value = selectedOutputProduct.id;
// Устанавливаем количество
document.getElementById('hidden-output-quantity').value = quantity; document.getElementById('hidden-output-quantity').value = quantity;
// Отправляем форму через AJAX // Отправляем форму через AJAX
@@ -535,7 +571,6 @@ document.addEventListener('DOMContentLoaded', function() {
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
// Перезагружаем страницу чтобы обновить список
window.location.reload(); window.location.reload();
} else { } else {
alert('Ошибка: ' + data.error); alert('Ошибка: ' + data.error);
@@ -546,14 +581,10 @@ document.addEventListener('DOMContentLoaded', function() {
alert('Ошибка при добавлении товара'); alert('Ошибка при добавлении товара');
}); });
// Очищаем выбор в пикере // Очищаем выбор
if (picker) { if (outputPicker) outputPicker.clearSelection();
picker.clearSelection(); selectedOutputProduct = null;
} qtyInput.value = 1;
selectedProduct = null;
document.getElementById('action-buttons').style.display = 'none';
// Сбрасываем количество на 1
document.getElementById('product-quantity').value = 1;
}); });
} }
}); });