Skip to content

cod gs #651

Description

@mohamedsami-ux

/**

  • TRU WorkForce Management System - Backend Engine (Google Apps Script)
  • Integrated Intelligent Systems: 3CX tracking, daily call calculation, and unified auto-cache.
  • Securely designed with LockService, robust try-catch-finally, and offline queue support.
    • APPROVED SYSTEM CONTROLS:
    1. Unified global spreadsheet ID for easier maintenance.
    1. Enforced LockService to protect manual/automated operations from data collision.
    1. Safe try-catch-finally structures protecting administrative processes.
      */

// Global Spreadsheet Identifier
const SPREADSHEET_ID = "1ApKbwjdJPyjK-t3EZrd8XGhzs52aYevQNfAo8L3b5kU";
const ATTENDANCE_SHEET_NAME = "Attendance Digital";
const DEFAULT_TEAM = "Community Operations";
const LOCK_TIMEOUT_MS = 10000;

/**

  • Directs the user to the appropriate page based on the query parameters.
  • @param {Object} e HTTP Event parameters from the web app deployment.
  • @return {HtmlOutput} Evaluated HTML interface template.
    */
    function doGet(e) {
    var page = e && e.parameter ? e.parameter.page : null;

// 1. Supervisor/Admin Dashboard (?page=admin)
if (page === 'admin') {
return HtmlService.createHtmlOutputFromFile('Admin')
.setTitle('TRU - Supervisor Dashboard')
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}

// 2. Main Live Hall Dashboard (?page=wallboard)
if (page === 'wallboard') {
return HtmlService.createHtmlOutputFromFile('Wallboard')
.setTitle('TRU - Live TV Wallboard')
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}

// 3. IT Helpdesk Panel (?page=it)
if (page === 'it') {
return HtmlService.createHtmlOutputFromFile('ITPanel')
.setTitle('TRU - IT Support HelpDesk')
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}

// 4. Shift Management & Scheduling Platform
if (page === 'rota' || page === 'shifts' || page === 'ShiftManager' || page === 'TRU Shift Management Platform') {
return HtmlService.createHtmlOutputFromFile('ShiftManager')
.setTitle('TRU - Shift Management & Scheduling')
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}

// 5. Standard Agent Portal (Default view)
var template = HtmlService.createTemplateFromFile('Index');
var phoneNumber = (e && e.parameter && e.parameter.phoneNumber) ? e.parameter.phoneNumber : "";
template.phoneNumber = phoneNumber;

return template.evaluate()
.setTitle('TRU - WorkForce Management')
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}

/**

  • Gets the current active user's Google email address.
  • @return {string} User email address.
    */
    function getUserEmail() {
    return Session.getActiveUser().getEmail();
    }

/**

  • Extracts and formats the first name of the user from their email address.
  • @return {string} Formatted first name.
    */
    function getUserFirstName() {
    var email = Session.getActiveUser().getEmail();
    var namePart = email.split('@')[0];
    var firstName = namePart.split('.')[0];
    return firstName.charAt(0).toUpperCase() + firstName.slice(1);
    }

/**

  • Simple ping endpoint to measure client-server latency.
  • @return {string} Standard response token.
    */
    function pingServer() {
    return "pong";
    }

/**

  • Counts total calls received today in the general queue.

  • @return {number} Calculated call count.
    */
    function getTotalCallsToday() {
    try {
    var ss = SpreadsheetApp.openById(SPREADSHEET_ID);
    var mainSheet = ss.getSheetByName(ATTENDANCE_SHEET_NAME) || ss.getSheets()[0];
    var lastRow = mainSheet.getLastRow();
    if (lastRow <= 1) return 0;

    var data = mainSheet.getRange(2, 1, lastRow - 1, 19).getValues();
    var count = 0;
    var todayStr = new Date().toDateString();

    for (var i = 0; i < data.length; i++) {
    var rowDateStr = new Date(data[i][0]).toDateString();
    if (rowDateStr === todayStr && data[i][18]) {
    count++;
    }
    }
    return count;
    } catch(e) {
    console.error("Error calculating total calls today: " + e.toString());
    return 0;
    }
    }

