/** Astra educational example. Bound to a Google Spreadsheet. */ const REQUESTS = 'Requests'; const SUMMARY = 'Summary'; const HEADERS = ['Request ID','Timestamp','Name','Type','Description','Status','Updated At']; const STATUSES = ['New','In Progress','Done']; function setup() { const book = SpreadsheetApp.getActiveSpreadsheet(); let sheet = book.getSheetByName(REQUESTS) || book.insertSheet(REQUESTS); if (sheet.getLastRow() === 0) sheet.appendRow(HEADERS); const actual = sheet.getRange(1,1,1,HEADERS.length).getValues()[0]; if (JSON.stringify(actual) !== JSON.stringify(HEADERS)) throw Error('Unexpected Requests headers'); if (!book.getSheetByName(SUMMARY)) book.insertSheet(SUMMARY); return sheet; } function addRequest(name,type,description) { for (const [key,val] of Object.entries({name,type,description})) { if (typeof val !== 'string' || !val.trim() || val.length > 500) throw Error('Invalid '+key); } const lock = LockService.getDocumentLock(); if (!lock) throw Error('Run as a spreadsheet-bound script'); lock.waitLock(15000); try { const sheet = setup(), id = Utilities.getUuid(), now = new Date(); sheet.appendRow([id,now,name.trim(),type.trim(),description.trim(),'New',now]); return id; } finally {lock.releaseLock();} } function updateStatus(id,status) { if (typeof id !== 'string' || !id.trim() || !STATUSES.includes(status)) throw Error('Invalid ID/status'); const lock = LockService.getDocumentLock(); lock.waitLock(15000); try { const sheet = setup(); const count = sheet.getLastRow()-1; if (count < 1) throw Error('No requests'); const ids = sheet.getRange(2,1,count,1).getValues(); const pos = ids.findIndex(row => row[0] === id); if (pos < 0) throw Error('Request not found'); sheet.getRange(pos+2,6,1,2).setValues([[status,new Date()]]); } finally {lock.releaseLock();} } function updateSummary() { const sheet = setup(), summary = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SUMMARY); const count = Math.max(0,sheet.getLastRow()-1); const statuses = count ? sheet.getRange(2,6,count,1).getValues().flat() : []; const data = [['Metric','Count'],['Total',count],...STATUSES.map(s => [s,statuses.filter(x=>x===s).length])]; summary.clearContents(); summary.getRange(1,1,data.length,2).setValues(data); } function demoAddRequest() { const id = addRequest('Demo Student','IT Support','Fictional laptop setup request'); PropertiesService.getDocumentProperties().setProperty('DEMO_REQUEST_ID',id); updateSummary(); Logger.log('Created demo request: '+id); } function demoUpdateRequest() { const id = PropertiesService.getDocumentProperties().getProperty('DEMO_REQUEST_ID'); if (!id) throw Error('Run demoAddRequest first'); updateStatus(id,'In Progress'); updateSummary(); } function runSelfTests() { if (STATUSES.length !== 3 || HEADERS.length !== 7) throw Error('Schema mismatch'); if (!STATUSES.includes('Done') || STATUSES.includes('Deleted')) throw Error('Status list mismatch'); Logger.log('Schema and allowed-status checks passed (no rows modified)'); }