final look
This commit is contained in:
parent
39213fec27
commit
f7e1657f1a
|
|
@ -48,6 +48,7 @@ def get_items_for_selection(filters=None, start=0, page_length=50, custom_filter
|
||||||
company = filters.get('company')
|
company = filters.get('company')
|
||||||
transaction_date = filters.get('transaction_date')
|
transaction_date = filters.get('transaction_date')
|
||||||
only_in_stock = filters.get('only_in_stock', False)
|
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_by = filters.get('sort_by', 'item_name') # item_name, available_qty, rate
|
||||||
sort_order = filters.get('sort_order', 'asc') # asc, desc
|
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)
|
.where(Item.is_sales_item == 1)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if maintain_stock:
|
||||||
|
query = query.where(Item.is_stock_item == 1)
|
||||||
|
|
||||||
# Фильтр по поиску
|
# Фильтр по поиску
|
||||||
if search_term:
|
if search_term:
|
||||||
search_condition = (
|
search_condition = (
|
||||||
|
|
|
||||||
|
|
@ -296,6 +296,25 @@ body.selection-resizing * {
|
||||||
outline-offset: -2px;
|
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 {
|
.item-image-cell {
|
||||||
width: 50px;
|
width: 50px;
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,14 @@ class SelectionDialog {
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="filter-group">
|
||||||
<label>${__('Item Group')}</label>
|
<label>${__('Item Group')}</label>
|
||||||
<div id="selection-item-group-tree" class="item-group-tree">
|
<div id="selection-item-group-tree" class="item-group-tree">
|
||||||
|
|
@ -218,6 +226,13 @@ class SelectionDialog {
|
||||||
|
|
||||||
// Поиск — обработчик вешается в setup_search_field()
|
// Поиск — обработчик вешается в 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() {
|
this.dialog.$wrapper.find('#selection-only-in-stock').on('change', function() {
|
||||||
if (!me.filters.warehouse) {
|
if (!me.filters.warehouse) {
|
||||||
|
|
@ -239,6 +254,7 @@ class SelectionDialog {
|
||||||
if (me.warehouse_field) me.warehouse_field.set_value('');
|
if (me.warehouse_field) me.warehouse_field.set_value('');
|
||||||
if (me.pricelist_field) me.pricelist_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-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.dialog.$wrapper.find('.item-group-tree .active').removeClass('active');
|
||||||
me._update_stock_toggle_state();
|
me._update_stock_toggle_state();
|
||||||
// Очищаем произвольные фильтры
|
// Очищаем произвольные фильтры
|
||||||
|
|
@ -353,29 +369,33 @@ class SelectionDialog {
|
||||||
const $rows = this.dialog.$wrapper.find('#selection-items-grid tbody tr');
|
const $rows = this.dialog.$wrapper.find('#selection-items-grid tbody tr');
|
||||||
const row_count = $rows.length;
|
const row_count = $rows.length;
|
||||||
|
|
||||||
if (row_count === 0) return;
|
|
||||||
|
|
||||||
if (e.key === 'ArrowDown') {
|
if (e.key === 'ArrowDown') {
|
||||||
|
if (row_count === 0) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.focused_row_index = Math.min(this.focused_row_index + 1, row_count - 1);
|
this.focused_row_index = Math.min(this.focused_row_index + 1, row_count - 1);
|
||||||
this._update_focused_row($rows);
|
this._update_focused_row($rows);
|
||||||
|
$rows.eq(this.focused_row_index).find('.row-qty-input').focus().select();
|
||||||
} else if (e.key === 'ArrowUp') {
|
} else if (e.key === 'ArrowUp') {
|
||||||
|
if (row_count === 0) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.focused_row_index = Math.max(this.focused_row_index - 1, 0);
|
this.focused_row_index = Math.max(this.focused_row_index - 1, 0);
|
||||||
this._update_focused_row($rows);
|
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) {
|
} 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]) {
|
if (this.focused_row_index >= 0 && this.items_data[this.focused_row_index]) {
|
||||||
e.preventDefault();
|
|
||||||
const $row = $rows.eq(this.focused_row_index);
|
const $row = $rows.eq(this.focused_row_index);
|
||||||
const qty_val = parseFloat(($row.find('.row-qty-input').val() || '').replace(',', '.')) || 1;
|
const qty_val = parseFloat(($row.find('.row-qty-input').val() || '').replace(',', '.')) || 1;
|
||||||
const item = this.items_data[this.focused_row_index];
|
const item = this.items_data[this.focused_row_index];
|
||||||
this.add_to_cart(item, qty_val);
|
this.add_to_cart(item, qty_val);
|
||||||
}
|
}
|
||||||
} else if (e.key === 'Enter' && e.ctrlKey) {
|
|
||||||
// Добавить всё в документ
|
|
||||||
e.preventDefault();
|
|
||||||
this.finalize_selection();
|
|
||||||
} else if (e.key === 'Escape') {
|
} else if (e.key === 'Escape') {
|
||||||
this.dialog.hide();
|
this.dialog.hide();
|
||||||
}
|
}
|
||||||
|
|
@ -555,6 +575,7 @@ class SelectionDialog {
|
||||||
company: this.frm.doc.company || '',
|
company: this.frm.doc.company || '',
|
||||||
transaction_date: this.frm.doc.posting_date || frappe.datetime.get_today(),
|
transaction_date: this.frm.doc.posting_date || frappe.datetime.get_today(),
|
||||||
only_in_stock: this.filters.only_in_stock || false,
|
only_in_stock: this.filters.only_in_stock || false,
|
||||||
|
maintain_stock: this.filters.maintain_stock || false,
|
||||||
sort_by: this.sort_by,
|
sort_by: this.sort_by,
|
||||||
sort_order: this.sort_order
|
sort_order: this.sort_order
|
||||||
};
|
};
|
||||||
|
|
@ -645,6 +666,13 @@ class SelectionDialog {
|
||||||
<td class="item-image-cell">${img_html}</td>
|
<td class="item-image-cell">${img_html}</td>
|
||||||
<td>
|
<td>
|
||||||
<strong>${item.item_name}</strong>
|
<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>
|
<br><small class="text-muted">${item.item_code}</small>
|
||||||
${item.item_group ? `<br><small class="text-muted">${item.item_group}</small>` : ''}
|
${item.item_group ? `<br><small class="text-muted">${item.item_group}</small>` : ''}
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -713,10 +741,12 @@ class SelectionDialog {
|
||||||
|
|
||||||
// Клик по строке для навигации и инфо-панели
|
// Клик по строке для навигации и инфо-панели
|
||||||
$grid.find('tbody tr').on('click', function(e) {
|
$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.focused_row_index = parseInt($(this).data('item-index'));
|
||||||
me._update_focused_row($grid.find('tbody tr'));
|
me._update_focused_row($grid.find('tbody tr'));
|
||||||
me.show_item_info(me.focused_row_index);
|
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 item_index = parseInt($(this).data('item-index'));
|
||||||
const is_whole = me.items_data[item_index] && me.items_data[item_index].must_be_whole_number;
|
const is_whole = me.items_data[item_index] && me.items_data[item_index].must_be_whole_number;
|
||||||
$(this).find('.row-qty-input')
|
$(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 остатков по складам
|
// Tooltip остатков по складам
|
||||||
|
|
@ -1315,27 +1359,15 @@ class SelectionDialog {
|
||||||
}
|
}
|
||||||
|
|
||||||
_handle_qty_keydown(e, is_whole) {
|
_handle_qty_keydown(e, is_whole) {
|
||||||
// Останавливаем всплытие — иначе dialog._handle_keydown перехватит стрелки и Enter
|
// ArrowUp/Down пропускаем наверх — _handle_keydown обработает навигацию по строкам
|
||||||
|
if (e.key === 'ArrowUp' || e.key === 'ArrowDown') return;
|
||||||
|
|
||||||
|
// Останавливаем всплытие для всех остальных клавиш
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
// Разрешаем управляющие комбинации (Ctrl+A, Ctrl+C, Ctrl+V и т.п.)
|
// Разрешаем управляющие комбинации (Ctrl+A, Ctrl+C, Ctrl+V и т.п.)
|
||||||
if (e.ctrlKey || e.metaKey) return;
|
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 = [
|
const control_keys = [
|
||||||
'Backspace', 'Delete', 'Tab', 'Escape',
|
'Backspace', 'Delete', 'Tab', 'Escape',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue