Google Apps Script:
дрессированные коды

Коллекция скриптов для Google Apps Script с подробными пояснениями:
готовые кейсы - бери и пользуйся!
12 мая 2020, автор: Елена Позднякова
Эта статья периодически пополняется новыми кейсами и скриптами.
Задать вопрос или оставить комментарий можно здесь:
Оглавление

Пользовательское меню

для Гугл Таблицы, Гугл Документа, Гугл Формы
Как это работает:
С помощью Google Apps Script можно создать пользовательское меню для:
  • Гугл Таблицы,
  • Гугл Документа,
  • Гугл Формы,
  • Гугл Слайдов.
Доступно только из Контейнерного скрипта, который встроен в документ.
Меню Гугл Таблицы будет видеть только тот, кто имеет право редактирования (пользователи с доступом на просмотр меню не видят).
Меню Гугл Формы будет видеть тот, кто редактирует форму, а не заполняет её
.
1
Вот так выглядит простейшее пользовательское меню для Гугл Таблицы (на две функции):
Скрипт:

function onOpen() {
  var ui = SpreadsheetApp.getUi();
  ui.createMenu('МОЁ МЕНЮ')
    .addItem('Запустить функцию 1', 'myFunction1')
    .addSeparator()
    .addItem( 'Запустить функцию 2', 'myFunction2')
    .addToUi();
}
2
А здесь пользовательское меню для Гугл Документа, которое включает подменю:
Скрипт:

function onOpen() {
  var ui = DocumentApp.getUi();
  // или SpreadsheetApp или FormApp.
  ui.createMenu('ПОЛЬЗОВАТЕЛЬСКОЕ МЕНЮ')
  .addItem('Пункт меню1', 'function1')
  .addItem('Пункт меню2', 'function2')
  .addSeparator()
  .addSubMenu(ui.createMenu('Подменю')
              .addItem('Пункт подменю 1', 'function3')
              .addItem('Пункт подменю 2', 'function4'))
  .addToUi();
}
Некоторые использованные функции и триггеры:
1
Функция onOpen является простым триггером. Выполняется при открытии документа.
Авторизация пользователя для запуска этой функции не требуется, поэтому возможности её ограничены лишь несколькими действиями.
В частности, для onOpen доступно создание пользовательского меню документа, таблицы, слайдов и форм в контейнерном скрипте.

Описание простых триггеров на сайте разработчика.
Описание пользовательских меню на сайте разработчика.
2
Функция addItem добавляет элемент меню.

addItem(caption, functionName)
caption - название элемента меню
finctionName - функция, которую запускает элемент меню


Описание на сайте разработчика.
~

Автоматическое письмо после заполнения формы

Видео:
CRM на Google Таблицах. Кейс 1: Письмо клиенту, заполнившему форму
Как это работает:
1
Клиент заполняет Гугл Форму:
2
Ответы автоматически загружаются в Гугл Таблицу:
3
Клиенту автоматически отправляется письмо:
Скрипт:
Функция sendEmailToClient отправляет письмо клиенту, заполнившему форму, на указанный в форме email.
Контейнерный скрипт для Гугл Таблицы.

function sendEmailToClient() {
  
  //Текущая Гугл Таблица записана в переменную:
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  
  //Лист текущей таблицы с именем "Ответы на форму (1)", в который загружаются ответы из формы, записан в переменную:
  var listAnswers = ss.getSheetByName("Ответы на форму (1)"); 
  
  //Получен email клиента из ячейки с адресом: последняя строка, 4 ряд, - и записан в переменную:
  var clientEmail = listAnswers.getRange(listAnswers.getLastRow(),4).getValue();
  
  //Тело письма, отформатированное в html-разметке, записано в переменную:
  var body = 
      'Здравствуйте! <br/> Благодарим за заявку на онлайн-курс <strong>Power BI!</strong><br/> ' +
        'В ближайшее время наш менеджер свяжется с вами по указанному телефону.<br/>' +
          'Подробности о системе бизнес-аналитики Power BI по ' +
            '<a href = "https://finver.ru/blog/epoha-power-bi">ссылке.</a><br/>' +
              '<br/><br/>' + 
                'С уважением, <br/> онлайн-школа Codelab';
  //В описании кода приведены дополнительные пояснения относительно html-разметки
  
  //Отправлено письмо клиенту (отправителем будет тот, кто запускает скрипт):
  //Для отправки используется функция sendEmail (по-правильному: метод класса MailApp)
  //Ссылка на подробную информацию о методе в описании к коду
  
MailApp.sendEmail(clientEmail, "Ваша заявка получена",'',{htmlBody: body});
  
}
Некоторые использованные функции и триггеры:
1
Триггер, который срабатывает при отправке формы, настраивается в разделе "Триггеры текущего проекта":
Как альтернативный вариант, можно прописать триггер с помощью кода:

