snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 3s

This commit is contained in:
upstream-mirror
2026-09-17 23:28:13 +05:00
commit e7da97194a
32688 changed files with 4208681 additions and 0 deletions

View File

@@ -0,0 +1,121 @@
---
title: Автоответ на входящие письма
description: Создайте рабочий процесс, который использует ИИ для сортировки входящих писем и автоматически отправляет ответы в рамках цепочки.
---
Отвечайте на входящие письма за секунды — а не за часы. Этот рабочий процесс использует ИИ-агента, чтобы отфильтровать шум (рассылки, спам, автоответы) и подготовить персонализированный ответ на реальные сообщения. Затем ИИ отправляет его как ответ в цепочке внутри исходной переписки.
## Как работают цепочки писем
Каждое письмо содержит скрытый заголовок `Message-ID` — уникальный отпечаток, назначаемый почтовым сервером отправителя. Когда вы отвечаете на электронное письмо, ваш почтовый клиент устанавливает заголовок `In-Reply-To`, ссылающийся на этот отпечаток. Именно так Gmail, Outlook и другие клиенты группируют сообщения в цепочки.
В Twenty этот отпечаток хранится как `headerMessageId` в объекте Message. Ваш рабочий процесс получает его и передает в поле In-Reply-To действия Send Email.
## Создание рабочего процесса
### Шаг 1: Создайте новый рабочий процесс
Перейдите в **Настройки → Рабочие процессы** и нажмите **+ New Workflow**.
### Шаг 2: Настройте триггер на входящие сообщения
Выберите **When a Record is Created** и укажите **Messages**.
Каждый раз, когда письмо попадает в Twenty, этот триггер срабатывает.
<img src="/images/user-guide/workflows/auto-reply/workflow-overview.png" style={{width:'100%'}} />
### Шаг 3: Найдите, кто отправил письмо
Добавьте действие **Search Records**.
Адрес отправителя находится не в самом сообщении — он в связанной записи Message Participant.
| Поле | Значение |
| ---------- | ---------------------------------- |
| **Объект** | Участники сообщения |
| **Фильтр** | Message **равно** `{{trigger.id}}` |
| **Фильтр** | Role **равно** From |
| **Лимит** | 1 |
Это дает вам адрес электронной почты отправителя в `handle` и его имя в `displayName`.
<img src="/images/user-guide/workflows/auto-reply/find-sender.png" style={{width:'100%'}} />
### Шаг 4: Классификация и черновик ответа с помощью ИИ
Добавьте действие **AI Agent**. Этот шаг делает две вещи: решает, заслуживает ли письмо ответа, и, если да, составляет ответ.
Используйте подсказку вроде:
```
You are an email triage assistant for a sales team. Read the following
inbound email and decide if it deserves a reply.
Subject: {{trigger.subject}}
Body: {{trigger.text}}
From: {{Find Sender.first.displayName}} ({{Find Sender.first.handle}})
If this email is spam, a newsletter, an automated notification, or
otherwise does not need a human reply, respond with exactly: SKIP
Otherwise, write a short, professional reply (3-4 sentences max) that:
- Acknowledges their specific message
- Lets them know someone from the team will follow up shortly
- Is warm but not overly casual
Respond with only the reply text, no subject line or greeting prefix.
```
AI Agent выводит свой ответ в поле `response`, к которому могут обращаться следующие шаги.
<img src="/images/user-guide/workflows/auto-reply/ai-triage.png" style={{width:'100%'}} />
### Шаг 5: Ветвление по решению ИИ
Добавьте действие **If/Else**, чтобы проверить, решил ли ИИ ответить или пропустить.
| Поле | Значение |
| -------------- | ---------------------------------------------- |
| **Условие** | AI Agent `response` **не содержит** `SKIP` |
| **Если верно** | Перейти к Send Email |
| **Иначе** | Ничего не делать (рабочий процесс завершается) |
Спам, рассылки и автоматически сгенерированные сообщения отбрасываются. Все остальное переходит к следующему шагу.
<img src="/images/user-guide/workflows/auto-reply/should-reply.png" style={{width:'100%'}} />
### Шаг 6: Отправьте ответ в цепочке
Добавьте действие **Send Email** в ветке "Если верно". Нажмите **Advanced options**, затем **Add In-Reply-To**.
| Поле | Значение |
| --------------- | -------------------------------------- |
| **Кому** | `{{Find Sender.first.handle}}` |
| **Тема** | `Re: {{trigger.subject}}` |
| **Текст** | `{{AI Triage & Draft Reply.response}}` |
| **In-Reply-To** | `{{trigger.headerMessageId}}` |
Поле In-Reply-To делает это письмом-ответом, а не новой перепиской. Получатель увидит его в той же цепочке под исходным письмом в Gmail, Outlook или любом другом клиенте.
<img src="/images/user-guide/workflows/auto-reply/send-email.png" style={{width:'100%'}} />
<Tip>
**In-Reply-To** ожидает `message.headerMessageId` из триггера — это уникальный отпечаток письма, а не адрес получателя. Если оставить его пустым, письмо всё равно отправится, но как отдельное сообщение.
</Tip>
<Warning>
Gmail использует тему письма, чтобы группировать сообщения в цепочки. Тема письма **должна** начинаться с `Re:` (включая двоеточие и пробел), чтобы Gmail показывал ответ внутри исходной цепочки. Без этого ответ будет отображаться как отдельная переписка — даже если заголовок In-Reply-To установлен правильно.
</Warning>
### Шаг 7: Протестируйте и активируйте
Нажмите **Test**, затем проверьте ваш почтовый клиент. Ответ должен появиться под исходным сообщением.
Активируйте, когда будете довольны результатом.
## Идеи для развития
* **Отвечать только VIP-адресатам** — добавьте ветку, которая проверяет домен отправителя или то, существует ли он как Контакт в Twenty
* **Маршрутизация по намерению** — используйте отдельные подсказки для AI Agent, чтобы обрабатывать запросы по продажам иначе, чем обращения в поддержку
* **Обогатите данные перед ответом** — добавьте шаг Search Records, чтобы передать компанию отправителя или историю сделок в подсказку ИИ для более персонализированных ответов

View File

@@ -0,0 +1,179 @@
---
title: Автоматизации для стадии «Закрыта — выиграна»
description: Автоматизируйте действия после выигрыша, когда возможность закрывается.
---
Когда сделка закрывается, нужно сделать несколько вещей: обновить статус компании, уведомить участников команды, создать задачи по онбордингу. Автоматизируйте всё это одним рабочим процессом.
## Проблема
Когда возможность переходит в стадию «Закрыта — выиграна»:
* Тип компании должен измениться с «Потенциальный клиент» на «Клиент»
* Нужно создать задачи по онбордингу
* Команду Customer Success необходимо уведомить
* Менеджеру по продажам нужно подтверждение
Вручную это занимает много времени и приводит к ошибкам.
## Решение
Создайте рабочий процесс, который автоматически выполнит все действия после выигрыша.
## Полная настройка рабочего процесса
### Шаг 1: Создайте рабочий процесс
1. Перейдите в **Настройки → Рабочие процессы**
2. Нажмите **+ Новый рабочий процесс**
3. Назовите его "Deal Won - Post-Win Automation"
### Шаг 2: Настройте триггер
1. Выберите **Запись обновлена**
2. Выберите **Возможности**
3. В разделе "Поля для отслеживания" выберите **Стадия**
### Шаг 3: Добавьте фильтр по стадии
1. Добавьте действие **Фильтр**
2. Условие: `{{trigger.object.stage}}` равно «Закрыта выиграна»
### Шаг 4: Обновите тип компании
1. Добавьте действие **Обновить запись**
2. Настроить:
| Поле | Значение |
| ---------------------- | ------------------------------- |
| **Объект** | Компании |
| **Запись** | `{{trigger.object.company.id}}` |
| **Тип** | Клиент |
| **Дата первой сделки** | `{{trigger.object.closedAt}}` |
| **Владелец аккаунта** | `{{trigger.object.owner.id}}` |
### Шаг 5: Создайте задачу по онбордингу
1. Добавьте действие **Создать запись**
2. Настроить:
| Поле | Значение |
| ------------------------- | ---------------------------------------------------------------------------------------------------- |
| **Объект** | Задачи |
| **Заголовок** | `Onboarding: {{trigger.object.name}}` |
| **Исполнитель** | Сотрудник команды Customer Success |
| **Срок исполнения** | Через 3 дня |
| **Приоритет** | Высокий |
| **Связанная компания** | `{{trigger.object.company.id}}` |
| **Связанная возможность** | `{{trigger.object.id}}` |
| **Описание** | `New customer onboarding for {{trigger.object.company.name}}. Deal value: {{trigger.object.amount}}` |
### Шаг 6: Уведомите команду Customer Success
1. Добавьте действие **Send Email**
2. Настроить:
| Поле | Значение |
| --------- | -------------------------------------------------- |
| **Кому** | customer-success@yourcompany.com |
| **Тема** | `🎉 New Customer: {{trigger.object.company.name}}` |
| **Текст** | См. пример ниже |
**Пример текста письма**:
```
Hi CS Team,
We have a new customer!
Company: {{trigger.object.company.name}}
Deal: {{trigger.object.name}}
Value: {{trigger.object.amount}}
Sales Rep: {{trigger.object.owner.name}}
Close Date: {{trigger.object.closedAt}}
An onboarding task has been created automatically.
Let's give them a great start!
```
### Шаг 7: Отправьте подтверждение менеджеру по продажам
1. Добавьте ещё одно действие **Send Email**
2. Настроить:
| Поле | Значение |
| --------- | ------------------------------------------------------------------------------------------ |
| **Кому** | `{{trigger.object.owner.email}}` |
| **Тема** | `✅ Deal Closed: {{trigger.object.name}}` |
| **Текст** | Поздравляем! Ваша сделка обработана. Команда Customer Success уведомлена, онбординг начат. |
### Шаг 8: Протестируйте и активируйте
1. Протестируйте, переместив тестовую возможность в стадию «Закрыта выиграна»
2. Проверить:
* Тип компании изменился на «Клиент»
* Задача по онбордингу создана
* Команда Customer Success получила письмо
* Менеджер по продажам получил подтверждение
3. Активируйте, когда будете готовы
## Обработка статуса «Закрыта проиграна»
Создайте аналогичный рабочий процесс для проигранных сделок:
### Триггер
* Запись обновлена (Возможности, стадия = «Закрыта проиграна»)
### Действия
1. **Создать запись**: Задача «Анализ проигранной сделки»
2. **Обновить запись**: Добавьте причину проигрыша в запись компании
3. **Send Email**: Уведомите менеджера о проигранной сделке
## Продвинутое: многошаговый онбординг
Для сложного онбординга создайте несколько задач:
```javascript
export const main = async (params) => {
const tasks = [
{ title: "Welcome call", daysFromNow: 1, assignee: "CS" },
{ title: "Send onboarding materials", daysFromNow: 2, assignee: "CS" },
{ title: "Technical setup", daysFromNow: 5, assignee: "Support" },
{ title: "30-day check-in", daysFromNow: 30, assignee: "CS" }
];
return { tasks };
};
```
Используйте **Iterator** для создания каждой задачи из массива.
## Идеи для настройки
### Держите другие инструменты в актуальном состоянии
* Создайте клиента в биллинговой системе с помощью **HTTP Request**
### Условные действия
Используйте действия **Фильтр**, чтобы:
* Разный онбординг для Enterprise и SMB
* Разные исполнители в зависимости от региона
* Пропускать уведомления для небольших сделок
### Включить информацию о сделке
Используйте действие **Code** для форматирования:
* Документы с краткой сводкой по сделке
* Заметки для передачи команде Customer Success
* Пользовательские контрольные списки онбординга
## Связанные материалы
* [Действия рабочего процесса](/l/ru/user-guide/workflows/capabilities/workflow-actions)
* [Отправка писем из рабочих процессов](/l/ru/user-guide/workflows/capabilities/send-emails-from-workflows)

