From b42342acc3a2ad65893ff35bf75d2ff8d39c07be Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 16:41:22 +0000 Subject: [PATCH 01/25] feat: add restaurant module table management and kds - Added Restaurant Area and Restaurant Table doctypes - Added fields in pos_settings and Sales Invoice to support tracking table and kds status - Added restaurant store in frontend to handle state and offline caching of tables and areas - Added TableSelector.vue component in POSSale page to allow selection of empty/occupied tables - Updated InvoiceCart to show current active table and support sending orders to Kitchen Display System (KDS) - Added new KDS page with KDSOrderCard components to view orders and update status - Updated posSync offline logic to sync restaurant tables and areas when enabled Co-authored-by: hemidirasim <25175153+hemidirasim@users.noreply.github.com> --- POS/components.d.ts | 2 + POS/src/components/invoices/KDSOrderCard.vue | 159 ++++++++++++++++++ POS/src/components/pos/TableSelector.vue | 115 +++++++++++++ POS/src/components/sale/InvoiceCart.vue | 28 +++ POS/src/composables/useInvoice.js | 4 + POS/src/pages/KDS.vue | 114 +++++++++++++ POS/src/pages/POSSale.vue | 22 ++- POS/src/router.js | 5 + POS/src/socket.js | 12 +- POS/src/stores/posCart.js | 14 ++ POS/src/stores/posSync.js | 8 + POS/src/stores/restaurant.js | 90 ++++++++++ POS/src/utils/offline/db.js | 6 + pos_next/api/restaurant.py | 61 +++++++ pos_next/fixtures/custom_field.json | 114 +++++++++++++ .../doctype/pos_settings/pos_settings.json | 25 ++- .../doctype/restaurant_area/__init__.py | 0 .../restaurant_area/restaurant_area.json | 69 ++++++++ .../restaurant_area/restaurant_area.py | 8 + .../doctype/restaurant_table/__init__.py | 0 .../restaurant_table/restaurant_table.json | 89 ++++++++++ .../restaurant_table/restaurant_table.py | 8 + 22 files changed, 944 insertions(+), 9 deletions(-) create mode 100644 POS/src/components/invoices/KDSOrderCard.vue create mode 100644 POS/src/components/pos/TableSelector.vue create mode 100644 POS/src/pages/KDS.vue create mode 100644 POS/src/stores/restaurant.js create mode 100644 pos_next/api/restaurant.py create mode 100644 pos_next/pos_next/doctype/restaurant_area/__init__.py create mode 100644 pos_next/pos_next/doctype/restaurant_area/restaurant_area.json create mode 100644 pos_next/pos_next/doctype/restaurant_area/restaurant_area.py create mode 100644 pos_next/pos_next/doctype/restaurant_table/__init__.py create mode 100644 pos_next/pos_next/doctype/restaurant_table/restaurant_table.json create mode 100644 pos_next/pos_next/doctype/restaurant_table/restaurant_table.py diff --git a/POS/components.d.ts b/POS/components.d.ts index 1f2e8b35..5d12549b 100644 --- a/POS/components.d.ts +++ b/POS/components.d.ts @@ -28,6 +28,7 @@ declare module 'vue' { InvoiceManagement: typeof import('./src/components/invoices/InvoiceManagement.vue')['default'] ItemSelectionDialog: typeof import('./src/components/sale/ItemSelectionDialog.vue')['default'] ItemsSelector: typeof import('./src/components/sale/ItemsSelector.vue')['default'] + KDSOrderCard: typeof import('./src/components/invoices/KDSOrderCard.vue')['default'] LanguageSwitcher: typeof import('./src/components/common/LanguageSwitcher.vue')['default'] LazyImage: typeof import('./src/components/common/LazyImage.vue')['default'] LoadingSpinner: typeof import('./src/components/common/LoadingSpinner.vue')['default'] @@ -52,6 +53,7 @@ declare module 'vue' { ShiftClosingDialog: typeof import('./src/components/ShiftClosingDialog.vue')['default'] ShiftOpeningDialog: typeof import('./src/components/ShiftOpeningDialog.vue')['default'] StatusBadge: typeof import('./src/components/common/StatusBadge.vue')['default'] + TableSelector: typeof import('./src/components/pos/TableSelector.vue')['default'] Toast: typeof import('./src/components/common/Toast.vue')['default'] TranslatedHTML: typeof import('./src/components/common/TranslatedHTML.vue')['default'] UserMenu: typeof import('./src/components/common/UserMenu.vue')['default'] diff --git a/POS/src/components/invoices/KDSOrderCard.vue b/POS/src/components/invoices/KDSOrderCard.vue new file mode 100644 index 00000000..0449fcf8 --- /dev/null +++ b/POS/src/components/invoices/KDSOrderCard.vue @@ -0,0 +1,159 @@ + + + diff --git a/POS/src/components/pos/TableSelector.vue b/POS/src/components/pos/TableSelector.vue new file mode 100644 index 00000000..d51b4e90 --- /dev/null +++ b/POS/src/components/pos/TableSelector.vue @@ -0,0 +1,115 @@ + + + diff --git a/POS/src/components/sale/InvoiceCart.vue b/POS/src/components/sale/InvoiceCart.vue index d9183515..b790de96 100644 --- a/POS/src/components/sale/InvoiceCart.vue +++ b/POS/src/components/sale/InvoiceCart.vue @@ -65,6 +65,17 @@
+ +
+
+ + Table: {{ cartStore.restaurantTable.table_name }} +
+ +
+
@@ -158,6 +169,18 @@ {{ __("Order") }} + + +
@@ -1285,6 +1308,11 @@ function handleProceedToPayment() { emit("proceed-to-payment"); } +function sendToKitchen() { + cartStore.setKdsStatus("Pending"); + emit("save-draft"); +} + /** * ============================================================================ * PROPS diff --git a/POS/src/composables/useInvoice.js b/POS/src/composables/useInvoice.js index 66c7c58f..5fbcd4a0 100644 --- a/POS/src/composables/useInvoice.js +++ b/POS/src/composables/useInvoice.js @@ -892,12 +892,16 @@ export function useInvoice() { const rawItems = toRaw(invoiceItems.value) const rawPayments = toRaw(payments.value) const rawSalesTeam = toRaw(salesTeam.value) + const { usePOSCartStore } = await import("@/stores/posCart") + const cartStore = usePOSCartStore() const invoiceData = { doctype: targetDoctype, pos_profile: posProfile.value, posa_pos_opening_shift: posOpeningShift.value, customer: customer.value?.name || customer.value, + restaurant_table: cartStore.restaurantTable?.name, + kds_status: cartStore.kdsStatus, items: formatItemsForSubmission(rawItems), payments: rawPayments.map((p) => ({ mode_of_payment: p.mode_of_payment, diff --git a/POS/src/pages/KDS.vue b/POS/src/pages/KDS.vue new file mode 100644 index 00000000..ca597d9f --- /dev/null +++ b/POS/src/pages/KDS.vue @@ -0,0 +1,114 @@ + + + + + diff --git a/POS/src/pages/POSSale.vue b/POS/src/pages/POSSale.vue index 20a7664b..0d14f962 100644 --- a/POS/src/pages/POSSale.vue +++ b/POS/src/pages/POSSale.vue @@ -285,13 +285,18 @@ ]" style="contain: layout style paint" > - + +
@@ -983,6 +988,7 @@ import InvoiceCart from "@/components/sale/InvoiceCart.vue"; import InvoiceHistoryDialog from "@/components/sale/InvoiceHistoryDialog.vue"; import ItemSelectionDialog from "@/components/sale/ItemSelectionDialog.vue"; import ItemsSelector from "@/components/sale/ItemsSelector.vue"; +import TableSelector from "@/components/pos/TableSelector.vue"; import OffersDialog from "@/components/sale/OffersDialog.vue"; import OfflineInvoicesDialog from "@/components/sale/OfflineInvoicesDialog.vue"; import PaymentDialog from "@/components/sale/PaymentDialog.vue"; @@ -1018,6 +1024,7 @@ import { usePOSCartStore } from "@/stores/posCart"; import { usePOSDraftsStore } from "@/stores/posDrafts"; import { usePOSSettingsStore } from "@/stores/posSettings"; import { usePOSShiftStore } from "@/stores/posShift"; +import { useRestaurantStore } from "@/stores/restaurant"; import { usePOSSyncStore } from "@/stores/posSync"; import { usePOSUIStore } from "@/stores/posUI"; import { logger } from "@/utils/logger"; @@ -1026,6 +1033,7 @@ import { shouldValidateItemStock } from "@/utils/stockValidator"; // Initialize stores const cartStore = usePOSCartStore(); const shiftStore = usePOSShiftStore(); +const restaurantStore = useRestaurantStore(); const uiStore = usePOSUIStore(); const offlineStore = usePOSSyncStore(); const draftsStore = usePOSDraftsStore(); diff --git a/POS/src/router.js b/POS/src/router.js index 02b029c1..9a9df3f9 100644 --- a/POS/src/router.js +++ b/POS/src/router.js @@ -14,6 +14,11 @@ const routes = [ path: "/account/login", component: () => import("@/pages/Login.vue"), }, + { + name: "KDS", + path: "/kds", + component: () => import("@/pages/KDS.vue"), + }, // Catch-all route { path: "/:pathMatch(.*)*", diff --git a/POS/src/socket.js b/POS/src/socket.js index 27fc1740..c3029d77 100644 --- a/POS/src/socket.js +++ b/POS/src/socket.js @@ -1,7 +1,17 @@ import { io } from "socket.io-client" -import { socketio_port } from "../../../../sites/common_site_config.json" let socket = null +let socketio_port = 9000 + +try { + // Dynamically import to prevent build errors if file is missing + const config = require("../../../../sites/common_site_config.json") + if (config && config.socketio_port) { + socketio_port = config.socketio_port + } +} catch (e) { + // Default to 9000 if not found +} export function initSocket(siteNameOverride) { // Don't reinitialize if socket already exists diff --git a/POS/src/stores/posCart.js b/POS/src/stores/posCart.js index 5a73899d..e32b5f85 100644 --- a/POS/src/stores/posCart.js +++ b/POS/src/stores/posCart.js @@ -228,6 +228,8 @@ export const usePOSCartStore = defineStore("posCart", () => { appliedCoupon.value = null currentDraftId.value = null targetDoctype.value = "Sales Invoice" + restaurantTable.value = null + kdsStatus.value = "Pending" // Reset offer processing state offerProcessingState.value.lastCartHash = '' @@ -244,6 +246,8 @@ export const usePOSCartStore = defineStore("posCart", () => { const deliveryDate = ref("") const writeOffAmount = ref(0) + const restaurantTable = ref(null) + const kdsStatus = ref("Pending") function setDeliveryDate(date) { deliveryDate.value = date @@ -253,6 +257,14 @@ export const usePOSCartStore = defineStore("posCart", () => { writeOffAmount.value = amount || 0 } + function setRestaurantTable(table) { + restaurantTable.value = table + } + + function setKdsStatus(status) { + kdsStatus.value = status + } + async function submitInvoice() { if (invoiceItems.value.length === 0) { showWarning(__("Cart is empty")) @@ -318,6 +330,8 @@ export const usePOSCartStore = defineStore("posCart", () => { customer: customer.value?.name || customer.value || currentProfile?.customer, company: currentProfile?.company, + restaurant_table: restaurantTable.value?.name, + kds_status: kdsStatus.value, selling_price_list: currentProfile?.selling_price_list, currency: currentProfile?.currency, discount_amount: additionalDiscount.value || 0, diff --git a/POS/src/stores/posSync.js b/POS/src/stores/posSync.js index d23a44ec..3f9868cb 100644 --- a/POS/src/stores/posSync.js +++ b/POS/src/stores/posSync.js @@ -15,6 +15,7 @@ */ import { useToast } from "@/composables/useToast" +import { useRestaurantStore } from "@/stores/restaurant" import { cacheCustomersFromServer, cachePaymentMethodsFromServer, @@ -299,6 +300,13 @@ export const usePOSSyncStore = defineStore("posSync", () => { log.error('Failed to load sales persons', error) } + // Load restaurant tables + const restaurantStore = useRestaurantStore() + if (restaurantStore.isEnabled) { + log.info('Loading restaurant tables for offline use') + await restaurantStore.fetchFromNetwork() + } + // Load customers if cache needs refresh if (!cacheReady || needsRefresh) { showSuccess(__("Loading customers for offline use...")) diff --git a/POS/src/stores/restaurant.js b/POS/src/stores/restaurant.js new file mode 100644 index 00000000..32a4b1c7 --- /dev/null +++ b/POS/src/stores/restaurant.js @@ -0,0 +1,90 @@ +import { defineStore } from "pinia" +import { ref, computed } from "vue" +import { usePOSSettingsStore } from "./posSettings" +import { db } from "../utils/offline/db" +import { logger } from "../utils/logger" +import { call } from "../utils/apiWrapper" + +const log = logger.create("RestaurantStore") + +export const useRestaurantStore = defineStore("restaurant", () => { + const posSettingsStore = usePOSSettingsStore() + + // State + const tables = ref([]) + const areas = ref([]) + const isEnabled = computed(() => posSettingsStore.settings.enable_restaurant_mode) + const defaultArea = computed(() => posSettingsStore.settings.default_restaurant_area) + + // Actions + async function loadTablesAndAreas() { + if (!isEnabled.value) return + + try { + log.info("Loading tables and areas from local cache") + areas.value = await db.restaurant_areas.toArray() + tables.value = await db.restaurant_tables.toArray() + } catch (error) { + log.error("Failed to load tables from cache:", error) + } + } + + async function fetchFromNetwork() { + if (!isEnabled.value) return + + try { + log.info("Fetching tables from network") + const res = await call("pos_next.api.restaurant.get_tables") + + if (res) { + const { areas: fetchedAreas, tables: fetchedTables } = res + + // Update state + areas.value = fetchedAreas || [] + tables.value = fetchedTables || [] + + // Update offline cache + await db.transaction("rw", db.restaurant_areas, db.restaurant_tables, async () => { + await db.restaurant_areas.clear() + if (areas.value.length) await db.restaurant_areas.bulkPut(areas.value) + + await db.restaurant_tables.clear() + if (tables.value.length) await db.restaurant_tables.bulkPut(tables.value) + }) + } + } catch (error) { + log.error("Failed to fetch tables from network:", error) + } + } + + async function updateTableStatus(tableName, status) { + try { + // Update local state and cache optimistically + const table = tables.value.find(t => t.name === tableName) + if (table) { + table.status = status + await db.restaurant_tables.put(table) + } + + // Send to network + if (navigator.onLine) { + await call("pos_next.api.restaurant.update_table_status", { + table_name: tableName, + status + }) + } + } catch (error) { + log.error(`Failed to update status for table ${tableName}:`, error) + } + } + + return { + tables, + areas, + isEnabled, + defaultArea, + loadTablesAndAreas, + fetchFromNetwork, + updateTableStatus + } +}) diff --git a/POS/src/utils/offline/db.js b/POS/src/utils/offline/db.js index d71021f6..421d9134 100644 --- a/POS/src/utils/offline/db.js +++ b/POS/src/utils/offline/db.js @@ -81,6 +81,12 @@ const CURRENT_SCHEMA = { // Unpaid invoices cache for offline viewing // Stores invoices with outstanding amounts for partial payment management unpaid_invoices: "&name, pos_profile, outstanding_amount, customer", + + // Restaurant tables cache + restaurant_tables: "&name, area, status", + + // Restaurant areas cache + restaurant_areas: "&name", } /** diff --git a/pos_next/api/restaurant.py b/pos_next/api/restaurant.py new file mode 100644 index 00000000..aa43597b --- /dev/null +++ b/pos_next/api/restaurant.py @@ -0,0 +1,61 @@ +import frappe +from frappe import _ + +@frappe.whitelist() +def get_tables(): + """Fetch all restaurant areas and tables.""" + areas = frappe.get_all("Restaurant Area", fields=["name", "area_name", "description"]) + tables = frappe.get_all("Restaurant Table", fields=["name", "table_name", "area", "capacity", "status"]) + return { + "areas": areas, + "tables": tables + } + +@frappe.whitelist() +def update_table_status(table_name, status): + """Update the status of a specific table.""" + if not frappe.has_permission("Restaurant Table", "write"): + frappe.throw(_("Not permitted"), frappe.PermissionError) + + if not frappe.db.exists("Restaurant Table", table_name): + frappe.throw(_("Table {0} not found").format(table_name)) + + frappe.db.set_value("Restaurant Table", table_name, "status", status) + return {"status": "success"} + +@frappe.whitelist() +def update_kds_status(invoice_name, status): + """Update the KDS status of a sales invoice.""" + if not frappe.has_permission("Sales Invoice", "write"): + frappe.throw(_("Not permitted"), frappe.PermissionError) + + if not frappe.db.exists("Sales Invoice", invoice_name): + frappe.throw(_("Invoice {0} not found").format(invoice_name)) + + frappe.db.set_value("Sales Invoice", invoice_name, "kds_status", status) + return {"status": "success"} + +@frappe.whitelist() +def get_kds_orders(): + """Fetch all pending and preparing orders for the KDS.""" + # Only fetch submitted invoices or drafts depending on how POS Next saves KDS orders. + # Assuming here we fetch draft invoices that have a table and are not delivered. + orders = frappe.get_all( + "Sales Invoice", + filters={ + "docstatus": 0, # Drafts + "is_pos": 1, + "restaurant_table": ["is", "set"], + "kds_status": ["in", ["Pending", "Preparing", "Ready"]] + }, + fields=["name", "customer", "restaurant_table", "kds_status", "creation", "modified"] + ) + + for order in orders: + order["items"] = frappe.get_all( + "Sales Invoice Item", + filters={"parent": order.name}, + fields=["item_code", "item_name", "qty", "description"] + ) + + return orders diff --git a/pos_next/fixtures/custom_field.json b/pos_next/fixtures/custom_field.json index 7cfb2bac..93caac62 100644 --- a/pos_next/fixtures/custom_field.json +++ b/pos_next/fixtures/custom_field.json @@ -11,6 +11,120 @@ "description": "Leave empty for global items available to all companies", "docstatus": 0, "doctype": "Custom Field", + "dt": "Sales Invoice", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "restaurant_table", + "fieldtype": "Link", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "posa_is_printed", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Restaurant Table", + "length": 0, + "mandatory_depends_on": null, + "modified": "2024-05-20 10:00:00.000000", + "modified_by": "Administrator", + "module": "POS Next", + "name": "Sales Invoice-restaurant_table", + "no_copy": 0, + "non_negative": 0, + "options": "Restaurant Table", + "owner": "Administrator", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "allow_in_quick_entry": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "default": "Pending", + "depends_on": null, + "description": null, + "docstatus": 0, + "doctype": "Custom Field", + "dt": "Sales Invoice", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "kds_status", + "fieldtype": "Select", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "restaurant_table", + "is_system_generated": 0, + "is_virtual": 0, + "label": "KDS Status", + "length": 0, + "mandatory_depends_on": null, + "modified": "2024-05-20 10:00:00.000000", + "modified_by": "Administrator", + "module": "POS Next", + "name": "Sales Invoice-kds_status", + "no_copy": 0, + "non_negative": 0, + "options": "Pending\nPreparing\nReady\nDelivered", + "owner": "Administrator", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "doctype": "Custom Field", "dt": "Item", "fetch_from": null, "fetch_if_empty": 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 c5711a43..871b5d78 100644 --- a/pos_next/pos_next/doctype/pos_settings/pos_settings.json +++ b/pos_next/pos_next/doctype/pos_settings/pos_settings.json @@ -73,7 +73,10 @@ "enable_session_lock", "session_lock_timeout", "barcode_tab", - "barcode_rules" + "barcode_rules", + "restaurant_tab", + "enable_restaurant_mode", + "default_restaurant_area" ], "fields": [ { @@ -546,6 +549,26 @@ "fieldtype": "Table", "label": "Barcode Rules", "options": "POS Barcode Rules" + }, + { + "fieldname": "restaurant_tab", + "fieldtype": "Tab Break", + "label": "Restaurant" + }, + { + "default": "0", + "description": "Enable restaurant features like Table Management and KDS.", + "fieldname": "enable_restaurant_mode", + "fieldtype": "Check", + "label": "Enable Restaurant Mode" + }, + { + "description": "Default area to display on POS Screen", + "fieldname": "default_restaurant_area", + "fieldtype": "Link", + "label": "Default Restaurant Area", + "options": "Restaurant Area", + "depends_on": "enable_restaurant_mode" } ], "index_web_pages_for_search": 1, diff --git a/pos_next/pos_next/doctype/restaurant_area/__init__.py b/pos_next/pos_next/doctype/restaurant_area/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pos_next/pos_next/doctype/restaurant_area/restaurant_area.json b/pos_next/pos_next/doctype/restaurant_area/restaurant_area.json new file mode 100644 index 00000000..113f1c1c --- /dev/null +++ b/pos_next/pos_next/doctype/restaurant_area/restaurant_area.json @@ -0,0 +1,69 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2024-05-20 10:00:00.000000", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "area_name", + "description" + ], + "fields": [ + { + "fieldname": "area_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Area Name", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2024-05-20 10:00:00.000000", + "modified_by": "Administrator", + "module": "POS Next", + "name": "Restaurant Area", + "naming_rule": "By fieldname", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales Manager", + "share": 1, + "write": 1 + }, + { + "read": 1, + "role": "POSNext Cashier" + } + ], + "autoname": "field:area_name", + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} diff --git a/pos_next/pos_next/doctype/restaurant_area/restaurant_area.py b/pos_next/pos_next/doctype/restaurant_area/restaurant_area.py new file mode 100644 index 00000000..ac8f8f00 --- /dev/null +++ b/pos_next/pos_next/doctype/restaurant_area/restaurant_area.py @@ -0,0 +1,8 @@ +# Copyright (c) 2024, BrainWise and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class RestaurantArea(Document): + pass diff --git a/pos_next/pos_next/doctype/restaurant_table/__init__.py b/pos_next/pos_next/doctype/restaurant_table/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pos_next/pos_next/doctype/restaurant_table/restaurant_table.json b/pos_next/pos_next/doctype/restaurant_table/restaurant_table.json new file mode 100644 index 00000000..af7a2c3a --- /dev/null +++ b/pos_next/pos_next/doctype/restaurant_table/restaurant_table.json @@ -0,0 +1,89 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2024-05-20 10:00:00.000000", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "table_name", + "area", + "capacity", + "status" + ], + "fields": [ + { + "fieldname": "table_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Table Name", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "area", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Area", + "options": "Restaurant Area", + "reqd": 1 + }, + { + "default": "4", + "fieldname": "capacity", + "fieldtype": "Int", + "label": "Capacity" + }, + { + "default": "Empty", + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Status", + "options": "Empty\nOccupied\nReserved\nCleaning" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2024-05-20 10:00:00.000000", + "modified_by": "Administrator", + "module": "POS Next", + "name": "Restaurant Table", + "naming_rule": "By fieldname", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales Manager", + "share": 1, + "write": 1 + }, + { + "read": 1, + "role": "POSNext Cashier", + "write": 1 + } + ], + "autoname": "field:table_name", + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} diff --git a/pos_next/pos_next/doctype/restaurant_table/restaurant_table.py b/pos_next/pos_next/doctype/restaurant_table/restaurant_table.py new file mode 100644 index 00000000..e83ef55c --- /dev/null +++ b/pos_next/pos_next/doctype/restaurant_table/restaurant_table.py @@ -0,0 +1,8 @@ +# Copyright (c) 2024, BrainWise and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class RestaurantTable(Document): + pass From 7c08dbc34ff86a13b74a58b5ed90622d00605874 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 17:24:43 +0000 Subject: [PATCH 02/25] feat: add item modifiers and special instructions for restaurant - Added `posa_special_instructions` custom field to Sales Invoice Item doctype to store item modifiers. - Updated `posCart` store to capture and send `posa_special_instructions` to backend during invoice submission. - Created `ItemModifiersDialog.vue` to allow cashiers to quickly add pre-defined or custom notes to cart items. - Added special instructions badge and edit button to items in `InvoiceCart.vue`. - Updated `KDSOrderCard.vue` to prominently display special instructions on the Kitchen Display System. Co-authored-by: hemidirasim <25175153+hemidirasim@users.noreply.github.com> --- POS/components.d.ts | 1 + POS/src/components/invoices/KDSOrderCard.vue | 3 + POS/src/components/sale/InvoiceCart.vue | 21 ++++ .../components/sale/ItemModifiersDialog.vue | 101 ++++++++++++++++++ POS/src/composables/useInvoice.js | 1 + POS/src/pages/POSSale.vue | 8 ++ POS/src/stores/posCart.js | 12 +++ pos_next/api/restaurant.py | 2 +- pos_next/fixtures/custom_field.json | 57 ++++++++++ pos_next/hooks.py | 3 + 10 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 POS/src/components/sale/ItemModifiersDialog.vue diff --git a/POS/components.d.ts b/POS/components.d.ts index 5d12549b..824f6299 100644 --- a/POS/components.d.ts +++ b/POS/components.d.ts @@ -26,6 +26,7 @@ declare module 'vue' { InvoiceFilters: typeof import('./src/components/invoices/InvoiceFilters.vue')['default'] InvoiceHistoryDialog: typeof import('./src/components/sale/InvoiceHistoryDialog.vue')['default'] InvoiceManagement: typeof import('./src/components/invoices/InvoiceManagement.vue')['default'] + ItemModifiersDialog: typeof import('./src/components/sale/ItemModifiersDialog.vue')['default'] ItemSelectionDialog: typeof import('./src/components/sale/ItemSelectionDialog.vue')['default'] ItemsSelector: typeof import('./src/components/sale/ItemsSelector.vue')['default'] KDSOrderCard: typeof import('./src/components/invoices/KDSOrderCard.vue')['default'] diff --git a/POS/src/components/invoices/KDSOrderCard.vue b/POS/src/components/invoices/KDSOrderCard.vue index 0449fcf8..1426f3e6 100644 --- a/POS/src/components/invoices/KDSOrderCard.vue +++ b/POS/src/components/invoices/KDSOrderCard.vue @@ -44,6 +44,9 @@
{{ item.description }}
+
+ {{ item.posa_special_instructions }} +
{{ item.qty }} diff --git a/POS/src/components/sale/InvoiceCart.vue b/POS/src/components/sale/InvoiceCart.vue index b790de96..e732a5cc 100644 --- a/POS/src/components/sale/InvoiceCart.vue +++ b/POS/src/components/sale/InvoiceCart.vue @@ -816,6 +816,15 @@ > {{ item.item_name }} + + + + {{ __("Note") }} +
+ + + +