var form = FormApp.openById('1234567890abcdefghijklmnopqrstuvwxyz');
ScriptApp.newTrigger('myFunction')
    .forForm(form)
    .onFormSubmit()
    .create();
2
Функция getLastRow (получить порядковый номер последнего ряда в диапазоне), метод класса Range (range = диапазон).
Описание на сайте разработчика.
3
Функция sendEmail (отправить письмо), метод класса MailApp.
Описание на сайте разработчика.
4
Пояснения относительно html-разметки в письме:
Фрагмент кода:

//Тело письма, отформатированное в html-разметке, записано в переменную:​

var body = 

      'Здравствуйте! <br/> Благодарим за заявку на онлайн-курс <strong>Power BI!</strong><br/> ' +
        'В ближайшее время наш менеджер свяжется с вами по указанному телефону.<br/>' +
          'Подробности о системе бизнес-аналитики Power BI по ' +
            '<a href = "https://finver.ru/blog/epoha-power-bi">ссылке.</a><br/>' +
              '<br/><br/>' + 
                'С уважением, <br/> онлайн-школа Codelab';
Использование html-разметки в письме не обязательно, это всего лишь дополнительный атрибут.

Если в письме нет html разметки, то будет отправлен слепой текст:
  • без отступов,
  • без выделения жирным шрифтом,
  • без кликабельных ссылок.

Текст письма относится к типу данных текст, поэтому он должен быть заключен в кавычки: "двойные" или 'одинарные'.
Кавычки обязательны независимо от того, используется html или нет.

Тескт письма разделен на блоки, помещенные в кавычки и соединенные оператором плюс ('текст ' + 'текст'). Это сделано для удобства чтения кода, чтобы обеспечить возможность переноса строк.
Так тоже правильно ('текст текст'), но если перенести часть текста на следующую строку, не закрывая кавычки и не добавляя оператор плюс (+), то код не понимает, что продолжается текст и воспринимает перенесенную часть текста, как новую команду.

Поэтому перенос текстовых строк в коде выглядит так:
"текст текст текст " +
"текст текст"


Альтернативный равнозначный вариант кода выглядит так:
"текст текст текст текст текст"

Причем, данный перенос строки (в первом варианте), повторюсь, нужен только для удобства чтения кода, но в самом письме переноса строки не будет.

Примечание: если использовать для выделения текста обратные кавычки `` - то внутри можно переносить строчки сколько угодно раз.

Для того, чтобы добавить в письмо перенос строки, нужно в конце строки добавить тег <br/>. Это мы уже плавно перешли к тегам html-разметки.

Два бр подряд: <br/><br/> - это 2 отступа.

Чтобы текст получился жирным, его нужно заключить в парные теги <strong></strong>.
Вот так:
<strong>жирный текст</strong>

Чтобы добавить ссылку, её нужно поместить в парный тег <a></a>, и добавить к первому тегу <a> атрибут со ссылкой: href="здесь ссылка".

Вот так это выглядит:
<a href="https://megabyte.ga/"> текст ссылки </a>
Так выглядит письмо, созданное с помощью приведенного выше кода:
~

Три варианта письма

продолжение предыдущего кейса
Видео:
CRM на Google Таблицах. Кейс 2. Три варианта письма.Условные операторы if, else if, else
Как это работает:
В зависимости от того, какой курс клиент выбирает в заявке:

  • Power BI
  • Google Apps Script
  • Javascript

