Products and supplier emails load from Supabase. Use this screen to optionally connect Apps Script for copying order history to Google Sheets.
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 === 'additem') {
const sh = getCurrentSheet(curName);
const today = getTodayKey();
sh.appendRow([
today,
e.parameter.chef || '',
e.parameter.time || '',
e.parameter.supplier || '',
e.parameter.name || '',
e.parameter.sku || '',
e.parameter.qty || ''
]);
return ContentService.createTextOutput('ok');
}
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 || new Date().toLocaleTimeString();
const chef = e.parameter.chef || 'EDIT';
// Delete all rows for this SKU then write corrected row
deleteFromCurrent(sku, curName);
if (qty && parseFloat(qty) > 0) {
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, 'Europe/London', '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,
p.a1||'', p.a2||'', p.a3||'', p.a4||'', p.a5||'', p.a6||'', p.a7||'',
p.a8||'', p.a9||'', p.a10||'', p.a11||'', p.a12||'', p.a13||'', p.a14||'',
p.glutenSubs||'', p.nutsSubs||'', p.vegan||'', p.vegetarian||''
];
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);
});
}