From df457a134103d3a275052535004e65acc8d2a76c Mon Sep 17 00:00:00 2001 From: NotAbdelrahmanelsayed Date: Tue, 9 Jun 2026 20:36:30 +0200 Subject: [PATCH] feat(cart): add LIFO cart order setting (newest item on top) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a "LIFO Cart Order (Newest on Top)" POS setting. When enabled and no explicit cart sort is active, the most recently added item is shown at the top of the cart instead of the bottom — handy on long carts where the cashier wants to see what was just scanned. - New cart_lifo check field on POS Settings (default off) + backend constants (POS_SETTINGS_FIELDS / DEFAULT_POS_SETTINGS). - posSettings store exposes a cartLifo computed. - useCartSort accepts an optional lifoMode; reverses the list when LIFO is on and no sort column is selected. Explicit sorts are unaffected. - InvoiceCart passes the setting through; toggle lives in the Sales Operations settings group so it takes effect live via reloadSettings(). Co-Authored-By: Claude Opus 4.8 --- POS/src/components/sale/InvoiceCart.vue | 5 ++++- POS/src/components/settings/POSSettings.vue | 6 ++++++ POS/src/composables/useCartSort.js | 12 ++++++++++-- POS/src/stores/posEvents.js | 3 ++- POS/src/stores/posSettings.js | 4 ++++ pos_next/api/constants.py | 2 ++ .../pos_next/doctype/pos_settings/pos_settings.json | 8 ++++++++ 7 files changed, 36 insertions(+), 4 deletions(-) diff --git a/POS/src/components/sale/InvoiceCart.vue b/POS/src/components/sale/InvoiceCart.vue index d91835154..b78f63706 100644 --- a/POS/src/components/sale/InvoiceCart.vue +++ b/POS/src/components/sale/InvoiceCart.vue @@ -1372,7 +1372,10 @@ const { sortedItems, CART_SORT_OPTIONS, CART_SORT_ICONS, toggleCartSortDropdown, handleCartSortToggle, getCartSortLabel, getCartSortIconState, -} = useCartSort(() => props.items); +} = useCartSort( + () => props.items, + computed(() => settingsStore.cartLifo), +); /** * ============================================================================ diff --git a/POS/src/components/settings/POSSettings.vue b/POS/src/components/settings/POSSettings.vue index 89ca3e85e..5d86d7bb6 100644 --- a/POS/src/components/settings/POSSettings.vue +++ b/POS/src/components/settings/POSSettings.vue @@ -358,6 +358,11 @@ :label="__('Allow Partial Payment')" :description="__('Enable partial payment for invoices')" /> + Array} itemsGetter - Getter (or ref) that returns the raw cart * items array. Typically `() => props.items`. */ -export function useCartSort(itemsGetter) { +export function useCartSort(itemsGetter, lifoMode = null) { // Normalise getter once — avoid typeof check on every access const getItems = typeof itemsGetter === 'function' ? itemsGetter @@ -100,7 +100,15 @@ export function useCartSort(itemsGetter) { const items = getItems() const field = cartSortBy.value - if (!field) return items + if (!field) { + // No explicit sort: honour the LIFO setting (newest added on top) + const lifo = lifoMode + ? typeof lifoMode === 'function' + ? lifoMode() + : lifoMode.value + : false + return lifo ? [...items].reverse() : items + } const dir = cartSortOrder.value === 'asc' ? 1 : -1 diff --git a/POS/src/stores/posEvents.js b/POS/src/stores/posEvents.js index 9d1c4af62..9f8e8cdeb 100644 --- a/POS/src/stores/posEvents.js +++ b/POS/src/stores/posEvents.js @@ -266,7 +266,8 @@ export const usePOSEventsStore = defineStore('posEvents', () => { 'allow_return', 'allow_write_off_change', 'allow_partial_payment', - 'silent_print' + 'silent_print', + 'cart_lifo' ] const salesChanges = salesFields.filter(field => field in changes) if (salesChanges.length > 0) { diff --git a/POS/src/stores/posSettings.js b/POS/src/stores/posSettings.js index 9278faf4e..654ef5a8a 100644 --- a/POS/src/stores/posSettings.js +++ b/POS/src/stores/posSettings.js @@ -35,6 +35,7 @@ export const usePOSSettingsStore = defineStore("posSettings", () => { display_discount_percentage: 0, display_discount_amount: 0, show_variants_as_items: 0, + cart_lifo: 0, // Operations allow_sales_order: 0, allow_select_sales_order: 0, @@ -149,6 +150,7 @@ export const usePOSSettingsStore = defineStore("posSettings", () => { const showVariantsAsItems = computed(() => Boolean(settings.value.show_variants_as_items), ) + const cartLifo = computed(() => Boolean(settings.value.cart_lifo)) // Computed - Operations const allowSalesOrder = computed(() => @@ -320,6 +322,7 @@ export const usePOSSettingsStore = defineStore("posSettings", () => { display_discount_percentage: 0, display_discount_amount: 0, show_variants_as_items: 0, + cart_lifo: 0, allow_sales_order: 0, allow_select_sales_order: 0, create_only_sales_order: 0, @@ -435,6 +438,7 @@ export const usePOSSettingsStore = defineStore("posSettings", () => { displayDiscountPercentage, displayDiscountAmount, showVariantsAsItems, + cartLifo, // Computed - Operations allowSalesOrder, diff --git a/pos_next/api/constants.py b/pos_next/api/constants.py index 059c04ea0..d9058b781 100644 --- a/pos_next/api/constants.py +++ b/pos_next/api/constants.py @@ -40,6 +40,7 @@ "enable_session_lock", "session_lock_timeout", "show_variants_as_items", + "cart_lifo", ] # Default POS Settings values @@ -70,4 +71,5 @@ "enable_session_lock": 0, "session_lock_timeout": 5, "show_variants_as_items": 0, + "cart_lifo": 0, } diff --git a/pos_next/pos_next/doctype/pos_settings/pos_settings.json b/pos_next/pos_next/doctype/pos_settings/pos_settings.json index 5aa983bbe..2f06bbc6e 100644 --- a/pos_next/pos_next/doctype/pos_settings/pos_settings.json +++ b/pos_next/pos_next/doctype/pos_settings/pos_settings.json @@ -37,6 +37,7 @@ "display_discount_percentage", "display_discount_amount", "show_variants_as_items", + "cart_lifo", "section_break_operations", "allow_sales_order", "allow_select_sales_order", @@ -303,6 +304,13 @@ "fieldtype": "Check", "label": "Show Variants as Items" }, + { + "default": "0", + "description": "Show most recently added item at the top of the cart", + "fieldname": "cart_lifo", + "fieldtype": "Check", + "label": "LIFO Cart Order (Newest on Top)" + }, { "collapsible": 1, "fieldname": "section_break_operations",