No Orders Here...
Orders in this stage will automatically show up real-time.
No Menu Items Found
Try searching for something else or selecting a different category.
Cart is Empty
Click on any menu item on the left to add it to the order.
Menu & Food Item Inventory
Billing & Transaction Register
View past receipts, total sales, and reprint customer bills.
Sales Analytics & Revenue Dashboard
Comprehensive date-wise sales analytics, revenue breakdowns, top dishes, and payment stats.
Filtered Transactions
Showing 0 bills for selected period
Thermal Printer Setup
Connect thermal receipt printer via Web Bluetooth or ESC/POS Direct Network
any UPI App on this QR Code
Firebase Realtime Database
Developer Only LiveGoogle Apps Script Backend Code
Deploy to sync Menu Items, DATABASE Users & Activity Logs with your Google Spreadsheet.
// ============================================================================
// ANDHRA RUCHI (AR CLOUD) - GOOGLE APPS SCRIPT SYNC CODE
// Version: v3.9.0 (Release Date: 02-Oct-2026)
// Release Notes: Fixed Category vs Food/Snack column index resolution & prevented column overwrite
// ============================================================================
const SCRIPT_VERSION = "v3.9.0 (02-Oct-2026)";
const SCRIPT_VERSION_CODE = 390;
const FIREBASE_DB_URL = "https://s2ss-testing.firebaseio.com";
function doGet(e) {
try {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = getBestMenuSheet(ss);
const menuData = parseFlexibleSheet(sheet);
const dbCategories = getCategoriesFromDatabaseSheet(ss);
const dbUsers = getUsersFromDatabaseSheet(ss);
const fallbackCategories = Array.from(new Set(menuData.map(function(m) { return m.category; }).filter(Boolean)));
const categoriesList = dbCategories.length > 0 ? dbCategories : fallbackCategories;
const response = {
status: "success",
version: SCRIPT_VERSION,
versionCode: SCRIPT_VERSION_CODE,
updatedAt: "20-Aug-2026",
sheetNameUsed: sheet.getName(),
categories: categoriesList,
users: dbUsers,
storeOnline: true,
menu: menuData,
count: menuData.length,
timestamp: new Date().toISOString()
};
const callback = e && e.parameter && e.parameter.callback;
if (callback) {
return ContentService.createTextOutput(callback + '(' + JSON.stringify(response) + ')')
.setMimeType(ContentService.MimeType.JAVASCRIPT);
}
return ContentService.createTextOutput(JSON.stringify(response))
.setMimeType(ContentService.MimeType.JSON);
} catch (err) {
const errResp = { status: "error", version: SCRIPT_VERSION, error: err.toString() };
return ContentService.createTextOutput(JSON.stringify(errResp))
.setMimeType(ContentService.MimeType.JSON);
}
}
function getCategoriesFromDatabaseSheet(ss) {
const dbSheet = ss.getSheetByName("DATABASE") || ss.getSheetByName("Database") || ss.getSheetByName("database") || ss.getSheetByName("Categories") || ss.getSheetByName("categories");
if (!dbSheet) return [];
const rows = dbSheet.getDataRange().getValues();
if (rows.length <= 1) return [];
const categories = [];
for (let i = 1; i < rows.length; i++) {
const name = String(rows[i][0] || '').trim(); // Column A: Category Name
const image = String(rows[i][1] || '').trim(); // Column B: Category Image URL
if (name && !categories.some(c => (typeof c === 'string' ? c : c.name).toLowerCase() === name.toLowerCase())) {
categories.push({
id: name.toLowerCase().replace(/\s+/g, '_'),
name: name,
image: image || '',
imageUrl: image || ''
});
}
}
return categories;
}
function getUsersFromDatabaseSheet(ss) {
const dbSheet = ss.getSheetByName("DATABASE") || ss.getSheetByName("Database") || ss.getSheetByName("database");
if (!dbSheet) return [];
const rows = dbSheet.getDataRange().getValues();
if (rows.length <= 1) return [];
const users = [];
for (let i = 1; i < rows.length; i++) {
const row = rows[i];
const name = String(row[2] || '').trim(); // Column C: Name
const email = String(row[3] || '').trim().toLowerCase(); // Column D: Email
const access = String(row[4] || 'Admin').trim(); // Column E: Access
if (email || name) {
users.push({
name: name || (email ? email.split('@')[0] : 'User'),
email: email,
access: access || 'Admin'
});
}
}
return users;
}
function doPost(e) {
try {
let contents = {};
if (e && e.postData && e.postData.contents) {
try {
contents = JSON.parse(e.postData.contents);
} catch(parseErr) {
contents = e.parameter || {};
}
} else if (e && e.parameter) {
contents = e.parameter;
}
const ss = SpreadsheetApp.getActiveSpreadsheet();
const action = contents.action;
const sheet = getBestMenuSheet(ss);
if (action === "ping") {
return ContentService.createTextOutput(JSON.stringify({
status: "success",
version: SCRIPT_VERSION,
versionCode: SCRIPT_VERSION_CODE
})).setMimeType(ContentService.MimeType.JSON);
} else if (action === "add_bill" || action === "archive_bill") {
archiveBillToBillsSheet(ss, contents.bill, contents.user);
} else if (action === "bulk_archive_bills") {
const bills = contents.bills || [];
for (let i = 0; i < bills.length; i++) {
archiveBillToBillsSheet(ss, bills[i], contents.user);
}
} else if (action === "toggleMenuItem") {
const statusText = contents.statusText || (contents.available ? "ON" : "OFF");
toggleMenuItemInSheet(sheet, contents.name || contents.itemId, contents.hsn || "", statusText);
appendLogToSheet(ss, {
user: contents.user,
action: "TOGGLE_MENU_ITEM",
details: (contents.name || contents.itemId) + " set to " + statusText
});
} else if (action === "editMenuItem" || action === "updateMenuItem") {
updateMenuItemInSheet(sheet, contents.item, contents.oldName, contents.oldHsn);
appendLogToSheet(ss, {
user: contents.user,
action: "UPDATE_MENU_ITEM",
details: "Updated item: " + (contents.item && contents.item.name)
});
} else if (action === "addMenuItem") {
addMenuItemToSheet(sheet, contents.item);
appendLogToSheet(ss, {
user: contents.user,
action: "ADD_MENU_ITEM",
details: "Added item: " + (contents.item && contents.item.name)
});
} else if (action === "addUser") {
addUserToDatabaseSheet(ss, contents.user);
appendLogToSheet(ss, {
user: contents.actor,
action: "CREATE_USER",
details: "Added authorized user: " + (contents.user && contents.user.name) + " (" + (contents.user && contents.user.email) + ") as " + (contents.user && contents.user.access)
});
} else if (action === "deleteUser") {
deleteUserFromDatabaseSheet(ss, contents.email);
appendLogToSheet(ss, {
user: contents.actor,
action: "DELETE_USER",
details: "Deleted user: " + contents.email
});
} else if (action === "saveCategory") {
saveCategoryToDatabaseSheet(ss, contents.category, contents.oldName);
appendLogToSheet(ss, {
user: contents.user,
action: "SAVE_CATEGORY",
details: "Category saved: " + (contents.category ? (contents.category.name || contents.category) : contents.name)
});
} else if (action === "deleteCategory") {
deleteCategoryFromDatabaseSheet(ss, contents.name);
appendLogToSheet(ss, {
user: contents.user,
action: "DELETE_CATEGORY",
details: "Category deleted: " + contents.name
});
} else if (action === "updateOrderStatus") {
appendLogToSheet(ss, {
user: contents.user,
action: "ORDER_STATUS_CHANGED",
details: "Order #" + contents.orderId + " moved to " + contents.newStatus
});
} else if (action === "log") {
appendLogToSheet(ss, contents.logEntry || contents);
}
return ContentService.createTextOutput(JSON.stringify({
status: "success",
action: action,
version: SCRIPT_VERSION,
versionCode: SCRIPT_VERSION_CODE
})).setMimeType(ContentService.MimeType.JSON);
} catch (err) {
return ContentService.createTextOutput(JSON.stringify({
status: "error",
version: SCRIPT_VERSION,
message: err.toString()
})).setMimeType(ContentService.MimeType.JSON);
}
}
// Dedicated Bookkeeping: Archive each bill into the "Bills" Tab
function archiveBillToBillsSheet(ss, bill, user) {
if (!bill) return;
try {
let billsSheet = ss.getSheetByName("Bills") || ss.getSheetByName("BILLS") || ss.getSheetByName("bills");
if (!billsSheet) {
billsSheet = ss.insertSheet("Bills");
billsSheet.appendRow(["Bill No", "Timestamp", "Customer Name", "Source", "Payment Mode", "Items Summary", "Total Bill (₹)", "Status"]);
billsSheet.setFrozenRows(1);
}
if (billsSheet.getLastRow() === 0) {
billsSheet.appendRow(["Bill No", "Timestamp", "Customer Name", "Source", "Payment Mode", "Items Summary", "Total Bill (₹)", "Status"]);
billsSheet.setFrozenRows(1);
}
const billNo = String(bill.billNo || '').trim();
const timestamp = bill.timestamp || Utilities.formatDate(new Date(), Session.getScriptTimeZone() || "GMT+5:30", "dd-MMM-yyyy, hh:mm:ss a");
const customerName = String(bill.customerName || 'Walk-in Customer').trim();
const source = String(bill.source || 'POS').trim();
const paymentMode = String(bill.paymentMode || 'Cash').trim();
const items = bill.items || (Array.isArray(bill.itemsList) ? bill.itemsList.map(function(i) { return i.name + " x" + i.qty; }).join(", ") : "");
const totalBill = Number(bill.totalBill || bill.total || 0);
const status = bill.status || "delivered";
// Deduplication check: Avoid inserting duplicates if billNo already exists
const rows = billsSheet.getDataRange().getValues();
let existingRowIdx = -1;
for (let i = 1; i < rows.length; i++) {
if (String(rows[i][0] || '').trim().toUpperCase() === billNo.toUpperCase()) {
existingRowIdx = i + 1;
break;
}
}
if (existingRowIdx > 0) {
billsSheet.getRange(existingRowIdx, 1, 1, 8).setValues([[
billNo,
timestamp,
customerName,
source,
paymentMode,
items,
totalBill,
status
]]);
} else {
billsSheet.appendRow([
billNo,
timestamp,
customerName,
source,
paymentMode,
items,
totalBill,
status
]);
}
const userEmail = (user && (user.email || user.userEmail)) || (bill && (bill.userEmail || (bill.user && bill.user.email))) || "";
const userName = (user && (user.name || user.userName)) || (bill && (bill.userName || (bill.user && bill.user.name))) || "";
const role = (user && (user.role || user.access)) || (bill && (bill.role || (bill.user && bill.user.role))) || "";
appendLogToSheet(ss, {
userEmail: userEmail,
userName: userName,
role: role,
action: "BILL_ARCHIVED",
details: "Archived bill #" + billNo + " for " + customerName + " (" + source + ", ₹" + totalBill + ", " + paymentMode + ")"
});
} catch(e) {
Logger.log("Bills archive error: " + e.toString());
}
}
function appendLogToSheet(ss, logEntry) {
try {
let logSheet = ss.getSheetByName("logs") || ss.getSheetByName("LOGS") || ss.getSheetByName("Logs");
if (!logSheet) {
logSheet = ss.insertSheet("logs");
logSheet.appendRow(["Timestamp", "User Email", "User Name", "Role", "Action", "Details"]);
logSheet.setFrozenRows(1);
}
if (logSheet.getLastRow() === 0) {
logSheet.appendRow(["Timestamp", "User Email", "User Name", "Role", "Action", "Details"]);
logSheet.setFrozenRows(1);
}
const timestamp = (logEntry && logEntry.timestamp) || Utilities.formatDate(new Date(), Session.getScriptTimeZone() || "GMT+5:30", "dd-MMM-yyyy, hh:mm:ss a");
const userEmail = (logEntry && (logEntry.userEmail || (logEntry.user && logEntry.user.email))) || "";
const userName = (logEntry && (logEntry.userName || (logEntry.user && logEntry.user.name))) || "";
const role = (logEntry && (logEntry.role || (logEntry.user && logEntry.user.role) || (logEntry.user && logEntry.user.access))) || "";
const action = (logEntry && logEntry.action) || "";
const details = typeof (logEntry && logEntry.details) === "object" ? JSON.stringify(logEntry.details) : String((logEntry && logEntry.details) || "");
logSheet.appendRow([
timestamp,
userEmail,
userName,
role,
action,
details
]);
} catch(e) {
Logger.log("Logging error: " + e.toString());
}
}
function addUserToDatabaseSheet(ss, user) {
const dbSheet = ss.getSheetByName("DATABASE") || ss.getSheetByName("Database") || ss.getSheetByName("database");
if (!dbSheet) return;
const rows = dbSheet.getDataRange().getValues();
const searchEmail = String(user.email || '').trim().toLowerCase();
// 1. Check if user already exists in any row -> update in place
for (let i = 1; i < rows.length; i++) {
const rowEmail = String(rows[i][3] || '').trim().toLowerCase(); // Column D (Index 3)
if (rowEmail === searchEmail) {
dbSheet.getRange(i + 1, 3).setValue(user.name || ''); // Column C (Col 3)
dbSheet.getRange(i + 1, 5).setValue(user.access || 'User'); // Column E (Col 5)
return;
}
}
// 2. Find first empty slot specifically in USER columns (Columns C, D, E)
let targetRow = -1;
for (let i = 1; i < rows.length; i++) {
const rowEmail = String(rows[i][3] || '').trim();
const rowName = String(rows[i][2] || '').trim();
if (!rowEmail && !rowName) {
targetRow = i + 1;
break;
}
}
// 3. If no middle vacant row found, place right after the last user row
if (targetRow === -1) {
let lastUserRow = 1;
for (let i = 1; i < rows.length; i++) {
if (String(rows[i][3] || '').trim() || String(rows[i][2] || '').trim()) {
lastUserRow = i + 1;
}
}
targetRow = lastUserRow + 1;
}
// Write specifically to columns C, D, E (Col 3: Name, Col 4: Email, Col 5: Access)
dbSheet.getRange(targetRow, 3).setValue(user.name || '');
dbSheet.getRange(targetRow, 4).setValue(searchEmail);
dbSheet.getRange(targetRow, 5).setValue(user.access || 'User');
}
function deleteUserFromDatabaseSheet(ss, email) {
const dbSheet = ss.getSheetByName("DATABASE") || ss.getSheetByName("Database") || ss.getSheetByName("database");
if (!dbSheet) return;
const rows = dbSheet.getDataRange().getValues();
const searchEmail = String(email || '').trim().toLowerCase();
for (let i = 1; i < rows.length; i++) {
const rowEmail = String(rows[i][3] || '').trim().toLowerCase(); // Column D (Index 3)
if (rowEmail === searchEmail) {
// Clear ONLY columns C, D, E (Columns 3, 4, 5) without deleting Category in Columns A & B
dbSheet.getRange(i + 1, 3, 1, 3).clearContent();
// Shift subsequent users up by 1 slot to keep user list compact
for (let j = i + 1; j < rows.length; j++) {
const nextName = rows[j][2];
const nextEmail = rows[j][3];
const nextAccess = rows[j][4];
if (nextEmail || nextName) {
dbSheet.getRange(j, 3, 1, 3).setValues([[nextName, nextEmail, nextAccess]]);
dbSheet.getRange(j + 1, 3, 1, 3).clearContent();
}
}
return;
}
}
}
function saveCategoryToDatabaseSheet(ss, categoryObj, oldName) {
const dbSheet = ss.getSheetByName("DATABASE") || ss.getSheetByName("Database") || ss.getSheetByName("database");
if (!dbSheet) return;
const catName = typeof categoryObj === 'object' ? (categoryObj.name || '') : String(categoryObj || '');
const catImg = typeof categoryObj === 'object' ? (categoryObj.imageUrl || categoryObj.image || '') : '';
if (!catName) return;
const rows = dbSheet.getDataRange().getValues();
const searchOld = String(oldName || catName).trim().toLowerCase();
const searchNew = catName.trim().toLowerCase();
// 1. Update existing category row
for (let i = 1; i < rows.length; i++) {
const rowCat = String(rows[i][0] || '').trim().toLowerCase(); // Column A (Index 0)
if (rowCat === searchOld || rowCat === searchNew) {
dbSheet.getRange(i + 1, 1).setValue(catName); // Column A (Category Name)
if (catImg) dbSheet.getRange(i + 1, 2).setValue(catImg); // Column B (Image URL)
return;
}
}
// 2. Find middle vacant row specifically in Category columns (Column A & B)
let targetRow = -1;
for (let i = 1; i < rows.length; i++) {
const rowCat = String(rows[i][0] || '').trim();
if (!rowCat) {
targetRow = i + 1;
break;
}
}
// 3. Otherwise append row
if (targetRow === -1) {
targetRow = rows.length + 1;
}
dbSheet.getRange(targetRow, 1).setValue(catName);
if (catImg) dbSheet.getRange(targetRow, 2).setValue(catImg);
}
function deleteCategoryFromDatabaseSheet(ss, name) {
const dbSheet = ss.getSheetByName("DATABASE") || ss.getSheetByName("Database") || ss.getSheetByName("database");
if (!dbSheet) return;
const rows = dbSheet.getDataRange().getValues();
const searchName = String(name || '').trim().toLowerCase();
for (let i = 1; i < rows.length; i++) {
const rowCat = String(rows[i][0] || '').trim().toLowerCase();
if (rowCat === searchName) {
// Clear ONLY columns A & B (Columns 1 & 2) without deleting Users in Columns C, D, E
dbSheet.getRange(i + 1, 1, 1, 2).clearContent();
return;
}
}
}
function getBestMenuSheet(ss) {
const sheets = ss.getSheets();
for (let i = 0; i < sheets.length; i++) {
const name = sheets[i].getName().trim().toLowerCase();
if (name === "database" || name === "orders" || name === "bills" || name === "users" || name === "logs") continue;
if (name.includes("menu") || name.includes("item") || name.includes("food") || name === "sheet1") {
return sheets[i];
}
}
for (let i = 0; i < sheets.length; i++) {
const name = sheets[i].getName().trim().toLowerCase();
if (name !== "database" && name !== "orders" && name !== "bills" && name !== "logs") {
return sheets[i];
}
}
return sheets[0];
}
function findColIdx(headers, keywords, excludeIndices) {
const exclude = Array.isArray(excludeIndices) ? excludeIndices : (excludeIndices !== undefined && excludeIndices !== null ? [excludeIndices] : []);
// 1. Try exact match first
for (let k of keywords) {
const target = String(k).trim().toLowerCase();
for (let i = 0; i < headers.length; i++) {
if (exclude.indexOf(i) !== -1) continue;
const h = String(headers[i] || '').trim().toLowerCase();
if (h === target) return i;
}
}
// 2. Try substring match (header includes keyword)
for (let k of keywords) {
const target = String(k).trim().toLowerCase();
if (!target) continue;
for (let i = 0; i < headers.length; i++) {
if (exclude.indexOf(i) !== -1) continue;
const h = String(headers[i] || '').trim().toLowerCase();
if (h.indexOf(target) !== -1) return i;
}
}
return -1;
}
function parseFlexibleSheet(sheet) {
const rows = sheet.getDataRange().getValues();
if (rows.length <= 1) return [];
let headerRowIdx = 0;
for (let i = 0; i < Math.min(5, rows.length); i++) {
const rowStr = rows[i].join(' ').toLowerCase();
if (rowStr.includes('name') || rowStr.includes('mrp') || rowStr.includes('hsn') || rowStr.includes('status') || rowStr.includes('price')) {
headerRowIdx = i;
break;
}
}
const headers = rows[headerRowIdx].map(function(h) { return String(h).trim().toLowerCase(); });
const colHSN = findColIdx(headers, ['hsn', 'code', 'id', 'item id']);
const colName = findColIdx(headers, ['item name', 'name', 'product name', 'item', 'title'], [colHSN]);
const colCat = findColIdx(headers, ['category', 'categories', 'item category', 'dish category', 'cat'], [colHSN, colName]);
const colDiet = findColIdx(headers, ['diet', 'veg / nonveg', 'type', 'food type'], [colHSN, colName, colCat]);
const colMRP = findColIdx(headers, ['mrp', 'maximum retail price', 'original price'], [colHSN, colName, colCat, colDiet]);
const colPrice = findColIdx(headers, ['discounted price', 'discount price', 'selling price', 'price', 'full price', 'full plate'], [colHSN, colName, colCat, colDiet, colMRP]);
const colPrice2 = findColIdx(headers, ['price 2', 'price2', 'half price', 'half plate price', 'half plate', 'half'], [colHSN, colName, colCat, colDiet, colMRP, colPrice]);
const colStatus = findColIdx(headers, ['status', 'available', 'stock', 'online / offline'], [colHSN, colName, colCat, colDiet, colMRP, colPrice, colPrice2]);
const colImg = findColIdx(headers, ['image', 'img', 'photo', 'image url', 'url'], [colHSN, colName, colCat, colDiet, colMRP, colPrice, colPrice2, colStatus]);
const colDesc = findColIdx(headers, ['description', 'desc', 'details', 'item description'], [colHSN, colName, colCat, colDiet, colMRP, colPrice, colPrice2, colStatus, colImg]);
// colSnack keywords MUST NOT include 'cat' or generic 'food' which match Category!
const colSnack = findColIdx(headers, ['food / snack', 'food/snack', 'foodsnack', 'food snack', 'snack / food', 'portion type', 'meal type', 'snacks'], [colHSN, colName, colCat, colDiet, colMRP, colPrice, colPrice2, colStatus, colImg, colDesc]);
const colServe = findColIdx(headers, ['serve', 'serving', 'portion', 'unit', 'serving type'], [colHSN, colName, colCat, colDiet, colMRP, colPrice, colPrice2, colStatus, colImg, colDesc, colSnack]);
const idxHSN = colHSN >= 0 ? colHSN : 0;
const idxName = colName >= 0 ? colName : 1;
const idxCat = colCat >= 0 ? colCat : 2;
const idxDiet = colDiet >= 0 ? colDiet : 3;
const idxMRP = colMRP >= 0 ? colMRP : 4;
const idxPrice = colPrice >= 0 ? colPrice : 5;
const idxPrice2 = colPrice2 >= 0 ? colPrice2 : -1;
const idxStatus = colStatus >= 0 ? colStatus : 6;
const idxImg = colImg >= 0 ? colImg : 7;
const idxDesc = colDesc >= 0 ? colDesc : 8;
const idxSnack = (colSnack >= 0 && colSnack !== idxCat && colSnack !== idxName) ? colSnack : -1;
const idxServe = (colServe >= 0 && colServe !== idxCat && colServe !== idxName) ? colServe : -1;
const result = [];
for (let i = headerRowIdx + 1; i < rows.length; i++) {
const row = rows[i];
const nameVal = String(row[idxName] || '').trim();
const hsnVal = String(row[idxHSN] || '').trim();
if (!nameVal && !hsnVal) continue;
const statusVal = String(row[idxStatus] || 'ON').trim().toUpperCase();
const isAvailable = (statusVal === 'ON' || statusVal === 'TRUE' || statusVal === 'YES' || statusVal === 'ONLINE' || statusVal === '1');
const dietVal = String(row[idxDiet] || 'Veg').trim().toLowerCase();
const diet = (dietVal.includes('non') || dietVal.includes('egg')) ? 'non-veg' : 'veg';
const mrp = parseFloat(row[idxMRP]) || 0;
const price = parseFloat(row[idxPrice]) || mrp;
const price2 = (idxPrice2 >= 0 && row[idxPrice2] !== '' && !isNaN(parseFloat(row[idxPrice2]))) ? parseFloat(row[idxPrice2]) : null;
const rawServe = (idxServe >= 0) ? String(row[idxServe] || 'Pc').trim().toLowerCase() : 'pc';
const serve = rawServe.includes('plate') ? 'Plate' : 'Pc';
let rawCategory = String(row[idxCat] || 'South Indian').trim();
if (!rawCategory || ['food', 'snack', 'snacks', 'food / snack', 'food/snack'].indexOf(rawCategory.toLowerCase()) !== -1) {
rawCategory = 'South Indian';
}
result.push({
id: hsnVal ? ('hsn-' + hsnVal) : ('m-' + i),
hsn: hsnVal,
name: nameVal,
category: rawCategory,
diet: diet,
mrp: mrp,
price: price,
price2: price2,
available: isAvailable,
image: String(row[idxImg] || '').trim(),
description: String(row[idxDesc] || '').trim(),
foodSnack: idxSnack >= 0 ? String(row[idxSnack] || 'Food').trim() : 'Food',
serve: serve
});
}
return result;
}
function toggleMenuItemInSheet(sheet, nameOrId, hsn, statusText) {
const rows = sheet.getDataRange().getValues();
if (rows.length <= 1) return;
const newStatus = (statusText === "OFF" || statusText === false || statusText === "false") ? "OFF" : "ON";
const searchHsn = String(hsn || '').toLowerCase().trim();
const searchName = String(nameOrId || '').toLowerCase().trim();
const headers = rows[0].map(function(h) { return String(h).trim().toLowerCase(); });
const colHSN = findColIdx(headers, ['hsn', 'code', 'id']);
const colName = findColIdx(headers, ['item name', 'name', 'item', 'title'], [colHSN]);
const colStatus = findColIdx(headers, ['status', 'available', 'stock'], [colHSN, colName]);
const idxHSN = colHSN >= 0 ? colHSN : 0;
const idxName = colName >= 0 ? colName : 1;
const idxStatus = colStatus >= 0 ? colStatus : 6;
for (let i = 1; i < rows.length; i++) {
const rowHsn = String(rows[i][idxHSN] || '').toLowerCase().trim();
const rowName = String(rows[i][idxName] || '').toLowerCase().trim();
if ((searchHsn && rowHsn === searchHsn) || (searchName && rowName === searchName)) {
try {
sheet.getRange(i + 1, idxStatus + 1).setValue(newStatus);
} catch(e) {
try { sheet.getRange(i + 1, idxStatus + 1).setValue(newStatus === 'ON'); } catch(e2) {}
}
return;
}
}
}
function updateMenuItemInSheet(sheet, item, oldName, oldHsn) {
const rows = sheet.getDataRange().getValues();
if (rows.length === 0) {
addMenuItemToSheet(sheet, item);
return;
}
let headerRowIdx = 0;
for (let i = 0; i < Math.min(5, rows.length); i++) {
const rowStr = rows[i].join(' ').toLowerCase();
if (rowStr.includes('name') || rowStr.includes('mrp') || rowStr.includes('hsn') || rowStr.includes('status') || rowStr.includes('price')) {
headerRowIdx = i;
break;
}
}
const headers = rows[headerRowIdx].map(function(h) { return String(h).trim().toLowerCase(); });
const colHSN = findColIdx(headers, ['hsn', 'code', 'id', 'item id']);
const colName = findColIdx(headers, ['item name', 'name', 'product name', 'item', 'title'], [colHSN]);
const colCat = findColIdx(headers, ['category', 'categories', 'item category', 'dish category', 'cat'], [colHSN, colName]);
const colDiet = findColIdx(headers, ['diet', 'veg / nonveg', 'type', 'food type'], [colHSN, colName, colCat]);
const colMRP = findColIdx(headers, ['mrp', 'maximum retail price', 'original price'], [colHSN, colName, colCat, colDiet]);
const colPrice = findColIdx(headers, ['discounted price', 'discount price', 'selling price', 'price', 'full price', 'full plate'], [colHSN, colName, colCat, colDiet, colMRP]);
const colPrice2 = findColIdx(headers, ['price 2', 'price2', 'half price', 'half plate price', 'half plate', 'half'], [colHSN, colName, colCat, colDiet, colMRP, colPrice]);
const colStatus = findColIdx(headers, ['status', 'available', 'stock', 'online / offline'], [colHSN, colName, colCat, colDiet, colMRP, colPrice, colPrice2]);
const colImg = findColIdx(headers, ['image', 'img', 'photo', 'image url', 'url'], [colHSN, colName, colCat, colDiet, colMRP, colPrice, colPrice2, colStatus]);
const colDesc = findColIdx(headers, ['description', 'desc', 'details', 'item description'], [colHSN, colName, colCat, colDiet, colMRP, colPrice, colPrice2, colStatus, colImg]);
// colSnack keywords MUST NOT include 'cat' or generic 'food' which match Category!
const colSnack = findColIdx(headers, ['food / snack', 'food/snack', 'foodsnack', 'food snack', 'snack / food', 'portion type', 'meal type', 'snacks'], [colHSN, colName, colCat, colDiet, colMRP, colPrice, colPrice2, colStatus, colImg, colDesc]);
const colServe = findColIdx(headers, ['serve', 'serving', 'portion', 'unit', 'serving type'], [colHSN, colName, colCat, colDiet, colMRP, colPrice, colPrice2, colStatus, colImg, colDesc, colSnack]);
const idxHSN = colHSN >= 0 ? colHSN : 0;
const idxName = colName >= 0 ? colName : 1;
const idxCat = colCat >= 0 ? colCat : 2;
const idxDiet = colDiet >= 0 ? colDiet : 3;
const idxMRP = colMRP >= 0 ? colMRP : 4;
const idxPrice = colPrice >= 0 ? colPrice : 5;
const idxPrice2 = colPrice2 >= 0 ? colPrice2 : -1;
const idxStatus = colStatus >= 0 ? colStatus : 6;
const idxImg = colImg >= 0 ? colImg : 7;
const idxDesc = colDesc >= 0 ? colDesc : 8;
const searchHsn = String(oldHsn || (item && item.hsn) || '').trim().toLowerCase();
const searchName = String(oldName || (item && item.name) || '').trim().toLowerCase();
let targetRowIdx = -1;
for (let i = headerRowIdx + 1; i < rows.length; i++) {
const rowHsn = String(rows[i][idxHSN] || '').trim().toLowerCase();
const rowName = String(rows[i][idxName] || '').trim().toLowerCase();
if ((searchHsn && rowHsn === searchHsn) || (searchName && rowName === searchName)) {
targetRowIdx = i + 1;
break;
}
}
const rawDiet = String((item && item.diet) || 'Veg').toLowerCase();
const formattedDiet = rawDiet.includes('non') ? 'Nonveg' : 'Veg';
const rawSnack = String((item && item.foodSnack) || 'Food').toLowerCase();
const formattedSnack = rawSnack.includes('snack') ? 'Snacks' : 'Food';
const rawServe = String((item && item.serve) || 'Pc').toLowerCase();
const formattedServe = rawServe.includes('plate') ? 'Plate' : 'Pc';
const availText = (item && (item.available === true || item.available === 'true' || item.available === 'ON')) ? 'ON' : 'OFF';
// Ensure category is never 'Food' or 'Snacks'
let safeCategory = String((item && item.category) || 'South Indian').trim();
if (!safeCategory || ['food', 'snack', 'snacks', 'food / snack', 'food/snack'].indexOf(safeCategory.toLowerCase()) !== -1) {
safeCategory = 'South Indian';
}
const updates = [
{ col: idxHSN + 1, val: item.hsn || '' },
{ col: idxName + 1, val: item.name || '' },
{ col: idxCat + 1, val: safeCategory },
{ col: idxDiet + 1, val: formattedDiet },
{ col: idxMRP + 1, val: Number(item.mrp || item.price || 0) },
{ col: idxPrice + 1, val: Number(item.price || item.mrp || 0) },
{ col: idxStatus + 1, val: availText },
{ col: idxImg + 1, val: item.image || '' },
{ col: idxDesc + 1, val: item.description || '' }
];
// ONLY update Food/Snack if column was genuinely found and is separate from Category, Name, HSN
if (colSnack >= 0 && colSnack !== idxCat && colSnack !== idxName && colSnack !== idxHSN) {
updates.push({ col: colSnack + 1, val: formattedSnack });
}
// ONLY update Serve if column was genuinely found and is separate
if (colServe >= 0 && colServe !== idxCat && colServe !== colSnack && colServe !== idxName) {
updates.push({ col: colServe + 1, val: formattedServe });
}
if (idxPrice2 >= 0 && idxPrice2 !== idxPrice && idxPrice2 !== idxMRP) {
updates.push({ col: idxPrice2 + 1, val: (item.price2 !== undefined && item.price2 !== null && item.price2 !== '') ? Number(item.price2) : '' });
}
if (targetRowIdx > 0) {
for (let j = 0; j < updates.length; j++) {
try {
sheet.getRange(targetRowIdx, updates[j].col).setValue(updates[j].val);
} catch (e) {
try {
sheet.getRange(targetRowIdx, updates[j].col).clearDataValidations().setValue(updates[j].val);
} catch(err2) {}
}
}
} else {
addMenuItemToSheet(sheet, item);
}
}
function addMenuItemToSheet(sheet, item) {
const rawDiet = String((item && item.diet) || 'Veg').toLowerCase();
const formattedDiet = rawDiet.includes('non') ? 'Nonveg' : 'Veg';
const rawSnack = String((item && item.foodSnack) || 'Food').toLowerCase();
const formattedSnack = rawSnack.includes('snack') ? 'Snacks' : 'Food';
const rawServe = String((item && item.serve) || 'Pc').toLowerCase();
const formattedServe = rawServe.includes('plate') ? 'Plate' : 'Pc';
const availText = (item && (item.available === true || item.available === 'true' || item.available === 'ON')) ? 'ON' : 'OFF';
sheet.appendRow([
item.hsn || '',
item.name || '',
item.category || 'South Indian',
formattedDiet,
Number(item.mrp || item.price || 0),
Number(item.price || item.mrp || 0),
availText,
item.image || '',
item.description || '',
formattedSnack,
formattedServe,
(item.price2 !== undefined && item.price2 !== null && item.price2 !== '') ? Number(item.price2) : ''
]);
}
Assets & Mobile Management
Manage Posters, Categories & Voucher/Coupon Codes for the Customer Mobile App & Store.
Mobile App Posters & Banners
Posters displayed on the top carousel of the customer mobile app.
Categories Management
Categories stored in Column A & B of your DATABASE Sheet.
Voucher & Coupon Codes
Promo codes redeemable by users during checkout in the mobile app.
Delivery Apartments & Societies
Apartments and societies available for customer onboarding and delivery in the mobile app.