diff --git a/Screenshot_20260218_155218.png b/Screenshot_20260218_155218.png new file mode 100644 index 0000000..8dd5343 Binary files /dev/null and b/Screenshot_20260218_155218.png differ diff --git a/Screenshot_20260218_160142.png b/Screenshot_20260218_160142.png new file mode 100644 index 0000000..9fcbe58 Binary files /dev/null and b/Screenshot_20260218_160142.png differ diff --git a/Screenshot_20260218_161151.png b/Screenshot_20260218_161151.png new file mode 100644 index 0000000..e422ab0 Binary files /dev/null and b/Screenshot_20260218_161151.png differ diff --git a/Screenshot_20260218_161531.png b/Screenshot_20260218_161531.png new file mode 100644 index 0000000..092ae51 Binary files /dev/null and b/Screenshot_20260218_161531.png differ diff --git a/Screenshot_20260218_162613.png b/Screenshot_20260218_162613.png new file mode 100644 index 0000000..586de14 Binary files /dev/null and b/Screenshot_20260218_162613.png differ diff --git a/Screenshot_20260218_171933.png b/Screenshot_20260218_171933.png new file mode 100644 index 0000000..40cab77 Binary files /dev/null and b/Screenshot_20260218_171933.png differ diff --git a/selections/api.py b/selections/api.py index 54083b1..b7a31df 100644 --- a/selections/api.py +++ b/selections/api.py @@ -204,7 +204,10 @@ def _apply_custom_filters(query, Item, custom_filters): query с применёнными фильтрами """ item_meta = frappe.get_meta('Item') - valid_fields = {f.fieldname for f in item_meta.fields} + valid_fields = {f.fieldname: f for f in item_meta.fields} + + # Для этих типов значение в БД может содержать HTML-обёртку — точное совпадение бессмысленно + text_like_types = {'Small Text', 'Text', 'Long Text', 'Text Editor'} for cf in custom_filters: field = cstr(cf.get('field', '')).strip() @@ -214,8 +217,26 @@ def _apply_custom_filters(query, Item, custom_filters): if not field or field not in valid_fields: continue + field_meta = valid_fields[field] item_field = Item[field] + # Check: значение приходит как 'True'/'False', преобразуем в 1/0 + if field_meta.fieldtype == 'Check': + if value == 'True': + value = 1 + elif value == 'False': + value = 0 + else: + continue # пустой выбор — пропускаем фильтр + + # Для текстовых HTML-полей = и != заменяем на LIKE / NOT LIKE + if field_meta.fieldtype in text_like_types and operator in ('=', '!='): + if operator == '=': + query = query.where(item_field.like(f'%{value}%')) + else: + query = query.where(~item_field.like(f'%{value}%')) + continue + if operator == '=': query = query.where(item_field == value) elif operator == '!=': diff --git a/selections/public/css/selection_dialog.css b/selections/public/css/selection_dialog.css index 201c617..2d105ed 100644 --- a/selections/public/css/selection_dialog.css +++ b/selections/public/css/selection_dialog.css @@ -18,7 +18,7 @@ .selection-dialog-wrapper .modal-body { padding: 15px !important; - overflow: hidden !important; + overflow: clip !important; flex: 1 !important; } @@ -36,7 +36,7 @@ /* 3-колоночный layout */ .selection-dialog-content { display: flex; - gap: 20px; + gap: 0; height: calc(100vh - 130px); min-height: 500px; } @@ -44,8 +44,7 @@ /* Левая колонка: Фильтры */ .selection-filters { flex: 0 0 300px; - border-right: 1px solid var(--border-color); - padding-right: 15px; + padding-right: 12px; display: flex; flex-direction: column; overflow-y: auto; @@ -139,40 +138,85 @@ min-width: 0; } -/* Тулбар поиска (заменяет панель сортировки) */ -.selection-sort-toolbar { +/* ── Поиск в левой панели ─────────────────────────────────────────────── */ +.selection-search-group { + flex-shrink: 0; +} + +/* Убираем margin-bottom у frappe-control внутри поиска */ +.selection-search-group .frappe-control, +.selection-search-group .form-group { + margin-bottom: 0; +} + +/* Иконка поиска для левой панели */ +.selection-search-group .form-group { + position: relative; +} + +.selection-search-group .form-group::before { + content: '\f002'; + font-family: FontAwesome; + position: absolute; + left: 9px; + top: 50%; + transform: translateY(-50%); + color: var(--text-muted); + z-index: 1; + pointer-events: none; + font-size: 13px; +} + +.selection-search-group .form-control { + padding-left: 30px; +} + +/* ── Панель кастомных фильтров над таблицей ───────────────────────────── */ +.selection-filter-bar { display: flex; - align-items: center; - gap: 8px; - padding: 6px 10px; - background-color: var(--bg-light-gray); + flex-direction: column; + gap: 4px; + padding: 8px 10px; + background-color: var(--card-bg); border: 1px solid var(--border-color); border-radius: var(--border-radius); margin-bottom: 8px; flex-shrink: 0; + position: relative; + z-index: 200; + overflow: visible; } -.selection-search-icon { - color: var(--text-muted); - flex-shrink: 0; +/* Awesomplete dropdown должен отображаться поверх таблицы товаров */ +.selection-filter-bar .awesomplete > ul { + z-index: 1060; + position: absolute; } -.selection-search-input-toolbar { - flex: 1; - font-size: 13px; - height: 30px; - padding: 4px 8px; +.selection-filter-bar #custom-filters-list:empty { + display: none; +} + +.selection-filter-bar .btn-add-custom-filter { + align-self: flex-start; + margin-top: 2px; +} + +/* Убираем лишние отступы у frappe-control внутри панели фильтров */ +.selection-filter-bar .frappe-control, +.selection-filter-bar .form-group { + margin-bottom: 0; } /* Ручки resize для панелей */ .resize-handle { - flex: 0 0 6px; + flex: 0 0 5px; cursor: col-resize; background: var(--border-color); - border-radius: 3px; transition: background 0.15s; align-self: stretch; - margin: 0 2px; + touch-action: none; + user-select: none; } .resize-handle:hover, @@ -398,8 +442,7 @@ body.selection-resizing * { /* Правая колонка: Корзина */ .selection-cart { flex: 0 0 290px; - border-left: 1px solid var(--border-color); - padding-left: 15px; + padding-left: 12px; display: flex; flex-direction: column; } @@ -549,66 +592,47 @@ body.selection-resizing * { .custom-filter-row { display: flex; - gap: 4px; + gap: 6px; align-items: center; - margin-bottom: 5px; + margin-bottom: 4px; } -.cf-field { - flex: 1.5; - font-size: 12px; - padding: 3px 6px; - height: auto; +/* Только flex-размеры — внешний вид полностью от Frappe */ +.cf-field-wrap { + flex: 2; min-width: 0; } -.cf-operator { - flex: 1; - font-size: 12px; - padding: 3px 6px; - height: auto; +.cf-operator-wrap { + flex: 0 0 100px; min-width: 0; } -.cf-value { - flex: 1.5; - font-size: 12px; - padding: 3px 6px; - height: auto; +.cf-value-wrap { + flex: 2; min-width: 0; } +.cf-field-wrap .frappe-control, +.cf-operator-wrap .frappe-control, +.cf-value-wrap .frappe-control, +.cf-field-wrap .form-group, +.cf-operator-wrap .form-group, +.cf-value-wrap .form-group { + margin-bottom: 0; +} + .cf-remove { flex-shrink: 0; padding: 2px 6px; } .btn-add-custom-filter { - font-size: 12px; - margin-top: 4px; + font-size: 13px; + margin-top: 2px; } -/* Link-контролы в строках кастомных фильтров */ -.cf-value-link-wrapper { - flex: 1.5; - min-width: 0; -} - -.cf-value-link-wrapper .frappe-control { - margin-bottom: 0; -} - -.cf-value-link-wrapper .form-group { - margin-bottom: 0; -} - -.cf-value-link-wrapper input.form-control { - font-size: 12px; - padding: 3px 6px; - height: auto; -} - -/* Link-контролы для склада и прайса в левой панели */ +/* Link-контролы для склада и прайса */ #selection-warehouse-link .frappe-control, #selection-pricelist-link .frappe-control { margin-bottom: 0; diff --git a/selections/public/js/selection_dialog.js b/selections/public/js/selection_dialog.js index c7a585b..6dc5c72 100644 --- a/selections/public/js/selection_dialog.js +++ b/selections/public/js/selection_dialog.js @@ -51,8 +51,11 @@ class SelectionDialog { get_dialog_html() { return `