Updated ItemsSelector to switch filter tabs dynamically#163
Updated ItemsSelector to switch filter tabs dynamically#163MohamedAliSmk wants to merge 5 commits intodevelopfrom
Conversation
…de pos with some code change in api that return items after filter pos profile data
If sort field is brand, tabs show brands (All Brands + brand list). Otherwise tabs show item groups (All Items + item groups). Replaced the sort option from item_group to brand in ItemsSelector. Added brand-aware filter handling in itemSearch store: New state: selectedBrand, brands New resource: brandsResource -> calls pos_next.api.items.get_brands New actions: loadBrands(), setSelectedBrand() Updated server calls to include brand where needed (get_items, get_items_count) for: tab selection pagination search Added brand sorting in computed filteredItems (sortBy === 'brand'). Kept group flow intact; switching between modes clears the opposite filter to avoid conflicts.
engahmed1190
left a comment
There was a problem hiding this comment.
PR #163 — Full Review
Note: The GitHub diff only shows 2 files (frontend), but the branch includes 4 earlier commits with backend changes (
items.py,pos_profile.py,POS Brands Detaildoctype). This review covers the full branch diff againstdevelop.
🔴 CRITICAL — Build-Breaking: Duplicate case 'brand' in filteredItems sort switch
POS/src/stores/itemSearch.js lines 590–614
The sort switch block has two identical case 'brand': entries, each declaring const brandA and const brandB. Since all case blocks in a switch share the same lexical scope in JavaScript, this causes:
SyntaxError: Identifier 'brandA' has already been declared
The app will not build. This is a fatal error.
case 'brand': // line 590
const brandA = (a.brand || '').toLowerCase()
const brandB = (b.brand || '').toLowerCase()
compareResult = brandA.localeCompare(brandB)
break
case 'item_group':
// ...
break
case 'brand': // line 609 — DUPLICATE
const brandA = (a.brand || '').toLowerCase() // ← SyntaxError
const brandB = (b.brand || '').toLowerCase()
compareResult = brandA.localeCompare(brandB)
breakFix: Remove the second case 'brand': block (lines 609–614) entirely.
🔴 CRITICAL — Duplicate brand in SORT_OPTIONS renders two "Brand" buttons
POS/src/components/sale/ItemsSelector.vue lines 853–867
SORT_OPTIONS has field: 'brand' at index 1 (line 854) and again at index 3 (line 864). The UI will render two separate "Brand" buttons in the sort dropdown with different icons. The second entry also replaced what was originally item_group.
SORT_OPTIONS = [
{ field: 'name', ... },
{ field: 'brand', ... }, // line 853 — first brand entry
{ field: 'quantity', ... },
{ field: 'brand', ... }, // line 863 — DUPLICATE, was item_group
{ field: 'price', ... },
{ field: 'item_code', ... },
]Fix: Remove the duplicate at line 863–867 and restore the item_group sort option.
🔴 CRITICAL — get_items() does not accept or forward brand parameter
pos_next/api/items.py line 1096
Current signature:
def get_items(pos_profile, search_term=None, item_group=None, start=0, limit=20,
include_variants=0, show_variants_as_items=0):No brand parameter. The call to _build_item_base_conditions() at lines 1124–1128 never passes brand:
conditions, params, extra_joins = _build_item_base_conditions(
pos_profile_doc, item_group, exclude_variants=exclude_variants,
exclude_templates=exclude_templates,
hide_unavailable=hide_unavailable, warehouse=pos_profile_doc.warehouse,
)The frontend passes brand in these calls that all target get_items:
fetchItemsForBrand()— line 1093:brand: brandsearchItems()— line 1626:brand: selectedBrand.valuesetSelectedBrand()"All Items" — line 2010:brand: undefinedsetSelectedItemGroup()"All Items" — line 1884:brand: undefinedfetchPage()— line 1173:brand: undefinedloadMoreItems()— line 1272:brand: undefined
Frappe silently ignores unknown kwargs, so brand filtering is completely non-functional server-side. Users will see unfiltered results when clicking a brand tab.
Fix: Add brand=None to the get_items() signature and pass brand=brand to _build_item_base_conditions().
🔴 CRITICAL — get_items_count() does not accept or forward brand parameter
pos_next/api/items.py line 1642
def get_items_count(pos_profile, item_group=None, include_variants=0, show_variants_as_items=0):No brand parameter. The frontend passes brand in:
setSelectedBrand()count call — line 1995:brand: brand || undefinedsetSelectedItemGroup()count call — line 1867:brand: undefined
The count will always be the total item count, not brand-filtered, causing incorrect pagination numbers.
Fix: Add brand=None to the signature and pass brand=brand to _build_item_base_conditions().
🔴 CRITICAL — No Custom Field fixture for brands on POS Profile
The POS Brands Detail child doctype is created (istable: 1), and pos_profile.py does pos_profile.append("brands", {"brand": brand_name}) in both create_pos_profile() (line 556) and update_pos_profile() (line 638). However:
hooks.pyfixtures — noPOS Profile-brandsentry existscustom_field.json— no Table field for brands- No
install.py/after_migrate/after_installhook creates the field programmatically - No Property Setter or Client Script adds it dynamically
Without a brands Table field (type Table, options POS Brands Detail) on the POS Profile doctype:
pos_profile.append("brands", ...)will raise an error at save time_get_allowed_profile_brands()queriestabPOS Brands Detailwithparent = pos_profile— always returns emptyget_brands()falls back to returning all brands globally (up to 50), ignoring POS Profile config entirely
Fix: Add a Custom Field entry to the fixtures in hooks.py:
"POS Profile-brands" # fieldtype: Table, options: POS Brands DetailAnd create the corresponding fixture JSON or ensure it gets exported.
🟡 MEDIUM — Broken indentation in loadMoreItems
POS/src/stores/itemSearch.js lines 1254–1282
The if / else if / else block has inconsistent indentation:
[4 tabs] if (selectedItemGroup.value) {
[4 tabs] list = await fetchItemsForGroup(...)
[4 tabs] } else if (selectedBrand.value) {
[5 tabs] list = await fetchItemsForBrand(...) ← extra indent in body
[3 tabs] } else { ← drops one indent level
[4 tabs] const response = await call(...)
[6 tabs] brand: undefined, ← extra indent vs sibling props
[3 tabs] } ← mismatched closing brace
This won't cause a syntax error (braces still match), but it's confusing and suggests the else if block was pasted without adjusting indentation.
🟡 MEDIUM — item_group sort option removed
SORT_OPTIONS in ItemsSelector.vue no longer has field: 'item_group'. It was replaced by the (duplicate) second brand entry. The case 'item_group' handler still exists in the sort switch (line 602), but it's unreachable from the UI since there's no button for it.
Users lose the ability to sort by item group.
Fix: Keep both item_group and brand as separate sort options.
🟡 MEDIUM — Eager loadBrands() on every POS session start
loadBrands() is called inside setPosProfile() (around line 2094 in the store), meaning every POS session makes an extra API call to get_brands even if the user never uses brand sorting.
Fix: Only call loadBrands() lazily when sortBy switches to 'brand' (the watcher in ItemsSelector.vue already does this — remove the eager call from setPosProfile).
Summary
| # | Severity | Issue | Location |
|---|---|---|---|
| 1 | 🔴 Build-breaking | Duplicate case 'brand' — SyntaxError on const brandA redeclaration |
itemSearch.js:590,609 |
| 2 | 🔴 Build-breaking | Duplicate field: 'brand' in SORT_OPTIONS — two "Brand" buttons |
ItemsSelector.vue:853,863 |
| 3 | 🔴 Critical | get_items() ignores brand param — no server-side filtering |
items.py:1096 |
| 4 | 🔴 Critical | get_items_count() ignores brand param — wrong pagination counts |
items.py:1642 |
| 5 | 🔴 Critical | No Custom Field for brands on POS Profile — brands can't be saved |
hooks.py fixtures |
| 6 | 🟡 Medium | Broken indentation in loadMoreItems |
itemSearch.js:1254-1282 |
| 7 | 🟡 Medium | item_group sort option removed from UI |
ItemsSelector.vue:863 |
| 8 | 🟡 Medium | Unnecessary loadBrands() call on every session start |
itemSearch.js setPosProfile |
f95e89c to
17442eb
Compare
dynamic change of sort option based on the selected filter Fixed: 1. Removed duplicate case 'brand' from itemSearch.js:590,609 2. Removed duplicate field: 'brand' in SORT_OPTIONS from ItemsSelector.vue:853,863 3. Added brand param to get_items() and get_items_count() in items.py 4. Added Custom Field for brands as export customization and removed all custom fields from POS Profile in hooks.py fixtures 5. Fixed indentation in loadMoreItems in itemSearch.js:1254-1282 6. Added item_group sort option back to UI in ItemsSelector.vue:863 7. Removed unnecessary loadBrands() call on every session start in itemSearch.js setPosProfile
If sort field is brand, tabs show brands (All Brands + brand list). Otherwise tabs show item groups (All Items + item groups). Replaced the sort option from item_group to brand in ItemsSelector. Added brand-aware filter handling in itemSearch store: New state: selectedBrand, brands
New resource: brandsResource -> calls pos_next.api.items.get_brands New actions: loadBrands(), setSelectedBrand()
Updated server calls to include brand where needed (get_items, get_items_count) for: tab selection
pagination
search
Added brand sorting in computed filteredItems (sortBy === 'brand'). Kept group flow intact; switching between modes clears the opposite filter to avoid conflicts.