отправлять ему три разных варианта письма.

Есть еще и четвертый вариант, если клиент выбрал больше одного курса или не выбрал ни один.
Скрипт:

/*
Контейнерный скрипт. При заполнении Гугл Формы ответы автоматически загружаются в Гугл Таблицу и запускается триггер.
Триггеры установлены в разделе "Триггеры текущего проекта".
*/



//Функция отправляет  письмо клиенту, заполнившему форму, на указанный в форме Email. В зависимости от результатов формы есть 4 варианта письма
function sendEmailToClient() {
  
  //Текущая Гугл Таблица записана в переменную:
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  
  //Лист текущей таблицы с именем "Ответы на форму (1)", в который загружаются ответы из формы, записан в переменную:
  var listAnswers = ss.getSheetByName("Ответы на форму (1)"); 
  
  //Получен email клиента из ячейки с адресом: последняя строка, 4 ряд, - и записан в переменную:
  var clientEmail = listAnswers.getRange(listAnswers.getLastRow(),4).getValue();
  
  //Выбранный курс из таблицы с ответами записан в переменную
  var kurs = listAnswers.getRange(listAnswers.getLastRow(),2).getValue();
  
  //Созданы 4 разных тела письма:
  var bodyForPower = 
      'Здравствуйте! <br/> Благодарим за заявку на онлайн-курс <strong>Power BI!</strong><br/> ' +
        'В ближайшее время наш менеджер свяжется с вами по указанному телефону.<br/>' +
          'Подробности о системе бизнес-аналитики Power BI по ' +
            '<a href = "https://finver.ru/blog/epoha-power-bi">ссылке.</a><br/>' +
              '<br/><br/>' + 
                'С уважением, <br/> онлайн-школа Codelab';
  
  var bodyForJava = 
      'Здравствуйте! <br/> Благодарим за заявку на онлайн-курс <strong>Javascript!</strong><br/> ' +
        'В ближайшее время наш менеджер свяжется с вами по указанному телефону.<br/>' +
          '<br/><br/>' + 
            'С уважением, <br/> онлайн-школа Codelab';
  
  
  var bodyForGas = 
      'Здравствуйте! <br/> Благодарим за заявку на онлайн-курс <strong>Google Apps Script!</strong><br/> ' +
        'В ближайшее время наш менеджер свяжется с вами по указанному телефону.<br/>' +
          '<br/><br/>' + 
            'С уважением, <br/> онлайн-школа Codelab';
  
  var bodyForOther = 
      'Здравствуйте! <br/> Благодарим за заявку! <br/> ' +
        'В ближайшее время наш менеджер свяжется с вами по указанному телефону.<br/>' +
          '<br/><br/>' + 
            'С уважением, <br/> онлайн-школа Codelab';
  
  var body;
  
  if (kurs == 'Power BI')   {body = bodyForPower}
  
  else if (kurs == 'Javascript')   {body = bodyForJava}
  
  else if (kurs == 'Google Apps Script')   {body = bodyForGas} 
  
  else {body = bodyForOther};
  
  
  //Отправлено письмо клиенту (отправителем будет тот, кто запускает скрипт):
  //Для отправки используется функция sendEmail, метод класса MailApp

  MailApp.sendEmail(clientEmail, "Ваша заявка получена",'',{htmlBody: body});
  
}

~

Скрипт распределяет заявки и назначает менеджеров

продолжение предыдущего кейса
Видео:
CRM [на Google Таблицах].кейс 3: {Скрипт распределяет заявки и назначает менеджеров}
Скрипт из видео (Гугл Таблица)
Файл → Создать копию.
В копии: Инструменты → Редактор скриптов
Скрипт:

