Справка

Примеры скриптов

Скопировать значение одного поля в другое

Копируем значение из поля Поле-источник в поле Текстовое поле.

form.onChange(['Поле-источник'], true)
  .setValue('Текстовое поле', state => {
    const [value] = state.changes;

    if (!value)
      return null;

    return value.text;
  });

Вычислить сумму (разницу, процент) нескольких полей

В этом примере скрипт прибавляет премию сотрудника к его окладу и рассчитывает НДФЛ от полученной суммы. Затем вычитает НДФЛ из этой суммы и выводит итоговую выплату за месяц.

form.onChange(['Оклад', 'Премия'])
  .setValues(['НДФЛ (13%)', 'Итого к выплате за месяц'], state => {
    const [salary, bonus] = state.changes;

    if (!salary || !bonus)
      return null;

    const ndflTax = (salary.value + bonus.value) * 0.13;
    const result = (salary.value + bonus.value) - ndflTax;
    return [ndflTax, result];
  });

Вывести значение колонки справочника

Здесь Справочник — название поля с вашим справочником, Колонка — название колонки справочника, а Текст — поле типа Текст, в котором будет сохранено значение.

form.onChange(['Справочник'])
  .setValue('Текст', state => {
    const [item] = state.changes;

    if (!item || !item.columns)
      return null;

    return item.columns['Колонка'];
  });

Выбор значения из справочника по полю с типом Контакт

Иногда требуется подставлять в заявку ФИО сотрудника в родительном падеже. Простой пример — заявление на отпуск. Чтобы Pyrus делал это автоматически, создаём справочник со списком сотрудников и колонкой с их ФИО в родительном падеже. И подключаем к форме такой скрипт:

const catalogPromise = form.getCatalog(ID_справочника);

form.onChange(['Отпускник'])
  .setValueAsync('От (ФИО):', async state => {
    const [contactField] = state.changes;
    const catalogItems = await catalogPromise;

    if (!catalogItems || !contactField || !contactField.person_id)
      return null;

    const filteredRow = catalogItems
       .find(item => item.columns['Сотрудник'] == `${contactField.first_name} ${contactField.last_name}`);

    return filteredRow ? filteredRow.columns['ФИО Род'] : null;
  });

В этом примере:

  • Отпускник — название поля типа Контакт;
  • Сотрудник и ФИО Род — название колонок справочника, в котором мы ищем значение по выбранному полю;
  • От (ФИО): - поле типа Текст, в котором сохраняем найденное значение из справочника.

Валидация: дата окончания не раньше даты начала

form.onChange(['Дата начала', 'Дата окончания'])
  .validate('Дата окончания', state => {
    const [start, end] = state.changes;

    if (!start || !end)
      return null;

    if (start.date && end.date && start.date >= end.date)
      return {
        errorMessage: 'Не может быть раньше даты начала'
      };

    return null;
  });

Валидация: заполнено хотя бы одно поле

form.onChange(['Эл. почта', 'Телефон'], true)
  .validate('Эл. почта', state => {
    const [email, phone] = state.changes;

    const emailIsEmpty = !email || !email.text;
    const phoneIsEmpty = !phone || !phone.text;

    if (emailIsEmpty && phoneIsEmpty)
      return {
        errorMessage: 'Заполните электронную почту или телефон'
      };

    return null;
  });

Перенос крайнего срока в зависимости от приоритета

C возможностью установки произвольного срока.

form.onChange(['Высокий приоритет', 'Перенос даты'])
  .setValue('Срок', state => {
    const [checkmark, customDue] = state.changes;

    if (customDue && customDue.date)
      return {date: customDue.date};

    if (!checkmark)
      return null;

    if (checkmark.checked)
      return {days_from_create: 1};

    return {days_from_create: 3};
  });

Установка срока за N дней до указанной даты

В этом примере в зависимости от указанной при заполнении даты срок будет автоматически устанавливаться за семь дней до этой даты.

form.onChange(['Дата'])
  .setValues(['Срок'], state => {
    const [dateValue] = state.changes;

    if (!dateValue)
      return null;

    const dueDate = new Date(dateValue.date + 'T10:00:00Z');
    const daysBeforeDate = 7;
    dueDate.setDate(dueDate.getDate() - daysBeforeDate);

    return [{date: dueDate.toDateString()}];
  });

Автоматическая смена статусов задачи при редактировании полей

Скрипт подойдёт тем, кто в рамках задачи работает с партнёрами. В нашем примере статус задачи будет меняться на «В работе», если партнёр назначил ответственного в поле Ответственный за решение.

// Индивидуальный номер организации, которая редактирует форму.
// Чтобы узнать ID, напишите на support@pyrus.com.
const orgId = 111111;

