Представьте утреннее письмо: «Продажи за вчера: 0 ₽». Руководитель хмурится, открывает CRM и видит там вполне приличную выручку. Что случилось? Ничего страшного: выгрузка из платёжной системы в этот раз не успела обновиться к семи утра, а отчёт честно сложил всё, что нашёл. Нашёл он пустоту, а пустоту превратил в ноль.
Ноль и отсутствие данных — разные утверждения. Ноль говорит: «мы посмотрели, продаж не было». Пустое место говорит: «мы не знаем». Если в сводке они выглядят одинаково, читатель вынужден гадать, а гадать он будет не в вашу пользу: либо перестанет верить отчёту вообще, либо, что хуже, примет решение по ложной цифре.
Ниже разберём, как собрать в n8n сводку из трёх источников — продаж, расходов и задач — так, чтобы каждая её часть сообщала о собственной свежести, а сбой подготовки не маскировался под спокойное утро. Если вам нужна именно техническая сторона ежедневного отчёта по продажам с PDF, она разобрана в статье про PostgreSQL, n8n и PDF к началу рабочего дня. Здесь мы говорим о другом: как не обмануть читателя, когда источников несколько и они живут в разном темпе.
Сначала договоримся, что такое «вчера»
Прежде чем соединять данные, нужно ответить на неочевидный вопрос: о каком отрезке времени идёт речь. У трёх источников три разных представления о «вчера».
- Продажи записываются платёжной системой или интернет-магазином, чаще всего во времени UTC или во времени сервера магазина.
- Расходы приходят из учётной системы или таблицы бухгалтера, где дата — это дата документа, а не момент, когда деньги ушли. Нередко это просто календарный день без времени.
- Задачи живут в трекере, где важны момент закрытия и часовой пояс пользователя.
Если взять «вчера» по московскому времени, а продажи отдать в UTC, заказ в 01:30 по Москве окажется в позавчерашнем дне. Три часа разницы дают тихий, но систематический перекос: часть выручки каждый день переезжает в соседние сутки, и сумма за неделю сойдётся, а за отдельный день — нет.
Практическое решение простое: определить границы периода один раз, в самом начале сценария, и передавать их всем веткам как данные. Например, узел Code или Set вычисляет два значения — начало и конец отчётного дня в выбранном поясе — и дальше каждый запрос к источнику использует именно их, а не собственное «вчера». Для источников, которые хранят только дату без времени, граница превращается в обычное сравнение календарных дат. Главное — записать правило в одно место, чтобы оно не размазалось по трём запросам.
Теперь о расписании. Узел Schedule Trigger запускает сценарий по времени, но, как сказано в документации к Schedule Trigger, расписание работает только у опубликованного workflow и зависит от часового пояса самого workflow либо экземпляра n8n. Из этого следуют две практические вещи.
- Черновик, который вы аккуратно собрали и успешно прогнали вручную, по расписанию не заработает, пока его не опубликуют. Кнопка «выполнить» проверяет логику, но не включает утренний запуск.
- «Каждый день в 07:00» означает 07:00 в поясе workflow или экземпляра. Если вы переносили сценарий между серверами или копировали его, проверьте, какой пояс действует теперь. Одна неверная настройка — и отчёт придёт в десять утра, когда его уже никто не ждёт, либо в четыре ночи, когда данные ещё вообще не собраны.
Хорошая привычка — вывести часовой пояс прямо в подвал отчёта: «Период: 12.03, 00:00–23:59, Москва; сформировано 13.03 в 07:02». Это одновременно подсказка читателю и страховка для вас: если пояс однажды сменится, расхождение бросится в глаза.
Склеиваем записи по тому, что не меняется
Допустим, вы хотите не просто три отдельные цифры, а связанную картину: сколько продано по каждому менеджеру, сколько на него потрачено и сколько задач он закрыл. Для этого записи из трёх источников нужно сопоставить. И здесь кроется следующая ловушка: сопоставлять по имени нельзя. «Иванов А.», «Александр Иванов» и «А. Иванов» — один человек в трёх системах, но три разные строки для компьютера.
Нужен устойчивый ключ — поле, которое не зависит от написания: идентификатор сотрудника, артикул, код проекта, номер договора. Если общего ключа нет, его стоит завести отдельной небольшой таблицей соответствий: «идентификатор в CRM — идентификатор в трекере — код в учёте». Это скучная работа, но без неё любое объединение превратится в лотерею.
Для самого объединения в n8n есть узел Merge. Согласно документации к Merge, он объединяет потоки по выбранному режиму, в том числе по совпадению значений полей, а в настройках можно различать совпавшие записи, несовпавшие и все. Вот это различие для нашей задачи ключевое.
Посмотрите на три варианта с точки зрения руководителя:
| Что оставляем | Результат для отчёта | Риск |
|---|---|---|
| Только совпавшие | В сводке остаются те, кто есть во всех источниках | Менеджер без расходов или без задач исчезает из отчёта молча |
| Все записи | В сводке есть все; недостающие поля пусты | Нужно явно решить, как показывать пустые поля |
| Только несовпавшие | Список «сирот» — записей без пары | Это не отчёт, а диагностика, но очень полезная |
Для основной сводки логичнее оставлять все записи: исчезновение человека из отчёта опаснее, чем пустая клетка рядом с его именем. А несовпавшие записи стоит направить в отдельную ветку и положить в раздел «требует внимания»: если в продажах появился код менеджера, которого нет в справочнике, это либо нового сотрудника забыли завести, либо опечатка в источнике. В обоих случаях лучше узнать об этом сегодня, чем в конце квартала.
Обратите внимание, что Merge только соединяет данные. Он не решает, как сложить рубли с долларами, как сопоставить дневные продажи с месячными расходами и в какой форме показать результат. Эти правила — ваша ответственность, и их нужно задать явно. Если расходы приходят в нескольких валютах, либо приводите их к одной по зафиксированному курсу и пишете об этом в подписи, либо показываете каждую валюту отдельной строкой. Складывать разное молча — самый короткий путь к красивой и неверной цифре.
Итог по источнику и состояние источника — две разные вещи
Теперь главный поворот. Обычно сценарий устроен так: достали данные, сложили, отправили. Но у каждого источника есть ещё и состояние: ответил ли он вообще, свежие ли данные и сколько записей вернул. Эта информация должна путешествовать по сценарию рядом с цифрой, а не теряться по дороге.
Удобная модель — после каждого источника формировать небольшой «паспорт» блока из нескольких полей:
- status — «получено», «не ответил» или «данные устарели»;
- rows — сколько записей вернул источник;
- updated_at — отметка времени последнего обновления самих данных, если источник её отдаёт, либо момент запроса;
- value — итоговая сумма или количество, а если данных нет — пустое значение, а не 0.
Различие между «получено, но ноль строк» и «не ответил» принципиально. Первое означает, что источник жив и продаж за период действительно нет (или он ещё не догнал реальность — см. ниже). Второе означает, что мы вообще ничего не знаем. В n8n у многих узлов есть настройка продолжения работы при ошибке: она позволяет не обрывать весь сценарий из-за одного капризного источника, а пустить его по отдельной ветке. Конкретные названия опций зависят от узла и версии, поэтому сверяйтесь с настройками вашего узла. Идея же такова: сбой одного источника не должен убивать весь отчёт, но обязан оставить в нём заметный след.
Есть и третий, самый коварный случай: источник ответил, строки есть, но данные неполные, потому что выгрузка ещё идёт. Здесь без свежести не обойтись. Если платёжная система отдаёт время последней записи или время последнего обновления, сравните его с границей периода. Когда последняя запись в 18:40, а отчёт закрывает сутки до 23:59, это повод пометить блок как «данные могли не догрузиться». Порог, после которого данные считаются устаревшими, выбирайте по ритму источника: для интернет-магазина с заказами каждую минуту это один масштаб, для ручной таблицы расходов, которую заполняют раз в день, совсем другой. Общего правильного значения нет, есть ваше согласованное с владельцем источника.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации Россия, США, Франция и Великобритания. Оплата картой РФ и по СБП.
Макет сводки: ноль, пусто и время
Теперь соберём всё в тексте, который прочитает человек. Ниже пример с условными цифрами: допустим, продажи ответили, расходы ответили с задержкой, а трекер задач не ответил вовсе.
Сводка за 12 марта (период 00:00–23:59, Москва)
Сформировано 13 марта в 07:02
Продажи: 184 500 ₽, 37 заказов
данные обновлены 13.03 в 06:55 — полные
Возвраты: 0 ₽, 0 операций
данные обновлены 13.03 в 06:55 — полные
Расходы: 62 300 ₽ (предварительно)
последняя запись 12.03 в 18:10 — возможна неполнота
Задачи: нет данных
трекер не ответил в 07:01, повторная попытка в 07:30
Требует внимания:
— 2 продажи с кодом менеджера, которого нет в справочникеВ этом макете одновременно присутствуют три разных состояния, и каждое выглядит по-своему.
- Ноль — строка «Возвраты: 0 ₽». Источник ответил, данные полные, возвратов не было. Это настоящая информация, и ноль здесь уместен.
- Отсутствующее значение — строка «Задачи: нет данных». Здесь нет ни нуля, ни числа, а есть слова. Руководитель не должен думать, что задач закрыто ноль.
- Неполное значение — «Расходы: 62 300 ₽ (предварительно)». Цифра есть, но рядом пометка, что она может вырасти. Для решений о бюджете это существенная оговорка.
Время обновления у каждой строки — не украшение. Оно позволяет руководителю самому оценить, можно ли доверять цифре, не открывая системы-источники. А общий итог вроде «прибыль за день» в таком отчёте лучше либо не считать, пока какой-то из блоков неполный, либо подписывать «расчёт по неполным данным». Если же итог всё-таки выводится, он должен наследовать худшее состояние входящих блоков: хоть один предварительный — значит, и итог предварительный.
Технически сборка текста — это узел, который проходит по паспортам блоков и для каждого выбирает шаблон строки: для статуса «получено» — число и время, для «устарели» — число с пометкой, для «не ответил» — «нет данных» и причину. Пустое значение в n8n проще всего хранить как null и проверять явно, а не полагаться на то, что отсутствующее поле само превратится в ноль при сложении. Вот где рождаются утренние «0 ₽».
Где всё это жить: ресурсы под такой сценарий
Сценарий вроде нашего — не тяжёлая вычислительная задача. Он по расписанию обращается к нескольким источникам, склеивает небольшие выборки и отправляет текст. Нагрузка здесь определяется прежде всего размером выборок и числом одновременных сценариев на том же экземпляре n8n. Если, кроме отчёта, на сервере крутятся другие автоматизации и, скажем, база данных, запас по памяти пригодится: у публичных тарифов VPS MAATRIX он начинается с конфигурации Ba (1 vCPU, 2 ГБ RAM, 40 ГБ NVMe) и растёт до Djed (16 vCPU, 32 ГБ, 320 ГБ). Это параметры ресурсов, а не обещание конкретной ёмкости: реальную нагрузку нужно мерить на своих сценариях.
Для самостоятельно развёрнутого n8n важны также приятные мелочи, которые влияют именно на нашу задачу. Корневой доступ позволяет самому задать часовой пояс экземпляра и проверить время на сервере, а значит, расписание отработает в тот час, который вы имели в виду. Выбор локации стоит делать с оглядкой на то, откуда доступны ваши источники данных: если часть из них — внутренние системы в России, а часть — зарубежные сервисы, проверьте доступность тех и других из выбранной локации заранее, а не по факту первого пропавшего отчёта. В публичном каталоге MAATRIX предлагается n8n с установкой, но точное наличие и операционную систему лучше уточнить в форме заказа.
Нужен сервер под эту задачу? Подробности и варианты тарифов доступны на странице аренды VPS MAATRIX.
Когда молчание — тоже сбой
Мы позаботились о том, чтобы отчёт честно показывал недостающее. Но остался последний слепой участок: что, если не сработал сам сценарий? Не дошёл запуск, упал узел до отправки, закончилась память, сломалась авторизация в почте. Тогда руководитель утром не увидит ни нуля, ни пустоты — он вообще ничего не увидит. И тишина будет выглядеть как нормальный день: «наверное, ничего не случилось».
Это худший вариант, потому что отсутствие сообщения неотличимо от отсутствия новостей. Чтобы такое не происходило, нужен отдельный канал для сообщений о самом процессе подготовки, а не о бизнесе. В n8n для этого есть error workflow. По документации по обработке ошибок, такой сценарий назначается в настройках основного workflow и начинается с узла Error Trigger; один и тот же error workflow можно подключить к нескольким сценариям.
Последнее удобно: один обработчик на все ваши автоматизации — значит, одно место, где настроено, кому и как писать о поломке. Что в нём стоит собрать:
- название сценария, который упал, и узел, на котором это произошло;
- время сбоя в том же часовом поясе, что и в отчёте;
- короткий текст сообщения об ошибке;
- ссылку на выполнение в n8n, чтобы дежурный сразу попал к месту.
Важная деталь: адресат уведомления о сбое — не руководитель, а тот, кто может починить. Руководителю же полезно получить короткое «сводка сегодня не подготовлена, ответственный уведомлён». Тогда отсутствие письма превращается в сообщение. Второй рубеж — проверка «письма нет к назначенному часу». Она не заменяет error workflow, потому что не все поломки порождают ошибку: если сценарий не запустился совсем, например из-за неопубликованного workflow или неверного расписания, Error Trigger сработать не на чем. Для такого случая подойдёт независимая проверка вне этого сценария, скажем, второй сценарий или внешний монитор, который ждёт отметку об успешной отправке и поднимает тревогу, если её нет к нужному часу. Это уже ваша архитектурная добавка, а не штатная возможность узла, и стоит делать её на другом контуре, чем основной отчёт, иначе они сломаются вместе.
Заметьте, как всё складывается. Паспорта блоков рассказывают о здоровье источников, error workflow — о здоровье самого сценария, а отметка времени в подвале — о том, в каком мире и в каком часу всё это считалось. Три уровня, и ни один не подменяет другой.
Как руководителю читать такую сводку
Мы строили отчёт, но он нужен для решений. Поэтому последняя его часть — подсказка читателю, какие выводы уже можно делать, а какие рано. Вернёмся к нашему утреннему письму из макета и прочитаем его глазами руководителя.
Продажи — полные, свежие. На них можно опираться: динамика заказов за вчера известна, и сравнивать её с предыдущими днями корректно. Возвраты — ноль, и это настоящий ноль. Расходы — предварительные, с последней записью вечером. Решение «мы укладываемся в дневной бюджет» принимать рано: часть вечерних затрат ещё может прийти. Решение «мы явно превысили бюджет» принимать можно, ведь уже известная сумма только вырастет. Задачи — нет данных вообще. Говорить о загрузке команды и закрытых делах по этому письму нельзя; нужно дождаться повторной попытки или спросить в трекере напрямую.
Асимметрия вывода и есть главная ценность хорошей сводки. Неполные данные не делают отчёт бесполезным: они сужают круг того, что из него можно сказать. Предварительная сумма расходов надёжна как нижняя граница, но не как итог. Отсутствие задач не говорит ни «много», ни «мало». А вот продажи при полных данных можно обсуждать на планёрке без оговорок.
Поэтому в конце письма стоит оставлять короткую строку, которую сценарий формирует сам из паспортов блоков: «Сегодня можно опираться на: продажи, возвраты. Пока рано делать выводы по: расходам (данные неполные), задачам (нет данных). Следующее обновление — в 07:30». Это одно предложение, но оно экономит руководителю пять минут сомнений и одно неверное решение. И когда в следующий раз в отчёте вдруг мелькнёт «нет данных», это перестанет быть тревожным сюрпризом и станет обычным, понятным состоянием: данные ещё в пути, а письмо уже честно рассказало, чему из него верить.