View File

@@ -0,0 +1,136 @@
---
title: Выявление застойных возможностей
description: Автоматически уведомляйте менеджеров, когда возможности не обновлялись.
---
Поддерживайте воронку продаж в порядке, уведомляя менеджеров, когда возможности застаиваются. Этот рабочий процесс проверяет возможности, которые не обновлялись в течение указанного числа дней.
## Проблема
Возможности без обновлений приводят к:
* Остывающие сделки
* Ненадежные прогнозы
* Потеря выручки
## Решение
Создайте запланированный рабочий процесс, который находит застойные возможности и отправляет их менеджерам электронные письма.
## Пошаговая настройка
### Шаг 1: Создайте рабочий процесс
1. Перейдите в **Настройки → Рабочие процессы**
2. Нажмите **+ Новый рабочий процесс**
3. Назовите его «Оповещение о застойных возможностях»
### Шаг 2: Настройте триггер
1. Выберите **On a Schedule**
2. Установите ежедневный запуск (например, каждый день в 8:00)
### Шаг 3: Поиск застойных возможностей
1. Добавьте действие **Search Records**
2. Настроить:
| Поле | Значение |
| ---------- | ------------------------------------------ |
| **Объект** | Возможности |
| **Фильтр** | Updated At раньше, чем (сегодня - 7 дней) |
| **Фильтр** | Stage не "Closed Won" AND не "Closed Lost" |
| **Лимит** | 100 |
### Шаг 4: Проверьте, есть ли найденные записи
1. Добавьте действие **Фильтр**
2. Условие: `{{searchRecords.length}}` больше 0
3. Если застойных возможностей нет, рабочий процесс останавливается здесь
### Шаг 5: Сформатируйте оповещение (действие Code)
Добавьте действие **Code** для форматирования письма:
```javascript
export const main = async (params) => {
const opportunities = params.opportunities;
// Group opportunities by owner
const byOwner = {};
opportunities.forEach(opp => {
const ownerEmail = opp.owner?.email || 'unassigned';
if (!byOwner[ownerEmail]) {
byOwner[ownerEmail] = [];
}
byOwner[ownerEmail].push({
name: opp.name,
amount: opp.amount,
lastUpdated: opp.updatedAt,
stage: opp.stage
});
});
// Format summary for manager
let summary = "Stale Opportunities Report\n\n";
Object.entries(byOwner).forEach(([owner, opps]) => {
summary += `${owner}: ${opps.length} stale opportunities\n`;
opps.forEach(opp => {
summary += ` - ${opp.name} (${opp.stage})\n`;
});
summary += "\n";
});
return {
summary,
totalCount: opportunities.length
};
};
```
### Шаг 6: Отправьте письмо с оповещением
Добавьте действие **Send Email**:
| Поле | Значение |
| --------- | ---------------------------------------------------------------- |
| **Кому** | sales-manager@yourcompany.com |
| **Тема** | `🚨 {{code.totalCount}} застойных возможностей требуют внимания` |
| **Текст** | `{{code.summary}}` |
### Шаг 7: Протестируйте и активируйте
1. Нажмите **Test** для запуска рабочего процесса
2. Проверьте, что письмо содержит корректные данные
3. Активируйте, когда будете готовы
## Варианты настройки
### Измените порог застоя
Измените фильтр Search Records, чтобы заменить 7 дней на предпочитаемый вами период:
* 3 дня для быстрых продаж
* 14 дней для корпоративных сделок
* 30 дней для длинных циклов продаж
### Оповещайте отдельных менеджеров по продажам
Вместо одного письма менеджеру используйте **Iterator** для отправки персонализированных писем каждому менеджеру по продажам о его застойных сделках.
### Добавьте эскалацию
Создайте несколько рабочих процессов с возрастающим уровнем серьезности:
1. День 7: письмо менеджеру по продажам
2. День 14: письмо менеджеру по продажам и руководителю
3. День 21: создать задачу для руководителя о необходимости вмешаться
### Добавьте в Slack
Используйте **HTTP Request**, чтобы отправлять сообщения в Slack через вебхук вместо электронной почты или в дополнение к ней.
## Связанные материалы
* [Действия рабочего процесса](/l/ru/user-guide/workflows/capabilities/workflow-actions)
* [Отправка писем из рабочих процессов](/l/ru/user-guide/workflows/capabilities/send-emails-from-workflows)

View File

@@ -0,0 +1,69 @@
---
title: Отображение количества полученных электронных писем
description: Создайте рабочий процесс для автоматического подсчета и отображения количества электронных писем, полученных от каждого контакта.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<img src="/images/user-guide/workflows/workflow-number-of-emails-received.png" style={{width:'100%'}} />
## Обзор
Этот рабочий процесс срабатывает каждый раз при получении нового электронного письма и обновляет пользовательское поле в записи контакта значением общего количества писем от этого отправителя.
## Требования
Прежде чем настраивать этот рабочий процесс, создайте пользовательское поле в объекте **Контакты**:
1. Перейдите в **Настройки → Модель данных → Контакты**
2. Добавьте новое поле **Число**
3. Назовите его, например, "Количество писем, полученных от этого контакта"
## Пошаговая настройка
<VimeoEmbed videoId="1148700336" title="Видеодемонстрация" />
### Шаг 1: Настройте триггер
1. Перейдите в **Рабочие процессы** и создайте новый рабочий процесс
2. Выберите **Запись создана** в качестве триггера
3. Выберите **Участники сообщений** (доступно в разделе «Дополнительные объекты»)
<Note>
Участник сообщения — это комбинация идентификатора сообщения и идентификатора контакта, создающая одну уникальную запись на каждое сообщение. Это проще отслеживать, чем работать напрямую с объектом Messages, потому что мы можем получить доступ к полю `handle`, которое содержит адрес электронной почты отправителя (или получателя).
</Note>
### Шаг 2: Фильтр по роли
1. Добавьте действие **Фильтр**
2. Задайте условие: **Role** равно **FROM**
Это гарантирует, что вы считаете только сообщения, отправленные этим контактом, а не сообщения, отправленные ему.
### Шаг 3: Поиск всех участников сообщений с тем же handle
1. Добавьте действие **Search Records**
2. Выберите **Участники сообщений** в качестве объекта
3. Добавьте фильтры: **Handle** равен значению handle из триггера (адрес электронной почты отправителя) и **Role** равно **FROM**
4. Увеличьте **Limit** с 1 до **200** (максимум)
Это позволит найти все письма с этого адреса электронной почты, чтобы получить общее количество.
<Note>
Действие Search Records ограничено возвратом максимум 200 записей. Однако, поскольку вы используете только значение `totalCount` (а не отдельные записи), этот шаг вернет общее количество писем, отправленных этим контактом.
</Note>
### Шаг 4: Обновите запись контакта с помощью действия Create or Update Record
1. Добавьте действие **Create or Update Record**
2. Выберите **Контакты** в качестве объекта
3. Найдите контакт, сопоставив его адрес электронной почты со значением `handle` из участника сообщения
4. Установите значение вашего пользовательского поля "Количество полученных писем" равным `{{searchRecords.totalCount}}`
Значение `totalCount` из действия Search Records представляет общее количество писем, полученных от этого контакта.
## Связанные материалы
* [Действия рабочего процесса](/l/ru/user-guide/workflows/capabilities/workflow-actions)
* [Как создать пользовательские поля](/l/ru/user-guide/data-model/how-tos/customize-your-data-model)
* [Действие Search Records](/l/ru/user-guide/workflows/capabilities/workflow-actions#search-records)

View File

@@ -0,0 +1,170 @@
---
title: Отображение данных связанных записей
description: Показывайте данные из связанных записей (например, информацию о компании в сделках) с помощью рабочих процессов.
---
Отображайте данные из связанных записей прямо в ваших записях — например, показывайте численность сотрудников из компании в её сделках. Этот обходной путь с рабочим процессом полезен, пока вложенные поля не поддерживаются нативно.
## Типичные случаи использования
| Источник | Назначение | Поля для копирования |
| ----------- | ----------- | -------------------------------------------------------- |
| Компания | Возможность | Отрасль, размер компании, ARR |
| Контакт | Возможность | Email, телефон, должность |
| Возможность | Компания | Сумма последней сделки, дата последней выигранной сделки |
## Базовое копирование полей
### Пример: копирование Email контакта в сделку
**Цель**: При указании контактного лица в сделке скопируйте его email в сделку для быстрого доступа.
### Предварительное условие
Создайте целевые поля в **Settings → Data Model → Opportunities** перед созданием рабочего процесса:
* Email контакта (тип: Email)
* Телефон контакта (тип: Phone)
### Настройка
1. **Триггер**: Запись обновлена (Сделки, поле Контактное лицо)
2. **Фильтр**: Проверьте, что поле Контактное лицо не пустое
3. **Поиск записей**: найдите связанного контакта
* Объект: Контакты
* Фильтр: ID равен `{{trigger.object.pointOfContact.id}}`
4. **Обновление записи**:
* Объект: Сделки
* Запись: `{{trigger.object.id}}`
* Email контакта: `{{searchRecords[0].email}}`
* Телефон контакта: `{{searchRecords[0].phone}}`
## Копирование нескольких полей
### Пример: синхронизация данных компании со всеми связанными сделками
**Цель**: Когда данные компании меняются, обновляйте все связанные сделки.
### Настройка
1. **Триггер**: Запись обновлена (Компании)
* Поля: отрасль, размер компании, годовой доход
2. **Поиск записей**: найдите все сделки для этой компании
* Объект: Сделки
* Фильтр: ID компании равен `{{trigger.object.id}}`
3. **Итератор**: цикл по каждой сделке
4. **Обновление записи** (внутри итератора):
* Объект: Сделки
* Запись: `{{iterator.currentItem.id}}`
* Отрасль компании: `{{trigger.object.industry}}`
* Размер компании: `{{trigger.object.companySize}}`
* ARR компании: `{{trigger.object.annualRevenue}}`
## Копирование при создании записи
### Пример: предзаполнение сделки данными компании
**Цель**: При создании сделки, связанной с компанией, автоматически копируйте ключевую информацию о компании.
### Предварительное условие
Создайте целевые поля в **Settings → Data Model → Opportunities**:
* Отрасль компании (тип: Text)
* Размер компании (тип: Number)
### Настройка
1. **Триггер**: Запись создана (Сделки)
* Фильтр: поле Компания не пустое
2. **Поиск записей**: получите сведения о связанной компании
* Объект: Компании
* Фильтр: ID равен `{{trigger.object.company.id}}`
3. **Обновление записи**:
* Объект: Сделки
* Запись: `{{trigger.object.id}}`
* Отрасль компании: `{{searchRecords[0].industry}}`
* Размер компании: `{{searchRecords[0].employees}}`
<Note>
**Ограничение для задач и заметок**: связи в задачах и заметках жёстко заданы как многие-ко-многим и пока недоступны в триггерах или действиях рабочего процесса. Чтобы получить доступ к этим связям, вместо этого используйте [API](/l/ru/developers/extend/api).
</Note>
## Двунаправленная синхронизация
### Пример: синхронизация основного контакта
**Цель**: Когда в компании меняется основной контакт, обновляйте контакт. Когда контакт становится основным, обновляйте компанию.
### Рабочий процесс 1: Компания → Контакт
1. **Триггер**: Запись обновлена (Компании, поле Основной контакт)
2. **Обновление записи**: установите у контакта "Is Primary Contact" в значение true
3. **Поиск записей**: найдите предыдущего основного контакта
4. **Обновление записи**: установите у предыдущего контакта "Is Primary Contact" в значение false
### Рабочий процесс 2: Контакт → Компания
1. **Триггер**: Запись обновлена (Контакты, Is Primary Contact = true)
2. **Обновление записи**: установите у компании поле Primary Contact на этого контакта
<Note>
Осторожнее с двунаправленными синхронизациями, чтобы избежать бесконечных циклов. Используйте фильтры, чтобы проверять, действительно ли значение изменилось, прежде чем обновлять.
</Note>
## Использование кода для сложного сопоставления
### Пример: преобразование данных при копировании
**Цель**: Скопировать и отформатировать номер телефона из контакта в сделку.
```javascript
export const main = async (params) => {
const { phone } = params;
if (!phone) return { formattedPhone: null };
// Remove non-numeric characters
const digits = phone.replace(/\D/g, '');
// Format as (XXX) XXX-XXXX
const formatted = digits.length === 10
? `(${digits.slice(0,3)}) ${digits.slice(3,6)}-${digits.slice(6)}`
: phone;
return { formattedPhone: formatted };
};
```
## Лучшие практики
### Избегайте циклов
* Не создавайте рабочие процессы, которые бесконечно запускают друг друга
* Используйте конкретные условия для полей
* Добавляйте проверки, что значение действительно изменилось
### Обработка отсутствующих данных
* Всегда проверяйте, что исходная запись существует, перед копированием
* Задавайте значения по умолчанию для необязательных полей
* Используйте фильтры, чтобы пропускать случаи, когда исходное поле пусто
### Производительность
* Выполняйте пакетные обновления при копировании во множество записей
* Используйте запланированные рабочие процессы для массовых операций синхронизации
* Рассмотрите использование Итератора для обновления множества записей
## Связанные материалы
* [Действия рабочего процесса](/l/ru/user-guide/workflows/capabilities/workflow-actions)
* [Триггеры рабочего процесса](/l/ru/user-guide/workflows/capabilities/workflow-triggers)

View File

@@ -0,0 +1,202 @@
---
title: Формульные поля
description: Создавайте формульные поля с помощью рабочих процессов, пока не появится встроенная поддержка.
---
В Twenty пока нет собственной поддержки формульных полей (планируется в 2026), но того же результата можно добиться с помощью рабочих процессов. Это обходное решение позволяет автоматически вычислять и заполнять значения полей — от простых конкатенаций до сложной бизнес-логики.
## Типичные случаи использования
| Сценарий использования | Пример формулы |
| ---------------------- | ------------------------------------ |
| **Полное имя** | Имя + " " + Фамилия |
| **Ожидаемая сумма** | Сумма × Вероятность |
| **Дней до срока** | Срок - Сегодня |
| **Дней на этапе** | Сегодня - Дата входа в этап |
| **Оценка лида** | Баллы на основе нескольких критериев |
<Tip>
Полный пример отслеживания времени на этапах воронки см. в разделе [Как отслеживать, сколько времени возможности находятся на каждом этапе](/l/ru/user-guide/views-pipelines/how-tos/track-time-in-stage).
</Tip>
## Базовая формула: конкатенация
### Пример: автозаполнение полного имени
**Цель**: автоматически объединять имя и фамилию в поле полного имени.
### Настройка
1. **Триггер**: запись обновлена или создана (Люди)
2. **Фильтр**: проверьте, что изменились имя или фамилия
3. **Действие кода**:
```javascript
export const main = async (params) => {
const { firstName, lastName } = params;
const fullName = [firstName, lastName]
.filter(Boolean)
.join(' ');
return { fullName };
};
```
4. **Обновить запись**: установить Полное имя равным `{{code.fullName}}`
## Числовая формула: ожидаемая сумма
### Пример: рассчитать ожидаемую выручку
**Цель**: умножить сумму возможности на вероятность, чтобы получить ожидаемую сумму.
Полный рабочий процесс см. в разделе [Как показать ожидаемую сумму в воронке](/l/ru/user-guide/views-pipelines/how-tos/show-expected-amount-in-pipeline).
### Настройка
1. **Триггер**: запись обновлена (Возможности, поле Сумма ИЛИ Вероятность)
2. **Действие кода**:
```javascript
export const main = async (params) => {
const { amount, probability } = params;
const expectedAmount = (amount || 0) * (probability || 0) / 100;
return { expectedAmount };
};
```
3. **Обновить запись**: установить Ожидаемую сумму равной `{{code.expectedAmount}}`
## Формула даты: вычисление дней
### Пример: дни до срока задачи
**Цель**: вычислить, сколько дней осталось до срока задачи.
### Настройка
1. **Триггер**: запись обновлена или создана (Задачи, поле Срок)
2. **Действие кода**:
```javascript
export const main = async (params) => {
const { dueDate } = params;
if (!dueDate) {
return { daysUntilDue: null };
}
const due = new Date(dueDate);
const today = new Date();
const diffTime = due - today;
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return { daysUntilDue: diffDays };
};
```
3. **Обновить запись**: установить поле «Дни до срока» равным `{{code.daysUntilDue}}`
<Note>
Отрицательные значения означают просроченные задачи. Это поле можно использовать для фильтрации или сортировки задач по срочности.
</Note>
## Условная формула: оценка лида
### Пример: расчет оценки лида на основе критериев
**Цель**: оценивать лидов на основе размера компании, отрасли и вовлеченности.
### Настройка
1. **Триггер**: запись обновлена (Люди или Компании)
2. **Действие кода**:
```javascript
export const main = async (params) => {
const { companySize, industry, hasEmail, hasPhone, source } = params;
let score = 0;
// Company size scoring
if (companySize === 'Enterprise') score += 30;
else if (companySize === 'Mid-Market') score += 20;
else if (companySize === 'SMB') score += 10;
// Industry scoring
const targetIndustries = ['Technology', 'Finance', 'Healthcare'];
if (targetIndustries.includes(industry)) score += 25;
// Contact info scoring
if (hasEmail) score += 10;
if (hasPhone) score += 15;
// Source scoring
if (source === 'Referral') score += 20;
else if (source === 'Website') score += 10;
return { leadScore: score };
};
```
3. **Обновить запись**: установить Оценку лида равной `{{code.leadScore}}`
## Текстовая формула: извлечение домена
### Пример: извлечение домена из email
**Цель**: автоматически извлечь и сохранить домен email.
### Настройка
1. **Триггер**: запись обновлена (Люди, поле Email)
2. **Действие кода**:
```javascript
export const main = async (params) => {
const { email } = params;
if (!email) return { domain: null };
const domain = email.split('@')[1]?.toLowerCase();
return { domain };
};
```
3. **Обновить запись**: установить поле Домен равным `{{code.domain}}`
## Лучшие практики
### Производительность
* Запускайте только при изменении релевантных полей
* Используйте фильтры, чтобы пропускать записи, которым не требуется вычисление
* Избегайте сложных вычислений в высоконагруженных рабочих процессах
### Обработка ошибок
* Проверяйте значения null/undefined перед вычислениями
* Используйте значения по умолчанию при отсутствии данных
* Возвращайте понятные сообщения об ошибках при сбое вычислений
### Тестирование
* Тестируйте на крайних случаях (пустые поля, нулевые значения)
* Проверьте вычисления вручную перед активацией
* Отслеживайте выполнения рабочих процессов на предмет неожиданных результатов
## Связанные материалы
* [Как показать ожидаемую сумму в воронке](/l/ru/user-guide/views-pipelines/how-tos/show-expected-amount-in-pipeline)
* [Как отслеживать время на этапе](/l/ru/user-guide/views-pipelines/how-tos/track-time-in-stage)
* [Действия рабочего процесса](/l/ru/user-guide/workflows/capabilities/workflow-actions)

View File

@@ -0,0 +1,127 @@
---
title: Уведомление коллег о заметке для проверки
description: Отправляйте автоматические уведомления по email при назначении коллеги на проверку заметки.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<Note>
**Прямое упоминание коллег в заметках пока не поддерживается.** Эта функция запланирована на вторую половину 2026 года. Тем временем этот рабочий процесс предоставляет практичный обходной путь.
</Note>
## Проблема
Вы хотите упомянуть коллегу в заметке, чтобы коллеге пришло уведомление о необходимости её проверить. На данный момент Twenty не поддерживает @упоминания в заметках или задачах.
## Обходной путь
Вместо упоминания в тексте заметки вы можете:
1. Создайте поле связи **Reviewer** в объекте «Заметки»
2. Назначайте проверяющего при создании или редактировании заметки
3. Привлечь рабочий процесс автоматически отправить письмо рецензенту со ссылкой на заметку
<Warning>
**Ограничение**: этот подход позволяет назначать по одному проверяющему на поле. Если нужно уведомить нескольких людей, создайте дополнительные поля связи (например, Reviewer 1, Reviewer 2). Это плохо масштабируется, если нужно упомянуть сразу многих участников.
</Warning>
## Пошаговая настройка
### Шаг 1: Создайте поле Reviewer
<img src="/images/user-guide/workflows/reviewer-field-for-note.png" style={{width:'100%'}} />
1. Перейдите в **Настройки → Модель данных → Заметки**
2. Нажмите **Добавить поле**
3. Настроить:
| Настройка | Значение |
| -------------------- | --------------------------------------------------- |
| **Название** | Reviewer |
| **Тип** | Связь |
| **Связанный объект** | Участник рабочего пространства |
| **Тип связи** | Много заметок → Один участник рабочего пространства |
4. Сохраните поле
### Шаг 2: Создайте рабочий процесс
<VimeoEmbed videoId="1151897168" title="Видеодемонстрация" />
#### Создайте триггер рабочего процесса
1. Перейдите в основной вид и создайте новый рабочий процесс
2. Выберите **Record Updated** в качестве триггера
3. Выберите **Note** в качестве объекта и укажите поле Reviewer.
4. Это сработает каждый раз, когда у заметки обновляется проверяющий.
#### Добавьте фильтр, чтобы проверить, что проверяющий только что назначен
Добавьте действие **Фильтр**, чтобы рабочий процесс продолжался только когда назначен проверяющий:
| Условие | Значение |
| ------------ | -------- |
| **Reviewer** | не пусто |
#### Найдите участника рабочего пространства
1. Добавьте действие **Search Records**
2. Выберите **Workspace Member** (в разделе расширенных объектов)
3. Добавьте фильтр:
| Условие | Значение |
| ------- | --------------------------- |
| **ID** | равно `{{note.reviewerId}}` |
Это извлечёт полную запись участника рабочего пространства, включая адрес email и имя.
#### Отправьте уведомление по email
1. Добавьте действие **Send Email**
2. Настроить:
| Поле | Значение |
| --------- | --------------------------------------------------- |
| **Кому** | Search Record → First Workspace Member → User Email |
| **Тема** | Заметка требует вашей проверки |
| **Текст** | См. пример ниже |
**Пример текста письма:**
```text
Hi {{searchRecord.firstWorkspaceMember.name.firstName}},
You've been assigned to review a note.
View the note here:
https://yourSubDomain.twenty.com/object/note/{{recordIsUpdated.id}}
Best,
Twenty
```
<Note>
Замените `yourSubDomain` на фактический поддомен рабочего пространства Twenty.
</Note>
#### Протестируйте и активируйте
1. Нажмите **Test**, чтобы запустить рабочий процесс вручную
2. Создайте или отредактируйте заметку и назначьте проверяющего
3. Проверьте почтовый ящик проверяющего на письмо-уведомление
4. Активируйте рабочий процесс
## Использование
После настройки ваша команда сможет использовать этот рабочий процесс, выполняя следующее:
1. Создание новой заметки (или редактирование существующей)
2. Выбор коллеги в поле **Reviewer**
3. Коллега автоматически получает письмо с прямой ссылкой на заметку
## Связанные материалы
* [Триггеры рабочего процесса](/l/ru/user-guide/workflows/capabilities/workflow-triggers)
* [Отправка писем из рабочих процессов](/l/ru/user-guide/workflows/capabilities/send-emails-from-workflows)
* [Как создать пользовательские поля](/l/ru/user-guide/data-model/how-tos/create-custom-fields)
* [Создание полей связи](/l/ru/user-guide/data-model/how-tos/create-relation-fields)

View File

@@ -0,0 +1,106 @@
---
title: Отправка уведомлений по электронной почте о задачах со сроком выполнения
description: Автоматически уведомляйте членов команды об их предстоящих или просроченных задачах.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<img src="/images/user-guide/workflows/email_tasks_due.png" style={{width:'100%'}} />
Отправляйте ежедневные напоминания по электронной почте каждому члену команды о задачах, срок выполнения которых сегодня.
## Обзор
Этот рабочий процесс запускается по расписанию и:
1. Получает всех участников рабочего пространства
2. Последовательно обрабатывает каждого участника
3. Находит их задачи, срок выполнения которых сегодня
4. Форматирует и отправляет персонализированное письмо
## Пошаговая настройка
<VimeoEmbed videoId="1147463514" title="Видеодемонстрация" />
### Шаг 1: Настройте триггер
1. Перейдите в **Настройки → Рабочие процессы** и создайте новый рабочий процесс
2. Выберите **On a Schedule** в качестве триггера
3. Используйте выражение cron для ежедневного запуска в 8:00: `0 8 * * *`
### Шаг 2: Поиск всех участников рабочего пространства
1. Добавьте действие **Search Records**
2. Выберите **Workspace Members** (в разделе расширенных объектов)
3. Фильтры не требуются — это вернет всех участников
### Шаг 3: Добавьте итератор
1. Добавьте действие **Iterator**
2. Укажите в качестве входного массива участников рабочего пространства из предыдущего шага
3. Все действия внутри итератора выполнятся один раз для каждого участника
### Шаг 4: Поиск задач со сроком на сегодня (внутри итератора)
1. Внутри итератора добавьте действие **Search Records**
2. Выберите **Задачи** в качестве объекта
3. Добавьте фильтры:
* **Assignee** = текущий участник рабочего пространства (из итератора)
* **Due Date** = сегодня
### Шаг 5: Отформатируйте задачи в текст письма (внутри итератора)
Добавьте действие **Code**, чтобы отформатировать задачи в удобочитаемый список со ссылками:
```javascript
export const main = async (params: {
tasksDue?: Array<{ id: string; title: string }> | null | string;
}) => {
const tasksDue =
typeof params.tasksDue === "string"
? JSON.parse(params.tasksDue)
: params.tasksDue;
if (!Array.isArray(tasksDue) || tasksDue.length === 0) {
return {
formattedTasks: "No tasks due today."
};
}
const formattedTasks = tasksDue
.map(
t =>
`${t.title}\nhttps://yourSubDomain.twenty.com/object/task/${t.id}`
)
.join("\n\n");
return { formattedTasks };
};
```
<Note>
Замените `yourSubDomain` на фактический поддомен рабочего пространства Twenty.
</Note>
### Шаг 6: Отправьте письмо (внутри итератора)
1. Добавьте действие **Send Email** (по-прежнему внутри итератора)
2. Настроить:
| Поле | Значение |
| --------- | ---------------------------------------------------------------------------------------- |
| **Кому** | `{{iterator.currentItem.userEmail}}` (электронная почта участника рабочего пространства) |
| **Тема** | Ваши задачи на сегодня |
| **Текст** | `{{code.formattedTasks}}` |
### Шаг 7: Протестируйте и активируйте
1. Нажмите **Test**, чтобы запустить рабочий процесс вручную
2. Проверьте входящие на наличие писем
3. Активируйте рабочий процесс
## Связанные материалы
* [Действия рабочего процесса](/l/ru/user-guide/workflows/capabilities/workflow-actions)
* [Отправка писем из рабочих процессов](/l/ru/user-guide/workflows/capabilities/send-emails-from-workflows)
* [Обработка массивов в действиях Code](/l/ru/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions)