diff --git a/myproject/inventory/static/inventory/js/inventory_detail.js b/myproject/inventory/static/inventory/js/inventory_detail.js index 9eb2864..21b87f3 100644 --- a/myproject/inventory/static/inventory/js/inventory_detail.js +++ b/myproject/inventory/static/inventory/js/inventory_detail.js @@ -394,9 +394,9 @@ const emptyMessage = document.getElementById('empty-lines-message'); if (emptyMessage) emptyMessage.remove(); - // Добавляем новую строку + // Добавляем новую строку в начало таблицы const newRow = self.createLineRow(data.line); - tbody.appendChild(newRow); + tbody.insertBefore(newRow, tbody.firstChild); // Включаем кнопку завершения const completeBtn = document.getElementById('complete-inventory-btn'); diff --git a/myproject/inventory/templates/inventory/incoming_document/incoming_document_detail.html b/myproject/inventory/templates/inventory/incoming_document/incoming_document_detail.html index 11e8d13..5372ef4 100644 --- a/myproject/inventory/templates/inventory/incoming_document/incoming_document_detail.html +++ b/myproject/inventory/templates/inventory/incoming_document/incoming_document_detail.html @@ -20,19 +20,22 @@
- +
-
-
- {{ document.document_number }} - {% if document.status == 'draft' %} - Черновик - {% elif document.status == 'confirmed' %} - Проведён - {% elif document.status == 'cancelled' %} - Отменён - {% endif %} -
+
+ {% if document.can_edit %}
@@ -50,65 +53,67 @@
{% endif %}
-
-
-
-

Склад

-

{{ document.warehouse.name }}

+
+
+
+
+

Склад

+

{{ document.warehouse.name }}

+
+
+

Дата документа

+

{{ document.date|date:"d.m.Y" }}

+
+
+

Тип поступления

+

{{ document.get_receipt_type_display }}

+
+
+

Создан

+

{{ document.created_at|date:"d.m.Y H:i" }}

+
-
-

Дата документа

-

{{ document.date|date:"d.m.Y" }}

-
-
-

Тип поступления

-

{{ document.get_receipt_type_display }}

-
-
-

Создан

-

{{ document.created_at|date:"d.m.Y H:i" }}

-
-
- {% if document.supplier_name %} -
-

Поставщик

-

{{ document.supplier_name }}

-
- {% endif %} - - {% if document.notes %} -
-

Примечания

-

{{ document.notes }}

-
- {% endif %} - - {% if document.confirmed_at %} -
-
-

Проведён

-

{{ document.confirmed_at|date:"d.m.Y H:i" }}

+ {% if document.supplier_name %} +
+

Поставщик

+

{{ document.supplier_name }}

-
-

Провёл

-

{% if document.confirmed_by %}{{ document.confirmed_by.name|default:document.confirmed_by.email }}{% else %}-{% endif %}

+ {% endif %} + + {% if document.notes %} +
+

Примечания

+

{{ document.notes }}

+ {% endif %} + + {% if document.confirmed_at %} +
+
+

Проведён

+

{{ document.confirmed_at|date:"d.m.Y H:i" }}

+
+
+

Провёл

+

{% if document.confirmed_by %}{{ document.confirmed_by.name|default:document.confirmed_by.email }}{% else %}-{% endif %}