/**

  • Logs agent activities to the unified central sheet and their personal sheet.
    */
    function logAction(action, aux, status, timerData, reason, shiftTimes, phoneNumber) {
    var lock = LockService.getScriptLock();
    try {
    lock.waitLock(LOCK_TIMEOUT_MS);
    var ss = SpreadsheetApp.openById(SPREADSHEET_ID);
    var userEmail = Session.getActiveUser().getEmail();
    var sheetName = userEmail.split('@')[0].replace(/[^a-zA-Z0-9]/g, "");
    var agentName = sheetName.split('
    ').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');

    var now = new Date();

    // Adjust timestamp for historical sync or completion parameters
    if (shiftTimes && shiftTimes.end && (action === "End Shift" || action === "Force Sync" || action === "Manual Sync" || action.includes("Sync"))) {
    try {
    var parts = shiftTimes.end.split(" ");
    var timeParts = parts[0].split(":");
    var tempDate = new Date();
    var hrs = parseInt(timeParts[0], 10);
    var mins = parseInt(timeParts[1], 10);
    var secs = parseInt(timeParts[2], 10);

     if (parts[1]) {
       var ampm = parts[1].toUpperCase();
       if (ampm === "PM" && hrs < 12) hrs += 12;
       if (ampm === "AM" && hrs === 12) hrs = 0;
     }
     tempDate.setHours(hrs, mins, secs);
     now = tempDate;
    

    } catch(e) {
    now = new Date(); // Fallback
    }
    }

    var timeNote = calculateShiftStatus(action, now);
    var userAgent = HtmlService.getUserAgent();
    var deviceInfo = parseDevice(userAgent);

    var liveTeam = (shiftTimes && shiftTimes.team) ? shiftTimes.team : DEFAULT_TEAM;
    var finalDevice = (action === "Start Shift" && reason && reason.includes("CPU:")) ? deviceInfo + " | " + reason : deviceInfo;
    var finalReason = (action === "Start Shift" && reason && reason.includes("CPU:")) ? "System Check Passed" : (reason || "");
    var cleanPhone = phoneNumber ? phoneNumber.trim() : "";

    var rowData = [now, agentName, action, finalDevice, userEmail, aux, status, timeNote];
    if ((action === "End Shift" || action === "Force Sync" || action === "Manual Sync" || action.includes("Sync")) && timerData) {
    rowData.push(timerData["1"], timerData["2"], timerData["3"], timerData["4"], timerData["5"], timerData["6"]);
    } else {
    rowData.push("", "", "", "", "", "");
    }
    rowData.push(finalReason);
    if (shiftTimes) {
    rowData.push(shiftTimes.start || "", shiftTimes.end || "", liveTeam);
    } else {
    rowData.push("", "", liveTeam);
    }

    rowData.push(cleanPhone);

    var mainSheet = ss.getSheetByName(ATTENDANCE_SHEET_NAME) || ss.getSheets()[0];
    mainSheet.appendRow(rowData);

    var agentSheet = ss.getSheetByName(sheetName);
    if (!agentSheet) {
    agentSheet = ss.insertSheet(sheetName);
    var headers = ["Timestamp", "Agent Name", "Action", "Device", "User Email", "AUX", "Status", "Time Notes", "Auto-in", "ACW", "Outgoing", "Break", "Meeting", "Misc", "Reason/Note", "Start Shift", "End Shift", "Team", "Customer Phone"];
    agentSheet.appendRow(headers);
    agentSheet.getRange(1, 1, 1, headers.length).setFontWeight("bold").setBackground("#2C3B79").setFontColor("white").setHorizontalAlignment("center");
    agentSheet.setFrozenRows(1);
    }
    agentSheet.appendRow(rowData);

    // Synchronize Cache with System Properties
    var cache = PropertiesService.getScriptProperties();
    var agentState = {
    name: agentName,
    action: action,
    status: status,
    timestamp: now.getTime(),
    note: finalReason,
    email: userEmail,
    device: finalDevice,
    team: liveTeam,
    breakdown: timerData ? JSON.stringify(timerData) : "{}",
    phoneNumber: cleanPhone
    };
    cache.setProperty("live_agent_" + userEmail.toLowerCase(), JSON.stringify(agentState));

    return { success: true, note: timeNote };
    } catch(e) {
    return { success: false, error: e.toString() };
    } finally {
    lock.releaseLock();
    }
    }

