diff --git a/api/businesses/get.cfm b/api/businesses/get.cfm index 4b3dd97..5831eb5 100644 --- a/api/businesses/get.cfm +++ b/api/businesses/get.cfm @@ -53,7 +53,8 @@ try { TabMaxAuthAmount, TabAutoIncreaseThreshold, TabMaxMembers, - TabApprovalRequired + TabApprovalRequired, + OrderTypes FROM Businesses WHERE ID = :businessID ", { businessID: businessID }, { datasource: "payfrit" }); @@ -160,7 +161,8 @@ try { "TabMaxAuthAmount": isNumeric(q.TabMaxAuthAmount) ? q.TabMaxAuthAmount : 1000.00, "TabAutoIncreaseThreshold": isNumeric(q.TabAutoIncreaseThreshold) ? q.TabAutoIncreaseThreshold : 0.80, "TabMaxMembers": isNumeric(q.TabMaxMembers) ? q.TabMaxMembers : 10, - "TabApprovalRequired": isNumeric(q.TabApprovalRequired) ? q.TabApprovalRequired : 1 + "TabApprovalRequired": isNumeric(q.TabApprovalRequired) ? q.TabApprovalRequired : 1, + "OrderTypes": len(q.OrderTypes) ? q.OrderTypes : "1" }; // Add header image URL if extension exists diff --git a/api/businesses/saveOrderTypes.cfm b/api/businesses/saveOrderTypes.cfm new file mode 100644 index 0000000..c0b759a --- /dev/null +++ b/api/businesses/saveOrderTypes.cfm @@ -0,0 +1,51 @@ + + + + + + +/** + * Save Business Order Types + * POST JSON: { "BusinessID": 37, "OrderTypes": "1,2" } + * OrderTypes is a comma-separated list: 1=Dine-In, 2=Takeaway, 3=Delivery + */ + +response = { "OK": false }; + +try { + requestBody = toString(getHttpRequestData().content); + if (!len(requestBody)) { + throw(message="No request body provided"); + } + + data = deserializeJSON(requestBody); + + businessId = structKeyExists(data, "BusinessID") ? val(data.BusinessID) : 0; + if (businessId == 0) { + throw(message="BusinessID is required"); + } + + orderTypes = structKeyExists(data, "OrderTypes") && isSimpleValue(data.OrderTypes) ? trim(data.OrderTypes) : "1"; + + // Validate: only allow digits 1-3 separated by commas + if (!reFind("^[1-3](,[1-3])*$", orderTypes)) { + throw(message="OrderTypes must be a comma-separated list of 1, 2, or 3"); + } + + queryTimed(" + UPDATE Businesses SET OrderTypes = :orderTypes + WHERE ID = :bizId + ", { + orderTypes: { value: orderTypes, cfsqltype: "cf_sql_varchar" }, + bizId: { value: businessId, cfsqltype: "cf_sql_integer" } + }, { datasource: "payfrit" }); + + response.OK = true; + response.OrderTypes = orderTypes; + +} catch (any e) { + response.ERROR = e.message; +} + +writeOutput(serializeJSON(response)); + diff --git a/api/menu/items.cfm b/api/menu/items.cfm index fdb32b9..4d7e50f 100644 --- a/api/menu/items.cfm +++ b/api/menu/items.cfm @@ -558,7 +558,7 @@ @@ -598,7 +598,8 @@ "PAYFRITFEE": val(businessPayfritFee), "SESSIONENABLED": val(qBrand.SessionEnabled), "Menus": menuList, - "SelectedMenuID": requestedMenuID + "SelectedMenuID": requestedMenuID, + "ORDERTYPES": len(qBrand.OrderTypes) ? qBrand.OrderTypes : "1" })> diff --git a/api/orders/getOrCreateCart.cfm b/api/orders/getOrCreateCart.cfm index 08da5ae..e3080f2 100644 --- a/api/orders/getOrCreateCart.cfm +++ b/api/orders/getOrCreateCart.cfm @@ -175,12 +175,12 @@ })> - - + + diff --git a/kds/index.html b/kds/index.html index a4f2ac9..441fb35 100644 --- a/kds/index.html +++ b/kds/index.html @@ -80,6 +80,10 @@ .station-btn svg { width: 24px; height: 24px; opacity: 0.5; } .station-name-display { font-size: 11px; color: #555; margin-left: 8px; text-transform: uppercase; letter-spacing: 1px; } + /* Order type badges */ + .badge-pickup { display: inline-block; font-size: 10px; font-weight: 700; padding: 2px 6px; border-radius: 4px; background: #1a2a00; color: #a3e635; border: 1px solid #a3e63533; vertical-align: middle; margin-left: 6px; letter-spacing: 1px; } + .badge-delivery { display: inline-block; font-size: 10px; font-weight: 700; padding: 2px 6px; border-radius: 4px; background: #2a1a00; color: #fb923c; border: 1px solid #fb923c33; vertical-align: middle; margin-left: 6px; letter-spacing: 1px; } + /* Expand toggle */ .expand-toggle { background: none; border: 1px solid #333; color: #555; padding: 4px 10px; border-radius: 4px; font-size: 11px; cursor: pointer; margin-bottom: 10px; width: 100%; transition: all 0.2s; } .expand-toggle:hover { border-color: #555; color: #888; } diff --git a/kds/kds.js b/kds/kds.js index f007411..c58beb9 100644 --- a/kds/kds.js +++ b/kds/kds.js @@ -334,7 +334,7 @@ function renderOrder(order) { return `
-
#${order.OrderID}
+
#${order.OrderID}${order.OrderTypeID === 2 ? ' PICKUP' : ''}${order.OrderTypeID === 3 ? ' DELIVERY' : ''}
${formatElapsedTime(elapsedTime)}
${formatSubmitTime(order.SubmittedOn)}
diff --git a/portal/index.html b/portal/index.html index 9a52beb..fa26320 100644 --- a/portal/index.html +++ b/portal/index.html @@ -689,6 +689,31 @@
+
+
+

Order Types

+
+
+

Choose which order types your business supports. Dine-in is always enabled.

+
+
+ + Dine-In +
+
+ + Takeaway / Pickup +
+
+
+
+

Tabs / Running Checks

diff --git a/portal/portal.js b/portal/portal.js index c90220c..945fd7c 100644 --- a/portal/portal.js +++ b/portal/portal.js @@ -864,6 +864,11 @@ const Portal = { if (thresholdInput) thresholdInput.value = biz.TABAUTOINCREASETHRESHOLD || biz.TabAutoIncreaseThreshold || 0.80; const approvalCheckbox = document.getElementById('tabApprovalRequired'); if (approvalCheckbox) approvalCheckbox.checked = (biz.TABAPPROVALREQUIRED || biz.TabApprovalRequired || 1) == 1; + + // Order types + const orderTypes = (biz.ORDERTYPES || biz.OrderTypes || '1').split(','); + const takeawayCheckbox = document.getElementById('orderTypeTakeaway'); + if (takeawayCheckbox) takeawayCheckbox.checked = orderTypes.includes('2'); } } catch (err) { console.error('[Portal] Error loading business info:', err); @@ -901,6 +906,24 @@ const Portal = { } }, + async saveOrderTypes() { + const types = ['1']; // Dine-in always enabled + if (document.getElementById('orderTypeTakeaway').checked) types.push('2'); + try { + const response = await fetch(`${this.config.apiBaseUrl}/businesses/saveOrderTypes.cfm`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ BusinessID: this.config.businessId, OrderTypes: types.join(',') }) + }); + const data = await response.json(); + if (data.OK) { this.showToast('Order types saved!', 'success'); } + else { this.showToast(data.ERROR || 'Failed to save order types', 'error'); } + } catch (err) { + console.error('[Portal] Error saving order types:', err); + this.showToast('Error saving order types', 'error'); + } + }, + // Render hours editor renderHoursEditor(hours) { const container = document.getElementById('hoursEditor');