form.onChange([], true)
  .setStatus(state => {
    if (state.commenter && state.commenter.organization_id !== orgId)
      return {choice_name: 'В работе'};
  });

form.onChange(['Ответственный за решение'])
  .setStatus(state => {
    const [currVal] = state.changes;
    const [prevVal] = state.prev;

    if (!prevVal && currVal)
      return {choice_name: 'В работе'};
  });

Автоматическое заполнение срока SLA

Укажите в скрипте своё время решения и названия полей. Вместо числовых констант вы можете использовать переменные или формулы для расчёта времени решения. При изменении в заявке полей Приоритет и Клиент скрипт будет выполняться и устанавливать один из указанных в нём сроков.

form.onChange(['Приоритет', 'Клиент'])
  .setValue('Срок', state => {
    const [priority, client] = state.changes;

    if (!priority || !priority.columns || !client || !client.columns)
      return null;

    const tariff = client.columns['Тариф'];
    const priorityValue = priority.columns['Приоритет'];
    const hours = getSlaHoursByTariffAndPriority(tariff, priorityValue);

    if (hours === undefined)
      return null;

    return {hours_from_create: hours};
  });

function getSlaHoursByTariffAndPriority(tariff, priority) {
  switch (tariff) {
    case 'Базовый':
      switch (priority) {
        case 'Низкий' : return 64;
        case 'Средний': return 32;
        case 'Высокий': return 16;
      }
    case 'Стандарт':
      switch (priority) {
        case 'Низкий' : return 32;
        case 'Средний': return 16;
        case 'Высокий': return 8;
      }
    case 'Премиум':
      switch (priority) {
        case 'Низкий' : return 16;
        case 'Средний': return 8;
        case 'Высокий': return 4;
      }
    default:
      return undefined;
  }
}

Фильтрация справочника

Этот скрипт позволяет фильтровать список городов по выбранному региону. Другой пример — при выборе отдела показывать только статьи расходов, относящиеся к этому отделу.

const catalogPromise = form.getCatalog("Города по регионам");

form.onChange(["Регион"]).setFilterAsync("Город", async state => {
  const [region] = state.changes;
  const catalogItems = await catalogPromise;

  if (!catalogItems || !region || !region.columns)
    return null;

  const regionCol = region.columns["Регион"];
  const filtered = catalogItems
    .filter(item => item.columns["Регион"] === regionCol)
    .map(item => item.columns["Город"]);

  return filtered.length > 0
    ? {
        values: filtered
      }
    : null
});

Установка даты в рабочих днях

Этот скрипт позволяет задавать дату с учётом выходных дней. Даты выходных и рабочих дней берутся из справочника.

// Получаем массив дат из справочника "Даты перенос" со столбцами "Даты перенос", "Тип"
const catalogPromise = form.getCatalog("Даты перенос").then(items => {
  if (!items)
    return null;

  return {
    holidayItems: items
      .filter(item => item.columns["Тип"] == 'Выходной')
      .map(item => item.columns["Даты перенос"])
      .map(date => new Date(date + 'T00:00:00Z')),
    workingItems: items
      .filter(item => item.columns["Тип"] == 'Рабочий')
      .map(item => item.columns["Даты перенос"])
      .map(date => new Date(date + 'T00:00:00Z'))
  }
});

// Поиск даты в массиве
function containsDate(date, array) {
  for (let i = 0; i < array.length; i++) {
    if (date.getTime() == array[i].getTime()) {
      return true;
    }
  }
  return false;
}

// Проверить является ли дата выходным днем
function isWeekend(date) {
  return date.getDay() == 0 || date.getDay() == 6;
}

// Количество и список выходных и праздничных дней
form.onChange(['Дата','Дата окончания'])
  .setValuesAsync(['Список выходных дней', 'Число выходных между датами'], async state => {
    const [startDateField, endDateField] = state.changes;

    if (!startDateField || !endDateField)
      return null;

    const startDate = new Date(startDateField.date);
    const endDate = new Date(endDateField.date);

    const {holidayItems, workingItems} = await catalogPromise;

    const textArray = [];
    const monthNames = 'января,февраля,марта,апреля,мая,июня,июля,августа,сентября,октября,ноября,декабря'.split(',');

    while (startDate <= endDate) {
      if ((isWeekend(startDate) && !containsDate(startDate, workingItems))
        || containsDate(startDate, holidayItems)) {
        const dateText = startDate.getDate().toString() + ' ' +
          monthNames[startDate.getMonth()] + ' ' +
          startDate.getFullYear().toString() + ' г.';
        textArray.push(dateText);
      }

      startDate.setDate(startDate.getDate() + 1);
    }

    let text = '';
    if (textArray.length == 1) {
      text = textArray[0];
    }
    else if (textArray.length > 1) {
      text = textArray.slice(0, textArray.length - 1).join(', ') + ' и ' + textArray[textArray.length - 1];
    }

    return [text, textArray.length];
  });