+
+
+ {% endif %}
- {% endif %}
{% if document.can_edit %}
-
+
Добавить позицию в документ
-
- -
- {% include 'products/components/product_search_picker.html' with container_id='incoming-picker' title='Найти товар для поступления' warehouse_id=document.warehouse.id filter_in_stock_only=False skip_stock_filter=True categories=categories tags=tags add_button_text='Выбрать товар' content_height='250px' %} +
+ +
+ {% include 'products/components/product_search_picker.html' with container_id='incoming-picker' title='Найти товар...' warehouse_id=document.warehouse.id filter_in_stock_only=False skip_stock_filter=True categories=categories tags=tags add_button_text='Выбрать' content_height='150px' %}
@@ -217,11 +222,19 @@ {% endif %} - {{ item.cost_price|floatformat:2 }} {% if document.can_edit %} - + + {{ item.cost_price|floatformat:2 }} + + + {% else %} + {{ item.cost_price|floatformat:2 }} {% endif %} @@ -271,24 +284,13 @@ {% if document.can_edit %} -
- +
- @@ -351,6 +353,22 @@ document.addEventListener('DOMContentLoaded', function() { } }); + // Анимация для иконки сворачивания/разворачивания информации о документе + const documentInfoCollapse = document.getElementById('document-info-collapse'); + const documentInfoCollapseIcon = document.getElementById('document-info-collapse-icon'); + + if (documentInfoCollapse && documentInfoCollapseIcon) { + documentInfoCollapse.addEventListener('show.bs.collapse', function() { + documentInfoCollapseIcon.classList.remove('bi-chevron-down'); + documentInfoCollapseIcon.classList.add('bi-chevron-up'); + }); + + documentInfoCollapse.addEventListener('hide.bs.collapse', function() { + documentInfoCollapseIcon.classList.remove('bi-chevron-up'); + documentInfoCollapseIcon.classList.add('bi-chevron-down'); + }); + } + // Функция выбора товара function selectProduct(product) { const productId = String(product.id).replace('product_', ''); @@ -416,160 +434,10 @@ document.addEventListener('DOMContentLoaded', function() { clearSelectedBtn.addEventListener('click', clearSelectedProduct); } - // ============================================ - // Inline редактирование позиций в таблице - // ============================================ - - // Хранилище оригинальных значений при редактировании - const originalValues = {}; - // Обработчики для кнопок редактирования - document.querySelectorAll('.btn-edit-item').forEach(btn => { - btn.addEventListener('click', function() { - const row = this.closest('tr'); - const itemId = row.dataset.itemId; - - // Сохраняем оригинальные значения - originalValues[itemId] = { - quantity: row.querySelector('.item-quantity-input').value, - cost_price: row.querySelector('.item-cost-price-input').value, - notes: row.querySelector('.item-notes-input').value - }; - - // Переключаем в режим редактирования - toggleEditMode(row, true); - }); - }); - - // Обработчики для кнопок сохранения - document.querySelectorAll('.btn-save-item').forEach(btn => { - btn.addEventListener('click', function() { - const row = this.closest('tr'); - const itemId = row.dataset.itemId; - saveItemChanges(itemId, row); - }); - }); - - // Обработчики для кнопок отмены - document.querySelectorAll('.btn-cancel-edit').forEach(btn => { - btn.addEventListener('click', function() { - const row = this.closest('tr'); - const itemId = row.dataset.itemId; - - // Восстанавливаем оригинальные значения - if (originalValues[itemId]) { - row.querySelector('.item-quantity-input').value = originalValues[itemId].quantity; - row.querySelector('.item-cost-price-input').value = originalValues[itemId].cost_price; - row.querySelector('.item-notes-input').value = originalValues[itemId].notes; - } - - // Выходим из режима редактирования - toggleEditMode(row, false); - }); - }); - - /** - * Переключение режима редактирования строки - */ - function toggleEditMode(row, isEditing) { - // Переключаем видимость полей отображения/ввода - row.querySelectorAll('.item-quantity-display, .item-cost-price-display, .item-notes-display').forEach(el => { - el.style.display = isEditing ? 'none' : ''; - }); - row.querySelectorAll('.item-quantity-input, .item-cost-price-input, .item-notes-input').forEach(el => { - el.style.display = isEditing ? '' : 'none'; - }); - - // Переключаем видимость кнопок - row.querySelector('.item-action-buttons').style.display = isEditing ? 'none' : ''; - row.querySelector('.item-edit-buttons').style.display = isEditing ? '' : 'none'; - - // Фокус на поле количества при входе в режим редактирования - if (isEditing) { - const qtyInput = row.querySelector('.item-quantity-input'); - if (qtyInput) { - qtyInput.focus(); - qtyInput.select(); - } - } - } - - /** - * Сохранение изменений позиции - */ - function saveItemChanges(itemId, row) { - const quantity = row.querySelector('.item-quantity-input').value; - const costPrice = row.querySelector('.item-cost-price-input').value; - const notes = row.querySelector('.item-notes-input').value; - - // Валидация - if (!quantity || parseFloat(quantity) <= 0) { - alert('Количество должно быть больше нуля'); - return; - } - if (!costPrice || parseFloat(costPrice) < 0) { - alert('Закупочная цена не может быть отрицательной'); - return; - } - - // Отправляем на сервер - const formData = new FormData(); - formData.append('quantity', quantity); - formData.append('cost_price', costPrice); - formData.append('notes', notes); - formData.append('csrfmiddlewaretoken', document.querySelector('[name=csrfmiddlewaretoken]').value); - - // Блокируем кнопки во время сохранения - const saveBtn = row.querySelector('.btn-save-item'); - const cancelBtn = row.querySelector('.btn-cancel-edit'); - saveBtn.disabled = true; - cancelBtn.disabled = true; - saveBtn.innerHTML = ''; - - fetch(`/inventory/incoming-documents/{{ document.pk }}/update-item/${itemId}/`, { - method: 'POST', - body: formData, - headers: { - 'X-Requested-With': 'XMLHttpRequest' - } - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - // Обновляем отображение - let formattedQty = parseFloat(quantity); - if (formattedQty === Math.floor(formattedQty)) { - formattedQty = Math.floor(formattedQty).toString(); - } else { - formattedQty = formattedQty.toString().replace('.', ','); - } - row.querySelector('.item-quantity-display').textContent = formattedQty; - row.querySelector('.item-cost-price-display').textContent = parseFloat(costPrice).toFixed(2); - row.querySelector('.item-notes-display').textContent = notes || '-'; - - // Пересчитываем сумму - const totalCost = (parseFloat(quantity) * parseFloat(costPrice)).toFixed(2); - row.querySelector('td:nth-child(4) strong').textContent = totalCost; - - // Выходим из режима редактирования - toggleEditMode(row, false); - } else { - alert('Ошибка: ' + data.error); - } - }) - .catch(error => { - console.error('Error:', error); - alert('Произошла ошибка при сохранении'); - }) - .finally(() => { - saveBtn.disabled = false; - cancelBtn.disabled = false; - saveBtn.innerHTML = ''; - }); - } // ============================================ - // Inline редактирование количества + // Inline редактирование количества и цены // ============================================ function initInlineQuantityEdit() { @@ -708,14 +576,146 @@ document.addEventListener('DOMContentLoaded', function() { }); } + function initInlineCostPriceEdit() { + // Проверяем, есть ли на странице редактируемые цены + const editableCostPrices = document.querySelectorAll('.editable-cost-price'); + if (editableCostPrices.length === 0) { + return; // Нет элементов для редактирования + } + + // Обработчик клика на редактируемую цену + document.addEventListener('click', function(e) { + const costPriceSpan = e.target.closest('.editable-cost-price'); + if (!costPriceSpan) return; + + // Предотвращаем повторное срабатывание, если уже редактируем + if (costPriceSpan.querySelector('input')) return; + + const itemId = costPriceSpan.dataset.itemId; + const currentValue = costPriceSpan.dataset.currentValue; + + // Сохраняем оригинальный HTML + const originalHTML = costPriceSpan.innerHTML; + + // Создаем input для редактирования + const input = document.createElement('input'); + input.type = 'number'; + input.className = 'form-control form-control-sm'; + input.style.width = '100px'; + input.style.textAlign = 'right'; + input.value = parseFloat(currentValue).toFixed(2); + input.step = '0.01'; + input.min = '0'; + input.placeholder = 'Цена'; + + // Заменяем содержимое на input + costPriceSpan.innerHTML = ''; + costPriceSpan.appendChild(input); + input.focus(); + input.select(); + + // Функция сохранения + const saveCostPrice = async () => { + let newValue = input.value.trim(); + + // Валидация + if (!newValue || parseFloat(newValue) < 0) { + alert('Закупочная цена не может быть отрицательной'); + costPriceSpan.innerHTML = originalHTML; + return; + } + + // Проверяем, изменилось ли значение + if (parseFloat(newValue) === parseFloat(currentValue)) { + // Значение не изменилось + costPriceSpan.innerHTML = originalHTML; + return; + } + + // Показываем загрузку + input.disabled = true; + input.style.opacity = '0.5'; + + try { + // Получаем текущие значения других полей + const row = costPriceSpan.closest('tr'); + const quantity = row.querySelector('.item-quantity-input').value; + const notes = row.querySelector('.item-notes-input').value; + + const response = await fetch(`/inventory/incoming-documents/{{ document.pk }}/update-item/${itemId}/`, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value + }, + body: new URLSearchParams({ + quantity: quantity, + cost_price: newValue, + notes: notes + }) + }); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + + const data = await response.json(); + + if (data.success) { + // Обновляем отображение + const formattedPrice = parseFloat(newValue).toFixed(2); + costPriceSpan.textContent = formattedPrice; + costPriceSpan.dataset.currentValue = newValue; + + // Пересчитываем сумму + const totalCost = (parseFloat(quantity) * parseFloat(newValue)).toFixed(2); + row.querySelector('td:nth-child(4) strong').textContent = totalCost; + + // Обновляем итого + updateTotals(); + } else { + alert(data.error || 'Ошибка при обновлении цены'); + costPriceSpan.innerHTML = originalHTML; + } + } catch (error) { + console.error('Error:', error); + alert('Ошибка сети при обновлении цены'); + costPriceSpan.innerHTML = originalHTML; + } + }; + + // Функция отмены + const cancelEdit = () => { + costPriceSpan.innerHTML = originalHTML; + }; + + // Enter - сохранить + input.addEventListener('keydown', function(e) { + if (e.key === 'Enter') { + e.preventDefault(); + saveCostPrice(); + } else if (e.key === 'Escape') { + e.preventDefault(); + cancelEdit(); + } + }); + + // Потеря фокуса - сохранить + input.addEventListener('blur', function() { + setTimeout(saveCostPrice, 100); + }); + }); + } + // Функция обновления итоговых сумм function updateTotals() { // Можно реализовать пересчет итогов, если нужно // Пока оставим как есть, так как сервер возвращает обновленные данные } - // Инициализация inline редактирования количества + // Инициализация inline редактирования initInlineQuantityEdit(); + initInlineCostPriceEdit(); }); @@ -745,6 +745,17 @@ document.addEventListener('DOMContentLoaded', function() { color: #0d6efd !important; text-decoration: underline; } + +/* Стили для редактируемой цены */ +.editable-cost-price { + cursor: pointer; + transition: color 0.2s ease; +} + +.editable-cost-price:hover { + color: #0d6efd !important; + text-decoration: underline; +} {% endblock %} diff --git a/myproject/inventory/templates/inventory/inventory/inventory_detail.html b/myproject/inventory/templates/inventory/inventory/inventory_detail.html index 99826a3..731f47a 100644 --- a/myproject/inventory/templates/inventory/inventory/inventory_detail.html +++ b/myproject/inventory/templates/inventory/inventory/inventory_detail.html @@ -124,14 +124,14 @@
Строки инвентаризации
- + {% if inventory.status != 'completed' %}
-
+
Добавить товар в инвентаризацию
-
- {% include 'products/components/product_search_picker.html' with container_id='inventory-product-picker' title='Поиск товара для инвентаризации...' warehouse_id=inventory.warehouse.id filter_in_stock_only=False categories=categories tags=tags add_button_text='Добавить товар' content_height='250px' skip_stock_filter=True %} +
+ {% include 'products/components/product_search_picker.html' with container_id='inventory-product-picker' title='Поиск товара...' warehouse_id=inventory.warehouse.id filter_in_stock_only=False categories=categories tags=tags add_button_text='Добавить' content_height='150px' skip_stock_filter=True %}
{% endif %} diff --git a/myproject/products/templates/products/components/product_search_picker.html b/myproject/products/templates/products/components/product_search_picker.html index e49dc53..66d0db6 100644 --- a/myproject/products/templates/products/components/product_search_picker.html +++ b/myproject/products/templates/products/components/product_search_picker.html @@ -48,27 +48,27 @@ ProductSearchPicker.init('#writeoff-products', { {% if skip_stock_filter %}data-skip-stock-filter="true"{% endif %}>
- -
+ +
-
{% if show_filters|default:True %} - -
-
+ +
+
{% if categories %}