final look

This commit is contained in:
Ali 2026-02-18 20:17:31 +04:00
parent 39213fec27
commit f7e1657f1a
3 changed files with 81 additions and 26 deletions

View File

@ -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 = (

View File

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

View File

@ -75,6 +75,14 @@ class SelectionDialog {
</small>
</div>
</div>
<div class="filter-group">
<div class="selection-stock-toggle">
<label class="stock-toggle-label">
<input type="checkbox" id="selection-maintain-stock">
<span>${__('Maintain stock only')}</span>
</label>
</div>
</div>
<div class="filter-group">
<label>${__('Item Group')}</label>
<div id="selection-item-group-tree" class="item-group-tree">
@ -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 {
<td class="item-image-cell">${img_html}</td>
<td>
<strong>${item.item_name}</strong>
<a href="/app/item/${encodeURIComponent(item.item_code)}"
target="_blank"
class="item-open-link"
title="${__('Open Item')}"
onclick="event.stopPropagation()">
<i class="fa fa-external-link"></i>
</a>
<br><small class="text-muted">${item.item_code}</small>
${item.item_group ? `<br><small class="text-muted">${item.item_group}</small>` : ''}
</td>
@ -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',