// Установка даты в рабочих днях
form.onChange(['Дата','Кол-во дней'])
  .setValuesAsync(['Дата + Кол-во рабочих дней'], async state => {
    const [startDateField, daysNum] = state.changes;

    if (!startDateField || !daysNum)
      return null;

    const startDate = new Date(startDateField.date);
    const endDate = new Date(startDateField.date);
    endDate.setDate(endDate.getDate() + daysNum.value);

    const {holidayItems, workingItems} = await catalogPromise;

    do {
      if ((isWeekend(startDate) && !containsDate(startDate, workingItems))
        || containsDate(startDate, holidayItems)) {
        endDate.setDate(endDate.getDate() + 1);
      }

      startDate.setDate(startDate.getDate() + 1);
    } while (startDate <= endDate);

    return [{date: endDate.toDateString()}];
  });

Фильтрация поля типа Форма

В этом примере скрипт позволяет выполнить фильтрацию полей типа Форма, чтобы отсортировать заявку, соответствующую запросу.

form.onChange(["Текст"]).setFilter("Bug 143331863", state => {
  const [region] = state.changes;
  if (!region || !region.value)
    return null;

  return {
    filters: [
      {fieldCode: "code_field", value: 3},
      {fieldName: "name_field", value: "12"}
    ]
  }
});

Сделать поле невидимым

В этом примере поле Оптовая скидка скрыто, если значение в поле Количество не превышает 99.

form.onChange(['Количество'], true)
  .setVisibility(['Оптовая скидка'], state => {
    const [quantity] = state.changes;

    if (!quantity)
      return null;

    const num = quantity.value;
    return num > 99;
  });

Сделать поле невидимым, начиная с определённого этапа

В этом примере поле Клиент будет скрыто, начиная со второго этапа.

form.onChange([''], true)
  .setVisibility(['Клиент'], state => {
    return state.currentStep < 2;
  });

Скрываем поле, если письмо пришло не от заданного адреса

Если письмо пришло не от mail@example.com — скрываем поле «Тип заявки».

form.onChange(['Email'], true)
  .setVisibility(['Тип заявки'], state => {
    const [email] = state.changes;
    return email && email.text == 'mail@example.com';
  });

Подсчёт дней между датами

Скрипт считает количество дней между указанными датами. Также он показывает, сколько в указанном интервале праздничных дней — для этого нужно использовать отдельный справочник таких дней.

let catalog = null;
form.getCatalog("Праздничные дни").then(items => {
    catalog = items
});

form.onChange(['start_date', 'end_date'], true)
    .setValuesAsync(['days', "holidays"], async state => {
        const [start, end] = state.changes;

        if (!start.date || !end.date)
            return [null, null];

        let days = daysBetween(
            new Date(start.date),
            new Date(end.date)
        ) + 1;
        let hol = 0;
        const startdate = new Date(start.date);
        const enddate = new Date(end.date);
        let catalog_element = null;
        let catalog_Date = null;
        for (let i = 0; i < catalog.length; i++) {
            catalog_element = catalog[i].columns["День"]
            if (!catalog_element) continue;

            const day = catalog_element.slice(3,5);
            const month = catalog_element.slice(0,2);
            const year = catalog_element.slice(6,10);
            catalog_Date = new Date(`${day}/${month}/${year}`);
            console.log(catalog_Date, startdate <= catalog_Date && catalog_Date <= enddate);
            
            if (startdate <= catalog_Date && catalog_Date <= enddate) hol += 1;
        }

        return [days, hol];
    });

function daysBetween(d1, d2) {
    const msInDay = 1000 * 60 * 60 * 24;
    return Math.floor((d2.getTime() - d1.getTime()) / msInDay);
}

Подсчёт суммы в строках таблицы и скидки

Скрипт считает сумму значений в заполненной таблице и применяет указанную скидку.

form.onChange(["product", "quantity", "discount"])
    .setValues(["price_for_one", "total_price"], state => {
        const [product, quantity, discount] = state.changes;

        if (!product?.columns) return [null, null];

        const discount_rate = discount?.value
            ? discount.value / 100
            : 0;
        const item_price = product.columns["Цена"]
            ? Number(product.columns["Цена"]) * (1 - discount_rate)
            : 0;
        return quantity?.value ? [item_price, item_price * quantity.value] : [item_price, 0];
    });

Фильтрация полей в зависимости от поля типа Галочка

Скрипт показывает скрытые поля типа Справочник», если поставить галочку.