//Функция - при заполнении формы назначить менеджера из списка по порядку и отправить менеджеру письмо с информацией из формы:
function setCurrentManager() {

//Текущая Гугл Таблица записана в переменную:
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  
  //Лист текущей таблицы с именем "Ответы на форму (1)", в который загружаются ответы из формы, записан в переменную:
  var listAnswers = ss.getSheetByName("Ответы на форму (1)"); 
  
  
  //Лист текущей таблицы с именем "Список менеджеров" записан в переменную:
  var listManagers = ss.getSheetByName("Список менеджеров"); 
  
  
  //Создаем переменную "Текущий менеджер" и присваиваем ей ЗНАЧЕНИЕ ПО УМОЛЧАНИЮ (первый менеджер из списка на листе "Список менеджеров": 2 ряд, 1 столбец
  var currentManager = listManagers.getRange(2, 1).getValue();
  
  
  
  /*
  НАЧАЛО БЛОКА "Три условия"...
  
  При выполнении определенных условий, значение переменной currentManager ЗАМЕНИМ:
  а именно, определим менеджера предыдущей сделки (previousManager) на листе "Ответы на форму (1)", 
  найдем это значение на листе "Список менеджеров" и возьмем следующего по списку.
  
  Условия для замены:
  УСЛОВИЕ 1: Значение previousManager на листе "Ответы на форму (1)" не должно быть пустым
  УСЛОВИЕ 2: Значение previousManager должно быть найдено на листе "Список менеджеров"
  УСЛОВИЕ 3: Значение в следующем ряду после найденного previousManager на листе "Список менеджеров" не должно быть пустым.
  
  Если хотя бы одно условие не выполняется, то переменная currentManager сохраняет значение по умолчанию.
  */
  
    
  //Создаем переменную "Предыдущий менеджер" и присваиваем ей значение менеджера из предыдущей сделки:
  var previousManager = listAnswers.getRange(listAnswers.getLastRow(),6).offset(-1, 0).getValue();
  
  /*
  Объявляем переменную firstOccurrence (в переводе "Первое появление") - это ячейка на листе "Список менеджеров", в которой может быть найдено значение previousManager.
  Если значение НАЙДЕНО, то firstOccurrence будет иметь значение: Range, тип: object.
  Если значение НЕ НАЙДЕНО, то firstOccurrence будет иметь значение: null, тип: object.
  Пока значение переменной НЕ ПРИСВОЕНО, она имеет значение: undefined, тип: undefined.
  */
  var firstOccurrence;
  
  
  //Создаем поисковый запрос для листа "Список менеджеров" (найдем предыдущего менеджера, чтобы взять следующего по списку)
  var textFinderPreviousManager = listManagers.createTextFinder(previousManager);
  
  /* 
  УСЛОВИЕ 1
  Запускаем поисковый запрос с помощью встроенной функции findNext()
  Важно! Запускать функцию, использующую поисковый запрос, можно только если текст поискового запроса не пустой,иначе скрипт не выполнится,
  поэтому, перед запуском функции поставим условие, что запрос не пустой: 
  */
  
  if (previousManager !=''){firstOccurrence = textFinderPreviousManager.findNext()};
  
  /*
  После этого шага firstOccurrence принимает одно из 3-х значений:
  значение: undefined, тип: undefined (если функция findNext не запускалась);
  значение: null, тип: object (если значение не было найдено);
  значение: Range, тип: object (если значение было найдено).
  
  
  УСЛОВИЕ 2
  Нам нужно предпринимать дальнейшие действия только в последнем случае: если значение previousManager было найдено на листе "Список менеджеров".
  Если нашли,то создадим новую переменную valueFromNextRow, возьмем значение из следующей строки.
  */
  
  if (firstOccurrence == 'Range'){
    var valueFromNextRow = firstOccurrence.offset(1, 0).getValue();
    
    //УСЛОВИЕ 3 (если значение следующей строки не пустое, заменим на него текущего менеджера)
    if (valueFromNextRow != ''){currentManager = valueFromNextRow}
  }
  
 //...конец БЛОКА 3 УСЛОВИЯ
  
  
  
   
  
  //Записываем текущего менеджера в графу "Назначен менеджер" к текущей заявке:
  listAnswers.getRange(listAnswers.getLastRow(),6).setValue(currentManager);
  
  
  
  //Определяем email текущего менеджера: 
  
  //Создаем поисковый запрос со значением текущего менеджера
  var textFinderCurrentManager = listManagers.createTextFinder(currentManager);
  
  //Запускаем поисковый запрос с помощью встроенной функции findNext() и полученное значение записываем в переменную
  //Функцию можно запускать без условий, так как поисковый запрос точно не пустой
  var firstOccurrenceCurrentManager = textFinderCurrentManager.findNext();
  
  //Берем значение из соседней ячейки и записываем в переменную: 
  var currentManagerEmail = firstOccurrenceCurrentManager.offset(0, 1).getValue();
  
 
  
  
 
  
  //Запишем информацию из формы, которую заполнил клиент, в переменные для отправки письма менеджеру
  var timeOfForm = listAnswers.getRange(listAnswers.getLastRow(), 1).getValue();
  var kurs = listAnswers.getRange(listAnswers.getLastRow(), 2).getValue();
  var phone = listAnswers.getRange(listAnswers.getLastRow(), 3).getValue();
  var email = listAnswers.getRange(listAnswers.getLastRow(), 4).getValue();
  var addInformation = listAnswers.getRange(listAnswers.getLastRow(), 5).getValue();
  
  //Шаблон тела письма менеджеру в html-разметке:
  var body = 'Привет, ' + currentManager + '! <br/>'+
    'Поступила форма от клиента: <br/>'+
    '<table>' +
      '<tr><td>Время: </td><td>' + timeOfForm + '</td></tr>' + 
      '<tr><td>Курс: </td><td>' + kurs + '</td></tr>' + 
      '<tr><td>Номер телефона: </td><td>' + phone + '</td></tr>' + 
      '<tr><td>Email: </td><td>' + email + '</td></tr>' + 
      '<tr><td>Дополнительная информация: </td><td>' + addInformation + '</td></tr>' + 
      '</table>'

  
  
  //Отправляем на email текущего менеджера сооьщение о том, что ему передана в работу заявка
  MailApp.sendEmail(currentManagerEmail, 'Поступила заявка от клиента','',{htmlBody: body} );
  
  
}


