Перейти к основному содержимому

Формула (Formula)

Поле типа Формула автоматически вычисляет значение на основе JavaScript-выражения.

Основные характеристики

  • Тип данных: Вычисляемое значение (любой тип)
  • Язык: JavaScript (ES6+)
  • Режим: Только для чтения
  • Пересчет: Автоматический при изменении зависимых полей
  • Доступ: К полям текущей записи и связанным записям

Настройки поля

При создании поля типа "Формула" доступны следующие настройки:

  • Название поля: Отображаемое имя поля
  • Системное имя: Уникальный идентификатор для API
  • Описание: Подсказка для пользователей
  • Формула: JavaScript-выражение
  • Тип результата: String, Number, Boolean, Date, Array, Object
  • Формат отображения: Для числовых и дат

Синтаксис формул

Доступ к полям

// Текущая запись
record.field_name

// Примеры
record.price
record.quantity
record.created_at

Математические операции

// Сложение
record.price + record.tax

// Вычитание
record.total - record.discount

// Умножение
record.price * record.quantity

// Деление
record.total / record.count

// Остаток от деления
record.number % 2

// Возведение в степень
record.base ** record.exponent

Строковые операции

// Конкатенация
record.first_name + ' ' + record.last_name

// Шаблонные строки
`${record.first_name} ${record.last_name}`

// Методы строк
record.name.toUpperCase()
record.email.toLowerCase()
record.text.substring(0, 100)
record.description.replace('old', 'new')

Условные выражения

// Тернарный оператор
record.quantity > 0 ? 'В наличии' : 'Нет в наличии'

// Множественные условия
record.score >= 90 ? 'Отлично' :
record.score >= 70 ? 'Хорошо' :
record.score >= 50 ? 'Удовлетворительно' : 'Неудовлетворительно'

// Логические операторы
record.is_active && record.is_verified ? 'Активен' : 'Неактивен'

Примеры использования

Полное имя

{
"name": "full_name",
"type": "formula",
"formula": "record.first_name + ' ' + record.last_name",
"resultType": "string"
}

Итоговая стоимость

{
"name": "total",
"type": "formula",
"formula": "record.price * record.quantity",
"resultType": "number",
"format": "currency"
}

Возраст

{
"name": "age",
"type": "formula",
"formula": "Math.floor((new Date() - new Date(record.birth_date)) / (365.25 * 24 * 60 * 60 * 1000))",
"resultType": "number"
}

Статус заказа

{
"name": "order_status",
"type": "formula",
"formula": "record.paid && record.shipped ? 'Доставлен' : record.paid ? 'Оплачен' : 'Ожидает оплаты'",
"resultType": "string"
}

Скидка в рублях

{
"name": "discount_amount",
"type": "formula",
"formula": "record.price * (record.discount_percent / 100)",
"resultType": "number",
"format": "currency"
}

Финальная цена

{
"name": "final_price",
"type": "formula",
"formula": "record.price - (record.price * (record.discount / 100))",
"resultType": "number"
}

Работа с датами

Дней до дедлайна

{
"name": "days_until_deadline",
"type": "formula",
"formula": "Math.ceil((new Date(record.deadline) - new Date()) / (24 * 60 * 60 * 1000))",
"resultType": "number"
}

Форматирование даты

{
"name": "formatted_date",
"type": "formula",
"formula": "new Date(record.created_at).toLocaleDateString('ru-RU')",
"resultType": "string"
}

Просрочено или нет

{
"name": "is_overdue",
"type": "formula",
"formula": "new Date(record.deadline) < new Date() && record.status !== 'Завершена'",
"resultType": "boolean"
}

Работа с массивами

Количество элементов

{
"name": "items_count",
"type": "formula",
"formula": "record.items.length",
"resultType": "number"
}

Сумма элементов

{
"name": "total_amount",
"type": "formula",
"formula": "record.items.reduce((sum, item) => sum + item.price, 0)",
"resultType": "number"
}

Объединение массива

{
"name": "tags_string",
"type": "formula",
"formula": "record.tags.join(', ')",
"resultType": "string"
}

Математические функции

Округление

// Округление до целого
Math.round(record.value)

// Округление вверх
Math.ceil(record.value)

// Округление вниз
Math.floor(record.value)

// Округление до 2 знаков
Math.round(record.value * 100) / 100

Минимум и максимум

// Минимальное значение
Math.min(record.price1, record.price2, record.price3)

// Максимальное значение
Math.max(record.value1, record.value2)

Абсолютное значение

Math.abs(record.difference)

Степень и корень

// Квадрат
Math.pow(record.value, 2)

// Квадратный корень
Math.sqrt(record.value)

Работа со связанными записями

Доступ к связанной записи

// Если есть связь с пользователем
record.user.name

// Если есть связь с категорией
record.category.title

Вычисления со связанными данными

{
"name": "author_info",
"type": "formula",
"formula": "`Автор: ${record.author.name} (${record.author.email})`",
"resultType": "string"
}

Сложные примеры

Прогресс выполнения

{
"name": "completion_percent",
"type": "formula",
"formula": "record.total_tasks > 0 ? Math.round((record.completed_tasks / record.total_tasks) * 100) : 0",
"resultType": "number",
"format": "percent"
}

Рейтинг

{
"name": "rating_display",
"type": "formula",
"formula": "'⭐'.repeat(Math.round(record.rating))",
"resultType": "string"
}

Инициалы

{
"name": "initials",
"type": "formula",
"formula": "(record.first_name[0] || '') + (record.last_name[0] || '')",
"resultType": "string"
}

Цветовой индикатор

{
"name": "status_color",
"type": "formula",
"formula": "record.status === 'Завершена' ? 'green' : record.status === 'В работе' ? 'blue' : 'gray'",
"resultType": "string"
}

Валидация email

{
"name": "is_valid_email",
"type": "formula",
"formula": "/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(record.email)",
"resultType": "boolean"
}

Обработка ошибок

Проверка на null/undefined

// Безопасный доступ
record.user?.name || 'Не указано'

// Значение по умолчанию
record.discount || 0

// Проверка существования
record.price ? record.price * 1.2 : 0

Try-catch (не поддерживается напрямую)

// Вместо try-catch используйте условные выражения
record.value && !isNaN(record.value) ? record.value * 2 : 0

Ограничения

  • Нельзя изменять значения полей
  • Нельзя выполнять асинхронные операции
  • Нельзя использовать циклы (for, while)
  • Ограниченный набор глобальных объектов (Math, Date, String, Number, Array)
  • Максимальная длина формулы: 1000 символов

Производительность

Оптимизация формул

// ❌ Плохо (сложные вычисления)
record.items.map(i => i.price).reduce((a, b) => a + b, 0) / record.items.length

// ✅ Хорошо (простые вычисления)
record.total / record.count

Кеширование

Формулы пересчитываются только при изменении зависимых полей.

Отладка формул

Тестирование в консоли

// Создать тестовую запись
const testRecord = {
price: 1000,
quantity: 5,
discount: 10
};

// Протестировать формулу
const result = testRecord.price * testRecord.quantity * (1 - testRecord.discount / 100);
console.log(result); // 4500

Рекомендации

  • Используйте формулы для вычисляемых значений
  • Избегайте сложных вычислений (влияет на производительность)
  • Используйте понятные имена полей
  • Добавляйте комментарии в описание поля
  • Проверяйте на null/undefined
  • Используйте значения по умолчанию
  • Тестируйте формулы перед развертыванием

Отличие от других типов

  • Formula vs Number: Formula вычисляется автоматически, Number вводится вручную
  • Formula vs Lookup: Formula для вычислений, Lookup для получения значений из связанных записей
  • Formula vs Rollup: Formula для простых вычислений, Rollup для агрегации связанных записей