// ==========================================
// ОСНОВНОЙ РОУТИНГ И НАСТРОЙКИ 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
};
}