/**

  • Retrieves the state of all agents currently monitored in the system.
    */
    function getAllAgentsStatus() {
    try {
    var cache = PropertiesService.getScriptProperties();
    var keys = cache.getKeys();
    var list = [];
    var now = new Date().getTime();
    var limit12Hours = 12 * 60 * 60 * 1000;

    for (var i = 0; i < keys.length; i++) {
    if (keys[i].indexOf("live_agent_") === 0) {
    var val = cache.getProperty(keys[i]);
    if (val) {
    var parsed = JSON.parse(val);

       if (now - parsed.timestamp > limit12Hours) {
         cache.deleteProperty(keys[i]);
         continue;
       }
       
       if (!parsed.hasOwnProperty('phoneNumber')) {
         parsed.phoneNumber = "";
       }
       list.push(parsed);
     }
    

    }
    }
    if (list.length > 0) return list;

    // Fallback directly to spreadsheet layer
    var ss = SpreadsheetApp.openById(SPREADSHEET_ID);
    var mainSheet = ss.getSheetByName(ATTENDANCE_SHEET_NAME) || ss.getSheets()[0];
    var lastRow = mainSheet.getLastRow();
    if (lastRow <= 1) return [];
    var data = mainSheet.getRange(2, 1, lastRow - 1, 19).getValues();
    var agents = {};
    for (var j = 0; j < data.length; j++) {
    var email = data[j][4];
    if (email) {
    agents[email.toLowerCase()] = {
    name: data[j][1],
    action: data[j][2],
    status: data[j][6],
    timestamp: new Date(data[j][0]).getTime(),
    note: data[j][14] || "",
    email: email,
    device: data[j][3],
    team: data[j][17] || DEFAULT_TEAM,
    breakdown: "{}",
    phoneNumber: data[j][18] || ""
    };
    }
    }
    return Object.values(agents);
    } catch (e) {
    return [];
    }
    }

/**

  • Daily Cache Cleanup Utility

  • Removes logged inactive/out-of-shift agents older than 16 hours.
    */
    function cleanupStaleAgents() {
    try {
    var cache = PropertiesService.getScriptProperties();
    var keys = cache.getKeys();
    var now = new Date().getTime();
    var limit16Hours = 16 * 60 * 60 * 1000;
    var count = 0;

    for (var i = 0; i < keys.length; i++) {
    if (keys[i].indexOf("live_agent_") === 0) {
    var val = cache.getProperty(keys[i]);
    if (val) {
    var parsed = JSON.parse(val);
    if (now - parsed.timestamp > limit16Hours || parsed.action === "End Shift" || parsed.status === "Idle") {
    cache.deleteProperty(keys[i]);
    count++;
    }
    }
    }
    }
    console.log("Cleanup finished. Removed " + count + " stale agent properties.");
    return "Cleared " + count + " agents.";
    } catch(e) {
    return "Error: " + e.toString();
    }
    }

/**

  • Normalizes and extracts client OS and Device Model.
    */
    function parseDevice(ua) {
    var os = "Unknown OS", model = "Unknown Device";
    if (ua.indexOf("Android") > -1) {
    os = "Android";
    var match = ua.match(/Android\s.[^;]+;\s([^;)]+)/);
    model = match ? match[1].trim() : "Generic Device";
    } else if (ua.indexOf("iPhone") > -1) {
    os = "iOS";
    model = "iPhone";
    } else if (ua.indexOf("Windows") > -1) {
    os = "Windows";
    model = "PC";
    }
    return os + " | " + model;
    }

/**

  • Computes punctuality notes for Shift Starts and Ends.
    */
    function calculateShiftStatus(action, now) {
    var hours = now.getHours(), minutes = now.getMinutes(), seconds = now.getSeconds();
    var actualTotalSeconds = (hours * 3600) + (minutes * 60) + seconds;
    if (action === "Start Shift") {
    var targetSec;
    if (actualTotalSeconds <= (11 * 3600)) targetSec = 10 * 3600;
    else if (actualTotalSeconds <= (13.5 * 3600)) targetSec = 12 * 3600;
    else targetSec = 15 * 3600;
    return formatDifference(actualTotalSeconds, targetSec, "On Time", "Early", "Late");
    } else if (action === "End Shift") {
    var targetSec;
    if (actualTotalSeconds <= (19 * 3600)) targetSec = 18 * 3600;
    else if (actualTotalSeconds <= (21.5 * 3600)) targetSec = 20 * 3600;
    else targetSec = 23 * 3600;
    return formatDifference(actualTotalSeconds, targetSec, "On Time Logout", "Early Logout", "Over Time");
    }
    return "";
    }

/**

  • Formats time difference as custom labels.
    */
    function formatDifference(actual, target, onTimeLabel, earlyLabel, lateLabel) {
    if (Math.abs(actual - target) < 60) return onTimeLabel;
    var diff = Math.abs(actual - target);
    var h = Math.floor(diff / 3600), m = Math.floor((diff % 3600) / 60), s = diff % 60;
    var timeStr = [h, m, s].map(v => v < 10 ? "0" + v : v).join(":");
    return (actual < target) ? earlyLabel + " " + timeStr : lateLabel + " " + timeStr;
    }

/**

  • Administrator approval process for pending agent requests.
    */
    function adminGrantPermission(agentEmail, reqType) {
    reqType = reqType || "Permission";
    var lock = LockService.getScriptLock();
    try {
    lock.waitLock(LOCK_TIMEOUT_MS);
    var ss = SpreadsheetApp.openById(SPREADSHEET_ID);
    var sheetName = agentEmail.split('@')[0].replace(/[^a-zA-Z0-9]/g, "");
    var agentName = sheetName.split('
    ').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
    var now = new Date();

    var displayAction = reqType.replace(/request/gi, "Approved").replace(/leave/gi, "Leave Approved");
    if (displayAction.indexOf("Approved") === -1) {
    displayAction += " Approved";
    }

    var rowData = [now, agentName, displayAction, "System | Remote", agentEmail, "Approved", "Available", "Supervisor Approved " + reqType, "", "", "", "", "", "", "Remote Permission Granted", "", "", "", ""];
    var mainSheet = ss.getSheetByName(ATTENDANCE_SHEET_NAME) || ss.getSheets()[0];
    mainSheet.appendRow(rowData);
    var agentSheet = ss.getSheetByName(sheetName);
    if (agentSheet) agentSheet.appendRow(rowData);

    var cache = PropertiesService.getScriptProperties();
    var liveVal = cache.getProperty("live_agent_" + agentEmail.toLowerCase());
    var parsed = liveVal ? JSON.parse(liveVal) : { name: agentName, email: agentEmail };
    parsed.action = displayAction;
    parsed.status = "Available";
    parsed.timestamp = now.getTime();
    cache.setProperty("live_agent_" + agentEmail.toLowerCase(), JSON.stringify(parsed));

    return { success: true };
    } catch(e) {
    return { success: false, error: e.toString() };
    } finally {
    lock.releaseLock();
    }
    }

/**

  • Administrator rejection process for pending agent requests.
    */
    function adminRejectPermission(agentEmail, reqType) {
    reqType = reqType || "Permission";
    var lock = LockService.getScriptLock();
    try {
    lock.waitLock(LOCK_TIMEOUT_MS);
    var ss = SpreadsheetApp.openById(SPREADSHEET_ID);
    var sheetName = agentEmail.split('@')[0].replace(/[^a-zA-Z0-9]/g, "");
    var agentName = sheetName.split('
    ').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
    var now = new Date();

    var displayAction = reqType.replace(/request/gi, "Rejected").replace(/leave/gi, "Leave Rejected");
    if (displayAction.indexOf("Rejected") === -1) {
    displayAction += " Rejected";
    }

    var rowData = [now, agentName, displayAction, "System | Remote", agentEmail, "Rejected", "Available", "Supervisor Rejected " + reqType, "", "", "", "", "", "", "Remote Permission Rejected", "", "", "", ""];
    var mainSheet = ss.getSheetByName(ATTENDANCE_SHEET_NAME) || ss.getSheets()[0];
    mainSheet.appendRow(rowData);
    var agentSheet = ss.getSheetByName(sheetName);
    if (agentSheet) agentSheet.appendRow(rowData);

    var cache = PropertiesService.getScriptProperties();
    var liveVal = cache.getProperty("live_agent_" + agentEmail.toLowerCase());
    var parsed = liveVal ? JSON.parse(liveVal) : { name: agentName, email: agentEmail };
    parsed.action = displayAction;
    parsed.status = "Available";
    parsed.timestamp = now.getTime();
    cache.setProperty("live_agent_" + agentEmail.toLowerCase(), JSON.stringify(parsed));

    return { success: true };
    } catch(e) {
    return { success: false, error: e.toString() };
    } finally {
    lock.releaseLock();
    }
    }

/**

  • Manual state override or system broadcast logging.
    */
    function logActionManual(targetEmail, action, note) {
    var lock = LockService.getScriptLock();
    try {
    lock.waitLock(LOCK_TIMEOUT_MS);
    var ss = SpreadsheetApp.openById(SPREADSHEET_ID);
    var now = new Date();
    var rowData = [now, "SYSTEM", action, "Admin Control", targetEmail, "", "Note", "", "", "", "", "", "", "", note, "", "", "", ""];
    var mainSheet = ss.getSheetByName(ATTENDANCE_SHEET_NAME) || ss.getSheets()[0];
    mainSheet.appendRow(rowData);

    var cache = PropertiesService.getScriptProperties();
    var timestamp = now.getTime();
    if (action === "Broadcast") {
    var broadcastObj = {
    type: "popup",
    targets: [targetEmail],
    msg: note,
    time: timestamp,
    duration: 60
    };
    cache.setProperty("latest_broadcast_data", JSON.stringify(broadcastObj));
    } else {
    var liveVal = cache.getProperty("live_agent_" + targetEmail.toLowerCase());
    var parsed = liveVal ? JSON.parse(liveVal) : { name: "Agent", email: targetEmail };
    parsed.action = action;
    parsed.note = note;
    parsed.timestamp = timestamp;
    cache.setProperty("live_agent_" + targetEmail.toLowerCase(), JSON.stringify(parsed));
    }
    return { success: true };
    } catch(e) {
    return { success: false, error: e.toString() };
    } finally {
    lock.releaseLock();
    }
    }

/**

  • Dispatches a broadcast popup or audio notification to target agents.
    */
    function sendBroadcast(type, targets, message, duration) {
    var lock = LockService.getScriptLock();
    try {
    lock.waitLock(LOCK_TIMEOUT_MS);
    var ss = SpreadsheetApp.openById(SPREADSHEET_ID);
    var now = new Date();
    var timestamp = now.getTime();

    var targetsStr = targets.join(", ");
    var rowData = [now, "SYSTEM", "Broadcast (" + type + ")", "Admin Control", targetsStr, "", "Note", "", "", "", "", "", "", "", message, "", "", "", ""];
    var mainSheet = ss.getSheetByName(ATTENDANCE_SHEET_NAME) || ss.getSheets()[0];
    mainSheet.appendRow(rowData);

    var cache = PropertiesService.getScriptProperties();
    var broadcastObj = {
    type: type,
    targets: targets,
    msg: message,
    time: timestamp,
    duration: parseInt(duration, 10) || 60
    };
    cache.setProperty("latest_broadcast_data", JSON.stringify(broadcastObj));
    return { success: true };
    } catch(e) {
    return { success: false, error: e.toString() };
    } finally {
    lock.releaseLock();
    }
    }

/**

  • Superiors force-control action over an agent.
    */
    function remoteAction(agentEmail, actionType) {
    var lock = LockService.getScriptLock();
    try {
    lock.waitLock(LOCK_TIMEOUT_MS);
    var ss = SpreadsheetApp.openById(SPREADSHEET_ID);
    var sheetName = agentEmail.split('@')[0].replace(/[^a-zA-Z0-9]/g, "");
    var agentName = sheetName.split('
    ').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
    var now = new Date();
    var status = (actionType === "Force Logout") ? "Away" : "Available";
    var rowData = [now, agentName, "Admin " + actionType, "System | Remote", agentEmail, "Remote Control", status, "Action by Supervisor", "", "", "", "", "", "", "Remote " + actionType, "", "", "", ""];
    var mainSheet = ss.getSheetByName(ATTENDANCE_SHEET_NAME) || ss.getSheets()[0];
    mainSheet.appendRow(rowData);
    var agentSheet = ss.getSheetByName(sheetName);
    if (agentSheet) agentSheet.appendRow(rowData);

    var cache = PropertiesService.getScriptProperties();
    var liveVal = cache.getProperty("live_agent_" + agentEmail.toLowerCase());
    var parsed = liveVal ? JSON.parse(liveVal) : { name: agentName, email: agentEmail };
    parsed.action = (actionType === "Force Logout") ? "Admin Force Logout" : "Admin Reset to Auto-in";
    parsed.status = status;
    parsed.timestamp = now.getTime();
    parsed.phoneNumber = "";
    cache.setProperty("live_agent_" + agentEmail.toLowerCase(), JSON.stringify(parsed));
    return { success: true };
    } catch(e) {
    return { success: false, error: e.toString() };
    } finally {
    lock.releaseLock();
    }
    }

/**

  • Saves specific team maximum break parameters in cache.
    */
    function saveBreakLimitToProperties(teamKey, value) {
    try {
    var cache = PropertiesService.getScriptProperties();
    cache.setProperty("MaxBreakLimit_" + teamKey.toUpperCase(), value);
    return "Success";
    } catch(e) {
    return "Error: " + e.toString();
    }
    }

/**

  • Pulls active system broadcasts matching the current agent's email/team.
    */
    function getLatestBroadcastForUser(userEmail, userTeam) {
    try {
    var cache = PropertiesService.getScriptProperties();
    var raw = cache.getProperty("latest_broadcast_data");
    if (!raw) return null;
    var data = JSON.parse(raw);

    var userEmailClean = userEmail ? userEmail.toLowerCase() : "";
    var userTeamClean = userTeam ? userTeam.toLowerCase() : "";

    var isTargeted = false;
    for (var i = 0; i < data.targets.length; i++) {
    var tgt = data.targets[i].toLowerCase();
    if (tgt === "all" || tgt === "team:" + userTeamClean || tgt === "user:" + userEmailClean || tgt === userEmailClean) {
    isTargeted = true;
    break;
    }
    }

    return isTargeted ? data : null;
    } catch (e) {
    return null;
    }
    }

/**

  • Uploads medical leave documentation directly to Google Drive.
    */
    function uploadMedicalDoc(base64Data, fileName, agentEmail, dateStr) {
    var lock = LockService.getScriptLock();
    try {
    lock.waitLock(LOCK_TIMEOUT_MS);

    var folderName = "TRU_Workforce_Medical_Proofs";
    var folders = DriveApp.getFoldersByName(folderName);
    var folder = folders.hasNext() ? folders.next() : DriveApp.createFolder(folderName);

    var contentType = base64Data.substring(base64Data.indexOf(":") + 1, base64Data.indexOf(";"));
    var base64Clean = base64Data.substring(base64Data.indexOf(",") + 1);
    var rawBlob = Utilities.newBlob(Utilities.base64Decode(base64Clean), contentType, fileName);

    var file = folder.createFile(rawBlob);
    file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);

    return { success: true, url: file.getUrl() };
    } catch(e) {
    return { success: false, error: e.toString() };
    } finally {
    lock.releaseLock();
    }
    }

/**

  • Saves or updates all weekly shift patterns.
    */
    function saveWeeklySchedules(scheduleDb) {
    var lock = LockService.getScriptLock();
    try {
    lock.waitLock(LOCK_TIMEOUT_MS);
    var cache = PropertiesService.getScriptProperties();
    cache.setProperty("TRU_ShiftManager_Weekly_DB", JSON.stringify(scheduleDb));
    return { success: true };
    } catch(e) {
    return { success: false, error: e.toString() };
    } finally {
    lock.releaseLock();
    }
    }

/**

  • Fetches current weekly scheduling records for the dashboard interface.
    */
    function getWeeklySchedules() {
    try {
    var cache = PropertiesService.getScriptProperties();
    var raw = cache.getProperty("TRU_ShiftManager_Weekly_DB");
    return raw ? JSON.parse(raw) : null;
    } catch(e) {
    return null;
    }
    }

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions