diff --git a/selections/api.py b/selections/api.py index b7a31df..8493c2f 100644 --- a/selections/api.py +++ b/selections/api.py @@ -48,6 +48,7 @@ def get_items_for_selection(filters=None, start=0, page_length=50, custom_filter company = filters.get('company') transaction_date = filters.get('transaction_date') only_in_stock = filters.get('only_in_stock', False) + maintain_stock = filters.get('maintain_stock', False) sort_by = filters.get('sort_by', 'item_name') # item_name, available_qty, rate sort_order = filters.get('sort_order', 'asc') # asc, desc @@ -70,6 +71,9 @@ def get_items_for_selection(filters=None, start=0, page_length=50, custom_filter .where(Item.is_sales_item == 1) ) + if maintain_stock: + query = query.where(Item.is_stock_item == 1) + # Фильтр по поиску if search_term: search_condition = ( diff --git a/selections/public/css/selection_dialog.css b/selections/public/css/selection_dialog.css index 2d105ed..7ebb45a 100644 --- a/selections/public/css/selection_dialog.css +++ b/selections/public/css/selection_dialog.css @@ -296,6 +296,25 @@ body.selection-resizing * { outline-offset: -2px; } +/* Иконка открытия товара в новом окне */ +.item-open-link { + margin-left: 6px; + color: var(--primary-color); + opacity: 0.25; + font-size: 14px; + vertical-align: middle; + transition: opacity 0.15s; +} + +.selection-table tbody tr:hover .item-open-link { + opacity: 1; +} + +.item-open-link:hover { + opacity: 1; + text-decoration: none; +} + /* Изображения товаров */ .item-image-cell { width: 50px; diff --git a/selections/public/js/selection_dialog.js b/selections/public/js/selection_dialog.js index 6dc5c72..a19ce25 100644 --- a/selections/public/js/selection_dialog.js +++ b/selections/public/js/selection_dialog.js @@ -75,6 +75,14 @@ class SelectionDialog { +
+
+ +
+
@@ -218,6 +226,13 @@ class SelectionDialog { // Поиск — обработчик вешается в setup_search_field() + // Фильтр "Только складские товары" + this.dialog.$wrapper.find('#selection-maintain-stock').on('change', function() { + me.filters.maintain_stock = $(this).prop('checked'); + me.current_page = 0; + me.load_items(); + }); + // Фильтр "Только в наличии" this.dialog.$wrapper.find('#selection-only-in-stock').on('change', function() { if (!me.filters.warehouse) { @@ -239,6 +254,7 @@ class SelectionDialog { if (me.warehouse_field) me.warehouse_field.set_value(''); if (me.pricelist_field) me.pricelist_field.set_value(''); me.dialog.$wrapper.find('#selection-only-in-stock').prop('checked', false); + me.dialog.$wrapper.find('#selection-maintain-stock').prop('checked', false); me.dialog.$wrapper.find('.item-group-tree .active').removeClass('active'); me._update_stock_toggle_state(); // Очищаем произвольные фильтры @@ -353,29 +369,33 @@ class SelectionDialog { const $rows = this.dialog.$wrapper.find('#selection-items-grid tbody tr'); const row_count = $rows.length; - if (row_count === 0) return; - if (e.key === 'ArrowDown') { + if (row_count === 0) return; e.preventDefault(); this.focused_row_index = Math.min(this.focused_row_index + 1, row_count - 1); this._update_focused_row($rows); + $rows.eq(this.focused_row_index).find('.row-qty-input').focus().select(); } else if (e.key === 'ArrowUp') { + if (row_count === 0) return; e.preventDefault(); this.focused_row_index = Math.max(this.focused_row_index - 1, 0); this._update_focused_row($rows); + $rows.eq(this.focused_row_index).find('.row-qty-input').focus().select(); + } else if (e.key === 'Enter' && e.ctrlKey) { + // Добавить всё в документ + e.preventDefault(); + e.stopPropagation(); + this.finalize_selection(); } else if (e.key === 'Enter' && !e.ctrlKey) { - // Добавить выделенный товар в корзину + // Всегда блокируем — иначе Frappe перехватит и вызовет primary action + e.preventDefault(); + e.stopPropagation(); if (this.focused_row_index >= 0 && this.items_data[this.focused_row_index]) { - e.preventDefault(); const $row = $rows.eq(this.focused_row_index); const qty_val = parseFloat(($row.find('.row-qty-input').val() || '').replace(',', '.')) || 1; const item = this.items_data[this.focused_row_index]; this.add_to_cart(item, qty_val); } - } else if (e.key === 'Enter' && e.ctrlKey) { - // Добавить всё в документ - e.preventDefault(); - this.finalize_selection(); } else if (e.key === 'Escape') { this.dialog.hide(); } @@ -555,6 +575,7 @@ class SelectionDialog { company: this.frm.doc.company || '', transaction_date: this.frm.doc.posting_date || frappe.datetime.get_today(), only_in_stock: this.filters.only_in_stock || false, + maintain_stock: this.filters.maintain_stock || false, sort_by: this.sort_by, sort_order: this.sort_order }; @@ -645,6 +666,13 @@ class SelectionDialog { ${img_html} ${item.item_name} + + +
${item.item_code} ${item.item_group ? `
${item.item_group}` : ''} @@ -713,10 +741,12 @@ class SelectionDialog { // Клик по строке для навигации и инфо-панели $grid.find('tbody tr').on('click', function(e) { - if (!$(e.target).closest('.btn-add-to-cart, .row-qty-input, .stock-indicator').length) { + if (!$(e.target).closest('.btn-add-to-cart, .row-qty-input, .stock-indicator, .item-open-link').length) { me.focused_row_index = parseInt($(this).data('item-index')); me._update_focused_row($grid.find('tbody tr')); me.show_item_info(me.focused_row_index); + // Фокус на qty чтобы сразу можно было вводить количество + $(this).find('.row-qty-input').focus().select(); } }); @@ -725,7 +755,21 @@ class SelectionDialog { const item_index = parseInt($(this).data('item-index')); const is_whole = me.items_data[item_index] && me.items_data[item_index].must_be_whole_number; $(this).find('.row-qty-input') - .on('keydown', function(e) { me._handle_qty_keydown(e, is_whole); }); + .on('keydown', function(e) { + // Enter в qty — добавить в корзину, не пропускать дальше + if (e.key === 'Enter' && !e.ctrlKey) { + e.preventDefault(); + e.stopPropagation(); + const qty_val = parseFloat(($(this).val() || '').replace(',', '.')) || 1; + if (me.items_data[item_index]) { + me.add_to_cart(me.items_data[item_index], qty_val); + } + return; + } + // Ctrl+Enter пропускаем вверх — _handle_keydown вызовет finalize_selection + if (e.key === 'Enter' && e.ctrlKey) return; + me._handle_qty_keydown(e, is_whole); + }); }); // Tooltip остатков по складам @@ -1315,27 +1359,15 @@ class SelectionDialog { } _handle_qty_keydown(e, is_whole) { - // Останавливаем всплытие — иначе dialog._handle_keydown перехватит стрелки и Enter + // ArrowUp/Down пропускаем наверх — _handle_keydown обработает навигацию по строкам + if (e.key === 'ArrowUp' || e.key === 'ArrowDown') return; + + // Останавливаем всплытие для всех остальных клавиш e.stopPropagation(); // Разрешаем управляющие комбинации (Ctrl+A, Ctrl+C, Ctrl+V и т.п.) if (e.ctrlKey || e.metaKey) return; - // Стрелки вверх/вниз — шаг +1/-1 - if (e.key === 'ArrowUp') { - e.preventDefault(); - const current = parseFloat(e.target.value.replace(',', '.')) || 0; - e.target.value = current + 1; - return; - } - if (e.key === 'ArrowDown') { - e.preventDefault(); - const current = parseFloat(e.target.value.replace(',', '.')) || 0; - const min_val = is_whole ? 1 : 0.001; - e.target.value = Math.max(min_val, current - 1); - return; - } - // Всегда разрешаем служебные клавиши const control_keys = [ 'Backspace', 'Delete', 'Tab', 'Escape',