let table_catalog = null;
const catalog_id = 200743;
form.onChange(["VIP"], true)
    .setFilterAsync("table", async state => {
        const [vip] = state.changes;

        if (!table_catalog) await form.getCatalog(catalog_id ).then(items => table_catalog = items);
        let filtered = table_catalog.filter(x => x.columns["VIP"] == 0 || vip.checked).map(x => x.columns["id"]);

        return {values: filtered};
    });

Фильтрация полей по типу Форма

Скрипт показывает скрытые поля типа Форма в зависимости от указанного сотрудника.

form.onChange(["manager"], true)
    .setFilter("contragent", state => {
        const [manager] = state.changes;

        if (!manager || !manager.person_id) return null;

        return {
            filters: [{
                fieldName: "Менеджер",
                value: manager.person_id
            }]
        }
    }); 

Проверяем правильность заполнения текстового поля

Скрипт проверяет, чтобы текстовое поле было заполнено только заглавными латинскими буквами.

form.onChange(["fio_text"], true)
    .validate("fio_text", state => {
        const [text] = state.changes;

        if (!text?.text) return null;

        const reg = new RegExp(/^[A-Z'\s]+$/);
        if (!reg.test(text.text))
            return {
                errorMessage: "Обнаружены недопустимые символы"
            };
    });

Запрет удаления строк в таблице

Скрипт не даёт удалить заполненные строки таблицы в форме.

let exist_table = null;
form.onChange(["no_delete_table"], true)
    .validate("no_delete_table", state => {
        const [table] = state.changes;

        if (state.currentStep == 0) return null;

        if (!exist_table) {
            exist_table = [];
            if (table?.value) {
                for (let i in table.value) {
                    exist_table.push(table.value[i].Id);
                }
            }
            return null;
        }

        if (exist_table.length == 0) return null;

        if (!table?.value || table.value.length == 0)
            return {
                errorMessage: "Нельзя удалять строки таблицы. Перезагрузите страницу"
            };

        for (let i in exist_table) {
            if (!table.value.find(x => x.Id == exist_table[i]))
                return {
                    errorMessage: "Нельзя удалять строки таблицы. Перезагрузите страницу"
                };
        }

        return null;
    });

Ограничение на добавление файлов определённого типа

Скрипт позволяет добавить в поле только PDF-файлы и никакие другие.

form.onChange(["files"])
    .validate("files", state => {
        const [files] = state.changes;
        const correct_extensions = ["pdf"];
        if (!files?.files) return null;

        for (let i in files.files) {
            let extension = files.files[i].name.split(".").slice(-1);
            if (correct_extensions.indexOf(extension[0]) < 0)
                return {
                    errorMessage: "Можно приложить только pdf"
                };
        }

        return null;
    });

Отображение полей в зависимости от условий

Скрипт отображает выбранные поля формы в зависимости от выбранного значения справочника.

form.onChange(["problem_type"], true)
    .setVisibility(["ip_address"], state => {
        const [problem] = state.changes;

        return (problem?.columns?.["IP пользователя"] != "");
    });

Проверка оставшихся дней отпуска

Скрипт рассчитывает, сколько у сотрудника ещё осталось дней оплачиваемого отпуска. Данные для расчёта берутся из реестра формы.

form.onChange(["vacation_start", "vacation_end", "employee", "vacation_type"])
    .validateAsync("vacation_type", async state => {
        const [start, end, employee, vacation_type] = state.changes;

        if (!start?.date || !end?.date || !employee?.person_id || !vacation_type?.choice_name)
            return null;

        if (vacation_type.choice_name != "Оплачиваемый") return null;

        const max = 28;
        let this_time = daysBetween(new Date(start.date), new Date(end.date)) + 1;
        if (this_time > max)
            return {
                errorMessage: `Выбран промежуток в ${this_time} дней. Максимально ${max}`
            };

        let today = new Date();
        let already_taken = await form.fetchRegister(1027552, f =>
            f.fieldGreaterThen("Дата начала", {date: `01-01-${today.getFullYear()}`})
             .fieldLessThen("Дата окончания", {date: `01-01-${today.getFullYear()+1}`}),
            ["Дата начала", "Дата окончания"],
            {activeOnly: false}
        );

        if (!already_taken.tasks || already_taken.tasks.length == 0) return null;

        let taken_days = 0;
        for (let i in already_taken.tasks) {
            if (!already_taken.tasks[i].fields[0] || !already_taken.tasks[i].fields[1]) continue;

            taken_days += daysBetween(
                new Date(already_taken.tasks[i].fields[0].date),
                new Date(already_taken.tasks[i].fields[1].date)
            ) + 1;
        }
        if (taken_days + this_time > max)
            return {
                errorMessage: `Уже использовано ${taken_days} дней. Осталось ${max - taken_days}. Превышение на ${this_time + taken_days - max}`
            };

        return null;
    }); 

Была ли эта статья полезной?