~

Установить дату и время изменения

для Гугл Таблицы
Как это работает:
1
Комментарий к ячейке с датой и временем последнего изменения, установленный с помощью Google Apps Script:
Скрипт:

function onEdit(e) {
  
  var range = e.range;
  
   range.setNote('Последнее изменение: ' + new Date().toLocaleString('ru'))
  }

2
Такой же комментарий, но с условием: только для колонки №2
Скрипт:

function onEdit(e) {
  
  var range = e.range;
  
  if (range.getColumn() == '2')
    
  {range.setNote('Последнее изменение: ' + new Date().toLocaleString('ru'))
  }
}

3
Установить дату и время изменения в ячейку справа
Скрипт:

function onEdit(e) {
  
// Это контейнерный скрипт:
// e = SpreadsheetApp.getActiveRange()
  
  var range = e.range,
      row =   range.getRow(),
      column =   range.getColumn();
  
  
  range.offset(0, 1).setValue(new Date().toLocaleString('ru'))
  
}

Некоторые использованные функции и триггеры:
1
Функция onEdit является простым триггером. Выполняется при редактировании ячейки.

Описание триггера на сайте разработчика.

Синтаксис:
function onEdit(e) {
команда
}

onEdit - это зарезервированное имя функции, для триггера нужно использовать только его;
e - это не обязательный аргумент, который передает информацию об объекте события.

Перечень объектов, которые содержатся в e, можно посмотреть здесь.
Например:
  • range - ячейка или диапазон ячеек, который был изменен (обратиться к диапазону: e.range);
  • value - новое значение для ячейки (доступно только для редактирования одиночной ячейки, обратиться к значению: e.value);
  • sourse - Гугл Таблица (обратиться: e.sourse).
~

Вспомогательные функции

Проверить значение и тип переменной х
Посмотреть результат можно в редакторе скриптов: Вид - Журналы

Logger.log ('переменная: '+ х + ', тип: '+ typeof х)

// [20-05-17 09:19:35:584 MSK] переменная: Маша, тип: string


~