// ========================================== // ОСНОВНОЙ РОУТИНГ И НАСТРОЙКИ CORS // ========================================== function doGet(e) { // Если мы просто открываем ссылку в браузере (нет параметра action) -> показываем интерфейс if (!e || !e.parameter || !e.parameter.action) { return HtmlService.createHtmlOutputFromFile('Index') .setTitle('DIGIS_Skills') .addMetaTag('viewport', 'width=device-width, initial-scale=1') .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL); } // Если интерфейс запрашивает данные -> работаем как API return handleRequest(e); } function doPost(e) { return handleRequest(e); } function handleRequest(e) { var action = e.parameter.action; var result = {}; try { // Извлекаем payload: POST данные или GET параметры var payload = e.postData ? JSON.parse(e.postData.contents) : e.parameter; if (payload.action) action = payload.action; if (action === "getRespondents") { result = getRespondentsList(); } else if (action === "getPMs") { result = getPMsList(); } else if (action === "getAdmins") { result = getAdminsList(); } else if (action === "login") { result = processLogin(payload.id); } else if (action === "getQuestions") { result = getQuestionsList(); } else if (action === "submitAssessment") { result = saveAssessment(payload); } else if (action === "getStats") { result = getAdminStats(); } else { result = { success: false, error: "Неизвестное действие: " + action }; } } catch (err) { result = { success: false, error: err.toString() }; } // Возвращаем JSON с правильным MIME-типом return ContentService.createTextOutput(JSON.stringify(result)) .setMimeType(ContentService.MimeType.JSON); } // ========================================== // БИЗНЕС-ЛОГИКА: ПОЛУЧЕНИЕ СПИСКОВ // ========================================== // Получить список менеджеров и руководителей (Экран 1.1) function getRespondentsList() { var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Users'); if (!sheet) return { success: false, error: "Лист Users не найден" }; var data = sheet.getDataRange().getValues(); var users = []; for (var i = 1; i < data.length; i++) { var role = String(data[i][3]).toLowerCase().trim(); // Колонка D: Должность if (role === 'руководитель' || role === 'менеджер') { users.push({ id: data[i][0], name: data[i][1], department: data[i][2] }); } } return { success: true, respondents: users }; } // Получить список продакт-менеджеров (Экран 2.0) function getPMsList() { var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Users'); if (!sheet) return { success: false, error: "Лист Users не найден" }; var data = sheet.getDataRange().getValues(); var pms = []; for (var i = 1; i < data.length; i++) { var role = String(data[i][3]).toLowerCase().trim(); // Колонка D: Должность if (role === 'продакт-менеджер' || role === 'product manager') { pms.push({ id: data[i][0], name: data[i][1], department: data[i][2] || 'ДРП' }); } } return { success: true, pms: pms }; } // Получить список администраторов (Экран 3.1) function getAdminsList() { var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Users'); if (!sheet) return { success: false, error: "Лист Users не найден" }; var data = sheet.getDataRange().getValues(); var admins = []; for (var i = 1; i < data.length; i++) { var access = String(data[i][6]).toLowerCase().trim(); // Колонка G: Доступ к аналитике if (access === 'да' || access === 'есть') { admins.push({ id: data[i][0], name: data[i][1] }); } } return { success: true, admins: admins }; } // ========================================== // БИЗНЕС-ЛОГИКА: АВТОРИЗАЦИЯ И МАТРИЦА // ========================================== function processLogin(id) { var ss = SpreadsheetApp.getActiveSpreadsheet(); var usersSheet = ss.getSheetByName('Users'); if (!usersSheet) return { success: false, error: "Лист Users не найден" }; var usersData = usersSheet.getDataRange().getValues(); var user = null; // Ищем пользователя по ID for (var i = 1; i < usersData.length; i++) { if (String(usersData[i][0]).trim() === String(id).trim()) { user = { id: usersData[i][0], name: usersData[i][1], role: String(usersData[i][3]).toLowerCase().trim(), analyticsAccess: String(usersData[i][6]).toLowerCase().trim() === 'есть' || String(usersData[i][6]).toLowerCase().trim() === 'да' }; break; } } if (!user) { return { success: false, error: "Пользователь с таким ID не найден" }; } var targets = []; // Если это продакт-менеджер (оценивает себя) if (user.role === 'продакт-менеджер' || user.role === 'product manager') { targets.push({ id: user.id, name: user.name, isSelf: true }); } // Если это руководитель/менеджер (оценивает других) else if (user.role === 'руководитель' || user.role === 'менеджер') { var matrixSheet = ss.getSheetByName('Matrix'); var matrixData = matrixSheet.getDataRange().getValues(); var assignedPMIds = []; // Ищем, кого он должен оценить (Колонка C = ID_Оцениваемого_PM) for (var m = 1; m < matrixData.length; m++) { if (String(matrixData[m][0]).trim() === String(user.id).trim()) { assignedPMIds.push(String(matrixData[m][2]).trim()); } } // Считаем, сколько параметров уже оценено у каждого PM var resultsSheet = ss.getSheetByName('Results'); var completionCounts = {}; if (resultsSheet) { var resultsData = resultsSheet.getDataRange().getValues(); for (var r = 1; r < resultsData.length; r++) { if (String(resultsData[r][1]).trim() === String(user.id).trim()) { var targetId = String(resultsData[r][2]).trim(); // Прибавляем 1 за каждый пройденный параметр completionCounts[targetId] = (completionCounts[targetId] || 0) + 1; } } } // Собираем данные доступных для оценки PM for (var u = 1; u < usersData.length; u++) { var currentId = String(usersData[u][0]).trim(); if (assignedPMIds.indexOf(currentId) !== -1) { // Если оценено меньше 8 параметров, добавляем в список if ((completionCounts[currentId] || 0) < 8) { targets.push({ id: currentId, name: usersData[u][1], group: usersData[u][5] || 'ДРП' }); } } } } return { success: true, user: user, targets: targets }; } // ========================================== // БИЗНЕС-ЛОГИКА: ВОПРОСЫ И СОХРАНЕНИЕ // ========================================== // Получить список вопросов для опросника function getQuestionsList() { var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = ss.getSheetByName('Questions'); if (!sheet) { return { success: false, error: "Лист Questions не найден в базе данных" }; } var data = sheet.getDataRange().getValues(); var list = []; // Ожидаем структуру: A: ID_Вопроса, B: Блок_Компетенции, C: Текст_Индикатора for (var i = 1; i < data.length; i++) { if (data[i][0]) { list.push({ parameterId: data[i][1], // Используем название блока как ID группы parameterName: data[i][1], // Название блока (Параметр) questionId: data[i][0], // ID самого вопроса questionText: data[i][2] // Текст вопроса }); } } return { success: true, questions: list }; } // Сохранение оценки по одному параметру function saveAssessment(payload) { var lock = LockService.getScriptLock(); lock.waitLock(10000); // Защита от одновременной записи (Race Conditions) try { var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = ss.getSheetByName('Results'); if (!sheet) throw new Error("Лист Results не найден"); sheet.appendRow([ new Date(), // Timestamp payload.respondentId, // ID_Респондента payload.targetId, // ID_Оцениваемого_PM payload.parameterId, // ID_Вопроса (в нашем случае - Блок_Компетенции) payload.scores, // Оценка (массив JSON или "Н/В") payload.commentLow || '', // Комментарий_Зона_роста payload.commentHigh || '' // Комментарий_Суперзвезда ]); return { success: true }; } catch (err) { return { success: false, error: err.toString() }; } finally { lock.releaseLock(); } } // Получить простую статистику для Администратора function getAdminStats() { var ss = SpreadsheetApp.getActiveSpreadsheet(); var resultsSheet = ss.getSheetByName('Results'); var usersSheet = ss.getSheetByName('Users'); var totalPMs = 0; var totalAnswers = 0; var totalStars = 0; if (usersSheet) { var usersData = usersSheet.getDataRange().getValues(); for (var i = 1; i < usersData.length; i++) { var role = String(usersData[i][3]).toLowerCase().trim(); if (role === 'продакт-менеджер' || role === 'product manager') { totalPMs++; } } } if (resultsSheet) { var resultsData = resultsSheet.getDataRange().getValues(); totalAnswers = Math.max(0, resultsData.length - 1); // Минус заголовок for (var i = 1; i < resultsData.length; i++) { var scoresStr = String(resultsData[i][4]); // Колонка E: Оценка // Считаем количество раз, когда ставили звезду (балл 3) if (scoresStr.indexOf('3') !== -1) { totalStars++; } } } return { success: true, totalPMs: totalPMs, totalAnswers: totalAnswers, totalStars: totalStars }; }