Skip to content

cod gs #651

@mohamedsami-ux

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions