From 0a6183e52a814adb06b68ba2163c49932cee3c2e Mon Sep 17 00:00:00 2001 From: abinandabinand21-sudo Date: Thu, 26 Feb 2026 11:23:11 +0530 Subject: [PATCH 1/7] Added keyboard shortcuts for POS (View Shift, Promotion, Return, etc) --- POS/src/main.js | 88 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/POS/src/main.js b/POS/src/main.js index 4e19c274..34f61050 100644 --- a/POS/src/main.js +++ b/POS/src/main.js @@ -229,3 +229,91 @@ async function initializeApp() { } initializeApp() + +window.addEventListener('keydown', function (e) { + + const key = e.key.toLowerCase() + const active = document.activeElement + + const boxShortcuts = { + 'o': "View Shift", + 'd': "Draft Invoices", + 'i': "Invoice History", + 'k': "Return Invoice", + 'p': "Close Shift", + 'f': "Create Customer" + } + + if (e.ctrlKey && boxShortcuts[key]) { + e.preventDefault() + e.stopPropagation() + + const targetText = boxShortcuts[key] + + console.log(`CTRL + ${key.toUpperCase()} → ${targetText}`) + + setTimeout(() => { + const btn = Array.from(document.querySelectorAll("button")) + .find(b => b.innerText?.trim() === targetText) + + if (btn) { + btn.click() + } else { + console.log(`${targetText} button not found ❌`) + } + }, 300) + + return + } + + if (e.ctrlKey && key === 'b') { + e.preventDefault() + + const input = document.querySelector('input[placeholder*="Search"]') + input?.focus() + + console.log("Item search focused ✅") + return + } + + if (e.ctrlKey && key === 'u') { + e.preventDefault() + + const input = document.querySelector('input[placeholder*="customer"]') + input?.focus() + + console.log("Customer search focused ✅") + return + } + + if (e.ctrlKey && e.shiftKey && key === 'x') { + e.preventDefault() + + const btn = Array.from(document.querySelectorAll("button")) + .find(b => + b.innerText?.toLowerCase().includes("promotion") || + b.innerText?.toLowerCase().includes("coupon") + ) + + if (btn) { + btn.click() + console.log("Promotion popup opened ✅") + } else { + console.log("Promotion button not found ❌") + } + + return + } + + if (e.ctrlKey && e.shiftKey && key === 'm') { + e.preventDefault() + + const btn = Array.from(document.querySelectorAll("button")) + .find(b => b.innerText?.includes("Return")) + + btn?.click() + console.log("Return mode activated ✅") + return + } + +}, true) \ No newline at end of file From e67bea84bdba5304a5b9711508f588d46336ed60 Mon Sep 17 00:00:00 2001 From: abinandabinand21-sudo Date: Thu, 26 Feb 2026 16:39:08 +0530 Subject: [PATCH 2/7] Add keyboard shortcuts for POS operations --- POS/src/main.js | 48 +++++++++++++++++------------------------------- 1 file changed, 17 insertions(+), 31 deletions(-) diff --git a/POS/src/main.js b/POS/src/main.js index 34f61050..24468576 100644 --- a/POS/src/main.js +++ b/POS/src/main.js @@ -235,6 +235,15 @@ window.addEventListener('keydown', function (e) { const key = e.key.toLowerCase() const active = document.activeElement + // Prevent shortcuts while typing + if (active && ( + active.tagName === "INPUT" || + active.tagName === "TEXTAREA" || + active.isContentEditable + )) { + return + } + const boxShortcuts = { 'o': "View Shift", 'd': "Draft Invoices", @@ -244,48 +253,42 @@ window.addEventListener('keydown', function (e) { 'f': "Create Customer" } + // Box Actions if (e.ctrlKey && boxShortcuts[key]) { e.preventDefault() e.stopPropagation() const targetText = boxShortcuts[key] - console.log(`CTRL + ${key.toUpperCase()} → ${targetText}`) - setTimeout(() => { const btn = Array.from(document.querySelectorAll("button")) - .find(b => b.innerText?.trim() === targetText) + .find(b => b.innerText?.trim().toLowerCase() === targetText.toLowerCase()) - if (btn) { - btn.click() - } else { - console.log(`${targetText} button not found ❌`) - } + if (btn) btn.click() }, 300) - return + return } + // Focus Item Search if (e.ctrlKey && key === 'b') { e.preventDefault() const input = document.querySelector('input[placeholder*="Search"]') input?.focus() - - console.log("Item search focused ✅") return } + // Focus Customer Search if (e.ctrlKey && key === 'u') { e.preventDefault() const input = document.querySelector('input[placeholder*="customer"]') input?.focus() - - console.log("Customer search focused ✅") return } + // Promotion Popup if (e.ctrlKey && e.shiftKey && key === 'x') { e.preventDefault() @@ -295,24 +298,7 @@ window.addEventListener('keydown', function (e) { b.innerText?.toLowerCase().includes("coupon") ) - if (btn) { - btn.click() - console.log("Promotion popup opened ✅") - } else { - console.log("Promotion button not found ❌") - } - - return - } - - if (e.ctrlKey && e.shiftKey && key === 'm') { - e.preventDefault() - - const btn = Array.from(document.querySelectorAll("button")) - .find(b => b.innerText?.includes("Return")) - - btn?.click() - console.log("Return mode activated ✅") + if (btn) btn.click() return } From 3e30775ee157b0a8f02f3a7fcf915203dca2888a Mon Sep 17 00:00:00 2001 From: Ahmed Osama Date: Tue, 3 Mar 2026 20:42:37 +0000 Subject: [PATCH 3/7] feat: update Payments and Cash Control Report to use opening amounts and adjust chart data --- .../payments_and_cash_control_report.py | 55 ++++++++++--------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/pos_next/pos_next/report/payments_and_cash_control_report/payments_and_cash_control_report.py b/pos_next/pos_next/report/payments_and_cash_control_report/payments_and_cash_control_report.py index 24549cb0..a21aefe0 100644 --- a/pos_next/pos_next/report/payments_and_cash_control_report/payments_and_cash_control_report.py +++ b/pos_next/pos_next/report/payments_and_cash_control_report/payments_and_cash_control_report.py @@ -77,8 +77,8 @@ def get_columns(payment_methods): safe = method.lower().replace(" ", "_") columns.extend([ { - "fieldname": f"{safe}_expected", - "label": _(f"{method} Expected"), + "fieldname": f"{safe}_opening", + "label": _(f"{method} Opening"), "fieldtype": "Currency", "width": 130 }, @@ -98,8 +98,8 @@ def get_columns(payment_methods): columns.extend([ { - "fieldname": "total_expected", - "label": _("Total Expected"), + "fieldname": "total_opening", + "label": _("Total Opening"), "fieldtype": "Currency", "width": 130 }, @@ -192,26 +192,29 @@ def get_data(filters): "shift_end": r.shift_end, "shift_hours": shift_hours, "total_transactions": transaction_map.get(r.shift, 0), - "total_expected": 0, + "total_opening": 0, "total_closing": 0, "total_difference": 0, } row = shifts[r.shift] safe = r.payment_method.lower().replace(" ", "_") - row[f"{safe}_expected"] = flt(r.expected_amount, 2) - row[f"{safe}_closing"] = flt(r.closing_amount, 2) - row[f"{safe}_diff"] = flt(r.difference, 2) + opening = flt(r.opening_amount, 2) + closing = flt(r.closing_amount, 2) + diff = flt(closing - opening, 2) + row[f"{safe}_opening"] = opening + row[f"{safe}_closing"] = closing + row[f"{safe}_diff"] = diff - row["total_expected"] += flt(r.expected_amount, 2) - row["total_closing"] += flt(r.closing_amount, 2) - row["total_difference"] += flt(r.difference, 2) + row["total_opening"] += opening + row["total_closing"] += closing + row["total_difference"] += diff # Build final data list and determine status data = [] for shift_name in shift_order: row = shifts[shift_name] - row["total_expected"] = flt(row["total_expected"], 2) + row["total_opening"] = flt(row["total_opening"], 2) row["total_closing"] = flt(row["total_closing"], 2) row["total_difference"] = flt(row["total_difference"], 2) @@ -278,25 +281,25 @@ def get_conditions(filters): def get_chart_data(data, payment_methods): - """Generate chart showing payment method breakdown""" - if not data or not payment_methods: + """Generate chart showing opening, closing, and difference per shift.""" + if not data: return None - # Aggregate expected amounts by payment method across all shifts - datasets = [] - for method in payment_methods: - safe = method.lower().replace(" ", "_") - values = [flt(row.get(f"{safe}_expected", 0)) for row in data] - datasets.append({ - "name": method, - "values": values - }) + labels = [row.get("shift") for row in data] + opening_values = [flt(row.get("total_opening", 0), 2) for row in data] + closing_values = [flt(row.get("total_closing", 0), 2) for row in data] + diff_values = [flt(row.get("total_difference", 0), 2) for row in data] return { "data": { - "labels": [row.get("shift") for row in data], - "datasets": datasets + "labels": labels, + "datasets": [ + {"name": _("Opening"), "values": opening_values}, + {"name": _("Closing"), "values": closing_values}, + {"name": _("Difference"), "values": diff_values}, + ] }, "type": "bar", - "barOptions": {"stacked": True} + "fieldtype": "Currency", + "colors": ["#318AD8", "#48BB74", "#F56B6B"], } From 8cbe53da063bb49e0b9938d50bd291cd83438ed6 Mon Sep 17 00:00:00 2001 From: Ahmed Osama Date: Tue, 3 Mar 2026 21:29:52 +0000 Subject: [PATCH 4/7] feat: add expected amounts to Payments and Cash Control Report and update chart data --- .../payments_and_cash_control_report.py | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/pos_next/pos_next/report/payments_and_cash_control_report/payments_and_cash_control_report.py b/pos_next/pos_next/report/payments_and_cash_control_report/payments_and_cash_control_report.py index a21aefe0..2deb9c03 100644 --- a/pos_next/pos_next/report/payments_and_cash_control_report/payments_and_cash_control_report.py +++ b/pos_next/pos_next/report/payments_and_cash_control_report/payments_and_cash_control_report.py @@ -82,6 +82,12 @@ def get_columns(payment_methods): "fieldtype": "Currency", "width": 130 }, + { + "fieldname": f"{safe}_expected", + "label": _(f"{method} Expected"), + "fieldtype": "Currency", + "width": 130 + }, { "fieldname": f"{safe}_closing", "label": _(f"{method} Closing"), @@ -103,6 +109,12 @@ def get_columns(payment_methods): "fieldtype": "Currency", "width": 130 }, + { + "fieldname": "total_expected", + "label": _("Total Expected"), + "fieldtype": "Currency", + "width": 130 + }, { "fieldname": "total_closing", "label": _("Total Closing"), @@ -193,6 +205,7 @@ def get_data(filters): "shift_hours": shift_hours, "total_transactions": transaction_map.get(r.shift, 0), "total_opening": 0, + "total_expected": 0, "total_closing": 0, "total_difference": 0, } @@ -200,13 +213,16 @@ def get_data(filters): row = shifts[r.shift] safe = r.payment_method.lower().replace(" ", "_") opening = flt(r.opening_amount, 2) + expected = flt(r.expected_amount, 2) closing = flt(r.closing_amount, 2) - diff = flt(closing - opening, 2) + diff = flt(closing - expected, 2) row[f"{safe}_opening"] = opening + row[f"{safe}_expected"] = expected row[f"{safe}_closing"] = closing row[f"{safe}_diff"] = diff row["total_opening"] += opening + row["total_expected"] += expected row["total_closing"] += closing row["total_difference"] += diff @@ -215,6 +231,7 @@ def get_data(filters): for shift_name in shift_order: row = shifts[shift_name] row["total_opening"] = flt(row["total_opening"], 2) + row["total_expected"] = flt(row["total_expected"], 2) row["total_closing"] = flt(row["total_closing"], 2) row["total_difference"] = flt(row["total_difference"], 2) @@ -287,6 +304,7 @@ def get_chart_data(data, payment_methods): labels = [row.get("shift") for row in data] opening_values = [flt(row.get("total_opening", 0), 2) for row in data] + expected_values = [flt(row.get("total_expected", 0), 2) for row in data] closing_values = [flt(row.get("total_closing", 0), 2) for row in data] diff_values = [flt(row.get("total_difference", 0), 2) for row in data] @@ -295,11 +313,12 @@ def get_chart_data(data, payment_methods): "labels": labels, "datasets": [ {"name": _("Opening"), "values": opening_values}, + {"name": _("Expected"), "values": expected_values}, {"name": _("Closing"), "values": closing_values}, {"name": _("Difference"), "values": diff_values}, ] }, "type": "bar", "fieldtype": "Currency", - "colors": ["#318AD8", "#48BB74", "#F56B6B"], + "colors": ["#318AD8", "#F5A623", "#48BB74", "#F56B6B"], } From ae7fb9510256c4df74d5c08a6465cc443c5af310 Mon Sep 17 00:00:00 2001 From: Ahmed Osama Date: Tue, 3 Mar 2026 21:44:14 +0000 Subject: [PATCH 5/7] feat: add mode of payment filter to Payments and Cash Control Report --- .../payments_and_cash_control_report.js | 6 ++++++ .../payments_and_cash_control_report.py | 3 +++ 2 files changed, 9 insertions(+) diff --git a/pos_next/pos_next/report/payments_and_cash_control_report/payments_and_cash_control_report.js b/pos_next/pos_next/report/payments_and_cash_control_report/payments_and_cash_control_report.js index 3d5723fa..69e3c9a2 100644 --- a/pos_next/pos_next/report/payments_and_cash_control_report/payments_and_cash_control_report.js +++ b/pos_next/pos_next/report/payments_and_cash_control_report/payments_and_cash_control_report.js @@ -34,6 +34,12 @@ frappe.query_reports["Payments and Cash Control Report"] = { "label": __("Cashier"), "fieldtype": "Link", "options": "User" + }, + { + "fieldname": "mode_of_payment", + "label": __("Mode of Payment"), + "fieldtype": "Link", + "options": "Mode of Payment" } ] }; diff --git a/pos_next/pos_next/report/payments_and_cash_control_report/payments_and_cash_control_report.py b/pos_next/pos_next/report/payments_and_cash_control_report/payments_and_cash_control_report.py index 2deb9c03..3dae6e4c 100644 --- a/pos_next/pos_next/report/payments_and_cash_control_report/payments_and_cash_control_report.py +++ b/pos_next/pos_next/report/payments_and_cash_control_report/payments_and_cash_control_report.py @@ -294,6 +294,9 @@ def get_conditions(filters): if filters.get("shift"): conditions.append("pcs.name = %(shift)s") + if filters.get("mode_of_payment"): + conditions.append("pr.mode_of_payment = %(mode_of_payment)s") + return " AND " + " AND ".join(conditions) if conditions else "" From b4e68507858d08ccfc101f14d9cc89e9e8255215 Mon Sep 17 00:00:00 2001 From: abinandabinand21-sudo Date: Wed, 4 Mar 2026 16:50:33 +0530 Subject: [PATCH 6/7] Address review comments for POS keyboard shortcuts --- POS/src/composables/useKeyboardShortcuts.js | 105 ++++++++++++++++++++ POS/src/pages/POSSale.vue | 32 ++++-- 2 files changed, 131 insertions(+), 6 deletions(-) create mode 100644 POS/src/composables/useKeyboardShortcuts.js diff --git a/POS/src/composables/useKeyboardShortcuts.js b/POS/src/composables/useKeyboardShortcuts.js new file mode 100644 index 00000000..4cb3d2b9 --- /dev/null +++ b/POS/src/composables/useKeyboardShortcuts.js @@ -0,0 +1,105 @@ +import { onMounted, onUnmounted } from "vue"; + +/** + * useKeyboardShortcuts + * + * Registers POS-scoped keyboard shortcuts with strict guards: + * Ctrl+Shift+O → View Shift (only when shift is open) + * Ctrl+Shift+D → Draft Invoices + * Ctrl+Shift+H → Invoice History + * Ctrl+Shift+C → Close Shift (only when shift is open) + * Ctrl+Shift+N → Create Customer + * Ctrl+Alt+R → Return Invoice + * + * @param {Object} options + * @param {import('@/stores/posUI').POSUIStore} options.uiStore + * @param {import('@/stores/posShift').POSShiftStore} options.shiftStore + * @param {import('vue').Ref} options.editCustomer – ref + * @param {() => boolean} [options.isLocalOverlayOpen] – optional callback + * returning true when a non-uiStore overlay (Promotion, Settings, + * StockLookup, InvoiceManagement, InvoiceDetail) is open + */ +export function useKeyboardShortcuts({ + uiStore, + shiftStore, + editCustomer, + isLocalOverlayOpen = () => false, +}) { + function isEditableTarget(el) { + if (!el) return false; + const tag = el.tagName; + if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true; + if (el.isContentEditable) return true; + return false; + } + + function isOnPOSPage() { + // Match /pos or /pos/ but NOT /reports/sales-pos etc. + return /\/pos(\/|$)/.test(window.location.pathname); + } + + function handler(e) { + // ── Route guard ──────────────────────────────────────────────────────── + if (!isOnPOSPage()) return; + + // ── Input-field guard ────────────────────────────────────────────────── + if (isEditableTarget(e.target)) return; + + // ── Dialog / overlay guard ───────────────────────────────────────────── + if (uiStore.isAnyDialogOpen) return; + if (isLocalOverlayOpen()) return; + + const key = e.key.toLowerCase(); + + // ── Ctrl + Shift shortcuts (Alt must NOT be pressed) ─────────────────── + if (e.ctrlKey && e.shiftKey && !e.altKey && !e.metaKey) { + switch (key) { + case "o": // View Shift + if (shiftStore.hasOpenShift) { + e.preventDefault(); + uiStore.showOpenShiftDialog = true; + } + break; + + case "d": // Draft Invoices + e.preventDefault(); + uiStore.showDraftDialog = true; + break; + + case "h": // Invoice History + e.preventDefault(); + uiStore.showHistoryDialog = true; + break; + + case "c": // Close Shift + if (shiftStore.hasOpenShift) { + e.preventDefault(); + uiStore.showCloseShiftDialog = true; + } + break; + + case "n": // Create Customer + e.preventDefault(); + editCustomer.value = null; + uiStore.setInitialCustomerName(""); + uiStore.showCreateCustomerDialog = true; + break; + } + } + + // ── Ctrl + Alt + R → Return Invoice ───────────────────────────────── + // (Alt must be pressed, Shift must NOT be pressed to avoid Ctrl+Shift+Alt+R) + if (e.ctrlKey && e.altKey && !e.shiftKey && !e.metaKey && key === "r") { + e.preventDefault(); + uiStore.showReturnDialog = true; + } + } + + onMounted(() => { + document.addEventListener("keydown", handler); + }); + + onUnmounted(() => { + document.removeEventListener("keydown", handler); + }); +} diff --git a/POS/src/pages/POSSale.vue b/POS/src/pages/POSSale.vue index 3dca4077..18dfdc2b 100644 --- a/POS/src/pages/POSSale.vue +++ b/POS/src/pages/POSSale.vue @@ -971,6 +971,8 @@ import InvoiceDetailDialog from "@/components/invoices/InvoiceDetailDialog.vue"; import { useRealtimeStock } from "@/composables/useRealtimeStock"; import { usePOSEvents } from "@/composables/usePOSEvents"; import { useLocale } from "@/composables/useLocale"; +import { useKeyboardShortcuts } from "@/composables/useKeyboardShortcuts"; +import { registerDialog } from "@/composables/useDialogState"; import { session } from "@/data/session"; import { useUserData } from "@/data/user"; import { parseError } from "@/utils/errorHandler"; @@ -1062,22 +1064,40 @@ function computeCartHash() { .join("|"); } -// Promotion dialog -const showPromotionManagement = ref(false); +// Promotion dialog — registered so isAnyDialogOpen sees it +const showPromotionManagement = registerDialog(ref(false), "promotionManagement"); // Settings dialog -const showPOSSettings = ref(false); +const showPOSSettings = registerDialog(ref(false), "posSettings"); // Stock Lookup dialog (Products menu) -const showStockLookup = ref(false); +const showStockLookup = registerDialog(ref(false), "stockLookup"); // Invoice Management dialog -const showInvoiceManagement = ref(false); +const showInvoiceManagement = registerDialog(ref(false), "invoiceManagement"); // Invoice Detail dialog -const showInvoiceDetail = ref(false); +const showInvoiceDetail = registerDialog(ref(false), "invoiceDetail"); const selectedInvoiceForView = ref(null); +// ================================ +// Keyboard Shortcuts Registration +// ================================ +// Must be called after all dialog refs are declared so isLocalOverlayOpen +// can safely reference them inside the keydown event handler. +useKeyboardShortcuts({ + uiStore, + shiftStore, + editCustomer, + // Block shortcuts when local overlays (not tracked by uiStore) are open + isLocalOverlayOpen: () => + showPromotionManagement.value || + showPOSSettings.value || + showStockLookup.value || + showInvoiceManagement.value || + showInvoiceDetail.value, +}); + // Invoice history data (used by InvoiceManagement component) const invoiceHistoryData = ref([]); From 94c1fc614da58889f7d3d4f0e6422a74855fbf46 Mon Sep 17 00:00:00 2001 From: Abinand Date: Wed, 4 Mar 2026 17:01:43 +0530 Subject: [PATCH 7/7] Update main.js --- POS/src/main.js | 74 ------------------------------------------------- 1 file changed, 74 deletions(-) diff --git a/POS/src/main.js b/POS/src/main.js index 24468576..4e19c274 100644 --- a/POS/src/main.js +++ b/POS/src/main.js @@ -229,77 +229,3 @@ async function initializeApp() { } initializeApp() - -window.addEventListener('keydown', function (e) { - - const key = e.key.toLowerCase() - const active = document.activeElement - - // Prevent shortcuts while typing - if (active && ( - active.tagName === "INPUT" || - active.tagName === "TEXTAREA" || - active.isContentEditable - )) { - return - } - - const boxShortcuts = { - 'o': "View Shift", - 'd': "Draft Invoices", - 'i': "Invoice History", - 'k': "Return Invoice", - 'p': "Close Shift", - 'f': "Create Customer" - } - - // Box Actions - if (e.ctrlKey && boxShortcuts[key]) { - e.preventDefault() - e.stopPropagation() - - const targetText = boxShortcuts[key] - - setTimeout(() => { - const btn = Array.from(document.querySelectorAll("button")) - .find(b => b.innerText?.trim().toLowerCase() === targetText.toLowerCase()) - - if (btn) btn.click() - }, 300) - - return - } - - // Focus Item Search - if (e.ctrlKey && key === 'b') { - e.preventDefault() - - const input = document.querySelector('input[placeholder*="Search"]') - input?.focus() - return - } - - // Focus Customer Search - if (e.ctrlKey && key === 'u') { - e.preventDefault() - - const input = document.querySelector('input[placeholder*="customer"]') - input?.focus() - return - } - - // Promotion Popup - if (e.ctrlKey && e.shiftKey && key === 'x') { - e.preventDefault() - - const btn = Array.from(document.querySelectorAll("button")) - .find(b => - b.innerText?.toLowerCase().includes("promotion") || - b.innerText?.toLowerCase().includes("coupon") - ) - - if (btn) btn.click() - return - } - -}, true) \ No newline at end of file