The item list loads automatically from the Off The Rails Google Sheet. Use this screen to optionally connect the shared orders backend (Apps Script) so all chefs' orders pool into one live list.
Apps Script Setup (one-time)
In your Google Sheet go to Extensions → Apps Script, paste this code, click Deploy → New deployment → Web app (Anyone can access), copy the URL above.
// Paste into Google Apps Script
const RESET_HOUR = 9;
function doGet(e) {
const action = e.parameter.action || '';
const sheetName = e.parameter.sheet || 'OTR Daily Order';
const curName = e.parameter.current || 'OTR Current Order';
if (action === 'saveproduct') {
return saveProduct(e.parameter);
}
if (action === 'deleteproduct') {
return deleteProduct(e.parameter);
}
if (action === 'read') {
return readCurrentOrder(curName);
}
if (action === 'clear') {
clearCurrentOrder(curName);
return ContentService.createTextOutput('ok');
}
if (action === 'reset') {
copyToHistory(curName, sheetName);
clearCurrentOrder(curName);
return ContentService.createTextOutput('ok');
}
if (action === 'deleteline') {
const sku = e.parameter.sku || '';
deleteFromCurrent(sku, curName);
return ContentService.createTextOutput('ok');
}
if (action === 'editline') {
const sku = e.parameter.sku || '';
const sup = e.parameter.sup || '';
const name = e.parameter.name || '';
const qty = e.parameter.qty || '';
const time = e.parameter.time || '';
const chef = e.parameter.chef || 'EDIT';
deleteFromCurrent(sku, curName);
const sh = getCurrentSheet(curName);
sh.appendRow([getTodayKey(), chef, time, sup, name, sku, qty]);
return ContentService.createTextOutput('ok');
}
return ContentService.createTextOutput('ok');
}
function doPost(e) {
const data = JSON.parse(e.postData.contents);
const curName = data.currentOrder || 'OTR Current Order';
return writeOrder(data, curName);
}
function getTodayKey() {
const now = new Date();
if (now.getHours() < RESET_HOUR) now.setDate(now.getDate() - 1);
return Utilities.formatDate(now, 'GMT', 'yyyy-MM-dd');
}
function getSheet(name) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
let sh = ss.getSheetByName(name);
if (!sh) {
sh = ss.insertSheet(name);
sh.appendRow(['Date','Chef','Time','Supplier','Item','SKU','Qty']);
}
return sh;
}
function getCurrentSheet(curName) { return getSheet(curName); }
function readCurrentOrder(curName) {
const sh = getCurrentSheet(curName);
const today = getTodayKey();
const rows = sh.getDataRange().getValues();
const items = [];
for (let i = 1; i < rows.length; i++) {
if (String(rows[i][0]) === today) {
items.push({
chef: rows[i][1], time: rows[i][2],
supplier: rows[i][3], name: rows[i][4],
sku: rows[i][5], qty: rows[i][6]
});
}
}
const json = JSON.stringify({ ok: true, items });
return ContentService.createTextOutput(json)
.setMimeType(ContentService.MimeType.JSON);
}
function writeOrder(data, curName) {
const sh = getCurrentSheet(curName);
const today = getTodayKey();
data.items.forEach(item => {
sh.appendRow([today, data.chef, data.time,
item.supplier, item.name, item.sku, item.qty]);
});
return ContentService.createTextOutput('ok');
}
function deleteFromCurrent(sku, curName) {
const sh = getCurrentSheet(curName);
const today = getTodayKey();
const rows = sh.getDataRange().getValues();
for (let i = rows.length - 1; i >= 1; i--) {
if (String(rows[i][0]) === today && String(rows[i][5]) === String(sku)) {
sh.deleteRow(i + 1);
}
}
}
function clearCurrentOrder(curName) {
const sh = getCurrentSheet(curName);
const today = getTodayKey();
const rows = sh.getDataRange().getValues();
for (let i = rows.length - 1; i >= 1; i--) {
if (String(rows[i][0]) === today) sh.deleteRow(i + 1);
}
}
function copyToHistory(curName, histName) {
const cur = getCurrentSheet(curName);
const hist = getSheet(histName);
const today = getTodayKey();
const rows = cur.getDataRange().getValues();
for (let i = 1; i < rows.length; i++) {
if (String(rows[i][0]) === today) {
hist.appendRow(rows[i]);
}
}
}
// Auto-reset at 9am via time-based trigger
// Set up: Triggers -> Add Trigger -> dailyReset -> Time-driven -> Day timer -> 9am
function saveProduct(p) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const venue = p.venue || 'otr';
const tabName = venue === 'lp' ? 'LP Order Sheet' : 'OTR Order Sheet';
let sh = ss.getSheetByName(tabName);
if (!sh) sh = ss.getSheetByName('order sheet');
if (!sh) return ContentService.createTextOutput('no sheet');
const isEdit = p.isEdit === 'true';
const origSku = p.origSku || p.sku;
const rowNum = parseInt(p.sheetRow || '0');
// Col order: A:ITEM B:SKU C:SUPPLIER D:LOCATION E:TYPE F:FOH/KITCHEN G:KITCHEN H:UNIT I:COST J:PAR K:SECTIONM
const rowData = [p.name, p.sku, p.supplier, '', p.type, p.foh, '', '', p.price, '', p.section];
if (isEdit) {
// Try by exact sheet row number first (most reliable)
if (rowNum > 1) {
const existing = sh.getRange(rowNum, 1, 1, rowData.length).getValues()[0];
// Verify it's the right row by checking original SKU
if (String(existing[1]) === String(origSku)) {
sh.getRange(rowNum, 1, 1, rowData.length).setValues([rowData]);
return ContentService.createTextOutput('updated');
}
}
// Fallback: find by original SKU
const rows = sh.getDataRange().getValues();
for (let i = 1; i < rows.length; i++) {
if (String(rows[i][1]) === String(origSku)) {
sh.getRange(i + 1, 1, 1, rowData.length).setValues([rowData]);
return ContentService.createTextOutput('updated');
}
}
}
// New product — check for duplicate SKU first
const allRows = sh.getDataRange().getValues();
for (let i = 1; i < allRows.length; i++) {
if (String(allRows[i][1]) === String(p.sku)) {
// SKU exists - update instead of adding
sh.getRange(i + 1, 1, 1, rowData.length).setValues([rowData]);
return ContentService.createTextOutput('updated-existing');
}
}
sh.appendRow(rowData);
return ContentService.createTextOutput('added');
}
function deleteProduct(p) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const venue = p.venue || 'otr';
const tabName = venue === 'lp' ? 'LP Order Sheet' : 'OTR Order Sheet';
let sh = ss.getSheetByName(tabName);
if (!sh) sh = ss.getSheetByName('order sheet');
if (!sh) return ContentService.createTextOutput('no sheet');
const origSku = p.origSku || p.sku;
const rowNum = parseInt(p.sheetRow || '0');
if (rowNum > 1) {
const existing = sh.getRange(rowNum, 2, 1, 1).getValue();
if (String(existing) === String(origSku)) {
sh.deleteRow(rowNum);
return ContentService.createTextOutput('deleted');
}
}
// Fallback: find by SKU
const rows = sh.getDataRange().getValues();
for (let i = rows.length - 1; i >= 1; i--) {
if (String(rows[i][1]) === String(origSku)) {
sh.deleteRow(i + 1);
return ContentService.createTextOutput('deleted');
}
}
return ContentService.createTextOutput('not found');
}
function dailyReset() {
const venues = [
{ current: 'OTR Current Order', history: 'OTR Daily Order' },
{ current: 'LP Current Order', history: 'LP Daily Order' }
];
venues.forEach(v => {
copyToHistory(v.current, v.history);
clearCurrentOrder(v.current);
});
}