Перенос встроенных стилей из шаблона 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 %}
<!-- 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;
}
@@ -505,19 +543,17 @@ document.addEventListener('DOMContentLoaded', function() {
'Вход: ' + 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;
});
}
});