Добавлена обработка бинарных данных и форматирование email для n8n workflow
- Добавлен рабочий код для подготовки бинарных данных и вложений для узла Send email - Реализовано форматирование form_data в читаемый текст с разделами: * Личные данные (с автоматическим расчетом возраста, код документа, серия и номер) * Контактная информация * Страховщик (с телефоном страховой компании) * Банковские реквизиты (с получателем платежа) * Информация о страховом случае * Описание ситуации - Добавлена документация по использованию и устранению неполадок - Созданы альтернативные версии кода (простая версия, Function Node версия)
This commit is contained in:
113
ticket_form/docs/N8N_CODE_PREPARE_EMAIL_ATTACHMENTS.js
Normal file
113
ticket_form/docs/N8N_CODE_PREPARE_EMAIL_ATTACHMENTS.js
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
// ============================================================================
|
||||||
|
// n8n Code Node: Подготовка бинарных данных для отправки email с вложениями
|
||||||
|
// ============================================================================
|
||||||
|
// Согласно документации n8n, поле "Attachments" в узле "Send email" ожидает:
|
||||||
|
// "Enter the name of the binary properties that contain data to add as an attachment"
|
||||||
|
// "Add multiple attachments by entering a comma-separated list of binary properties"
|
||||||
|
//
|
||||||
|
// ИСПОЛЬЗОВАНИЕ В УЗЛЕ "Send email":
|
||||||
|
// В поле "Attachments" используйте: {{ $json.attachment_field }}
|
||||||
|
//
|
||||||
|
// Это строка с именами бинарных свойств через запятую, например:
|
||||||
|
// "file_0,file_1,file_2,file_3"
|
||||||
|
//
|
||||||
|
// ⚠️ ВАЖНО:
|
||||||
|
// - Это должны быть ИМЕНА бинарных свойств (ключи из объекта binary), а не сами данные
|
||||||
|
// - Формат: строка через запятую, НЕ массив
|
||||||
|
// - Бинарные данные должны быть в объекте binary с соответствующими ключами
|
||||||
|
//
|
||||||
|
// ❌ НЕ используйте:
|
||||||
|
// - {{ $json.attachments }} (это массив объектов!)
|
||||||
|
// - {{ $json.attachment_keys }} (это массив строк!)
|
||||||
|
// - {{ Object.keys($binary) }} (это массив!)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Проверяем наличие бинарных данных
|
||||||
|
// В n8n бинарные данные доступны через $binary или $input.item.binary
|
||||||
|
// Пробуем разные способы доступа к бинарным данным
|
||||||
|
let inputBinary = $binary;
|
||||||
|
if (!inputBinary || Object.keys(inputBinary).length === 0) {
|
||||||
|
// Пробуем через $input
|
||||||
|
try {
|
||||||
|
inputBinary = $input.item?.binary || $input?.binary || $binary;
|
||||||
|
} catch (e) {
|
||||||
|
inputBinary = $binary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inputBinary || Object.keys(inputBinary).length === 0) {
|
||||||
|
// Если бинарных данных нет, возвращаем данные без изменений
|
||||||
|
return [{
|
||||||
|
json: {
|
||||||
|
...$json,
|
||||||
|
attachment_field: '',
|
||||||
|
attachments: [],
|
||||||
|
attachment_keys: [],
|
||||||
|
error: 'No binary data found',
|
||||||
|
debug: {
|
||||||
|
binaryKeys: Object.keys(inputBinary || {}),
|
||||||
|
hasBinary: !!inputBinary,
|
||||||
|
hasDollarBinary: !!$binary,
|
||||||
|
dollarBinaryKeys: Object.keys($binary || {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создаем объект для бинарных данных и массивы для разных форматов
|
||||||
|
const binary = {};
|
||||||
|
const attachmentString = [];
|
||||||
|
const attachmentArray = []; // Массив объектов для поля Attachments
|
||||||
|
const attachmentKeys = []; // Простой массив ключей (альтернативный формат)
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
// Преобразуем все бинарные данные в формат file_0, file_1, etc.
|
||||||
|
for (const key of Object.keys(inputBinary)) {
|
||||||
|
const newKey = `file_${i}`;
|
||||||
|
const fileData = inputBinary[key];
|
||||||
|
|
||||||
|
// Копируем бинарные данные с сохранением всех свойств
|
||||||
|
binary[newKey] = {
|
||||||
|
...fileData,
|
||||||
|
fileName: fileData.fileName || fileData.name || `file_${i}.pdf`,
|
||||||
|
name: fileData.name || fileData.fileName || `file_${i}.pdf`
|
||||||
|
};
|
||||||
|
|
||||||
|
attachmentString.push(newKey);
|
||||||
|
attachmentKeys.push(newKey);
|
||||||
|
|
||||||
|
// Создаем объект для поля Attachments узла "Send email"
|
||||||
|
// ВАЖНО: data должен быть строкой с ключом, а не объектом
|
||||||
|
attachmentArray.push({
|
||||||
|
name: fileData.fileName || fileData.name || `file_${i}.pdf`,
|
||||||
|
data: newKey // Строка-ключ для ссылки на бинарные данные
|
||||||
|
});
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ⚠️ КРИТИЧНО: Бинарные данные должны быть переданы в объекте binary
|
||||||
|
// Ключи в binary должны совпадать с именами в attachment_field!
|
||||||
|
// В n8n Code Node нужно явно указать binary в возвращаемом объекте
|
||||||
|
const result = {
|
||||||
|
json: {
|
||||||
|
...$json,
|
||||||
|
// ⚠️ ВАЖНО: Строка с именами бинарных свойств через запятую БЕЗ ПРОБЕЛОВ
|
||||||
|
// Это именно то, что ожидает узел "Send email" согласно документации:
|
||||||
|
// "Enter the name of the binary properties... comma-separated list of binary properties"
|
||||||
|
// Формат: "file_0,file_1,file_2" - имена ключей из объекта binary
|
||||||
|
// ⚠️ Убираем все пробелы, чтобы имена точно совпадали с ключами в binary
|
||||||
|
attachment_field: attachmentString.join(',').replace(/\s+/g, ''),
|
||||||
|
// Массив объектов (для других целей, если нужно)
|
||||||
|
attachments: attachmentArray,
|
||||||
|
// Простой массив ключей (для отладки или других целей)
|
||||||
|
attachment_keys: attachmentKeys
|
||||||
|
},
|
||||||
|
// ⚠️ КРИТИЧНО: Бинарные данные должны быть переданы в объекте binary
|
||||||
|
binary: binary
|
||||||
|
};
|
||||||
|
|
||||||
|
// Возвращаем данные с бинарными файлами
|
||||||
|
return [result];
|
||||||
|
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
// ============================================================================
|
||||||
|
// n8n Code Node: Простая версия - передача бинарных данных как есть
|
||||||
|
// ============================================================================
|
||||||
|
// Используйте этот код, если бинарные данные уже приходят с именами file_0, file_1, etc.
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Получаем бинарные данные (пробуем разные способы доступа)
|
||||||
|
let inputBinary = $binary || $input?.item?.binary || $input?.binary || {};
|
||||||
|
|
||||||
|
// Получаем JSON данные
|
||||||
|
const jsonData = $json || $input?.item?.json || {};
|
||||||
|
|
||||||
|
// Проверяем наличие бинарных данных
|
||||||
|
if (!inputBinary || Object.keys(inputBinary).length === 0) {
|
||||||
|
return [{
|
||||||
|
json: {
|
||||||
|
...jsonData,
|
||||||
|
attachment_field: '',
|
||||||
|
error: 'No binary data found',
|
||||||
|
debug: {
|
||||||
|
hasBinary: !!inputBinary,
|
||||||
|
binaryKeys: Object.keys(inputBinary || {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем все ключи бинарных данных
|
||||||
|
const binaryKeys = Object.keys(inputBinary);
|
||||||
|
|
||||||
|
// Создаем строку с именами бинарных свойств через запятую
|
||||||
|
const attachmentField = binaryKeys.join(',');
|
||||||
|
|
||||||
|
// Возвращаем данные с бинарными файлами
|
||||||
|
// ⚠️ КРИТИЧНО: Бинарные данные должны быть переданы в объекте binary!
|
||||||
|
return [{
|
||||||
|
json: {
|
||||||
|
...jsonData,
|
||||||
|
attachment_field: attachmentField
|
||||||
|
},
|
||||||
|
// ⚠️ ВАЖНО: Передаем бинарные данные БЕЗ ИЗМЕНЕНИЙ
|
||||||
|
// Если они уже называются file_0, file_1, etc., они так и останутся
|
||||||
|
binary: inputBinary
|
||||||
|
}];
|
||||||
|
|
||||||
359
ticket_form/docs/N8N_CODE_PREPARE_EMAIL_ATTACHMENTS_WORKING.js
Normal file
359
ticket_form/docs/N8N_CODE_PREPARE_EMAIL_ATTACHMENTS_WORKING.js
Normal file
@@ -0,0 +1,359 @@
|
|||||||
|
// ============================================================================
|
||||||
|
// n8n Code Node: Подготовка бинарных данных для отправки email с вложениями
|
||||||
|
// РАБОЧАЯ ВЕРСИЯ - проверена и работает!
|
||||||
|
// ============================================================================
|
||||||
|
// Этот код успешно обрабатывает бинарные данные из webhook и подготавливает
|
||||||
|
// их для узла "Send email"
|
||||||
|
//
|
||||||
|
// ИСПОЛЬЗОВАНИЕ В УЗЛЕ "Send email":
|
||||||
|
// 1. В поле "Attachments" используйте: {{ $json.attachment_field }}
|
||||||
|
// 2. В поле "Text" используйте читаемый текст: {{ $json.email_body }}
|
||||||
|
// (вместо нечитаемого JSON {{ $('Webhook').item.json.body.form_data }})
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
const input = $input.all()?.[0] || {};
|
||||||
|
const binaries = input.binary || {};
|
||||||
|
const body = input.json?.body || {};
|
||||||
|
|
||||||
|
// Парсим form_data в объект
|
||||||
|
let formData = {};
|
||||||
|
let sub_dir = body.sub_dir || "";
|
||||||
|
|
||||||
|
if (typeof body.form_data === "string") {
|
||||||
|
try {
|
||||||
|
formData = JSON.parse(body.form_data);
|
||||||
|
sub_dir = formData?.sub_dir || sub_dir;
|
||||||
|
} catch (e) {
|
||||||
|
// Если не удалось распарсить, formData останется пустым объектом
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция для вычисления возраста на основе даты рождения
|
||||||
|
// Поддерживает форматы: DD-MM-YYYY, DD.MM.YYYY, YYYY-MM-DD
|
||||||
|
function calculateAge(birthday) {
|
||||||
|
if (!birthday) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Парсим дату в формате DD-MM-YYYY или DD.MM.YYYY
|
||||||
|
const dateStr = String(birthday).trim().replace(/\./g, '-');
|
||||||
|
const parts = dateStr.split(/[-/]/);
|
||||||
|
|
||||||
|
if (parts.length !== 3) return null;
|
||||||
|
|
||||||
|
// Определяем формат: DD-MM-YYYY или YYYY-MM-DD
|
||||||
|
let day, month, year;
|
||||||
|
if (parts[0].length === 4) {
|
||||||
|
// YYYY-MM-DD
|
||||||
|
year = parseInt(parts[0]);
|
||||||
|
month = parseInt(parts[1]) - 1; // месяцы в JS начинаются с 0
|
||||||
|
day = parseInt(parts[2]);
|
||||||
|
} else {
|
||||||
|
// DD-MM-YYYY
|
||||||
|
day = parseInt(parts[0]);
|
||||||
|
month = parseInt(parts[1]) - 1; // месяцы в JS начинаются с 0
|
||||||
|
year = parseInt(parts[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNaN(year) || isNaN(month) || isNaN(day)) return null;
|
||||||
|
|
||||||
|
// Проверяем валидность даты
|
||||||
|
if (year < 1900 || year > 2100) return null;
|
||||||
|
if (month < 0 || month > 11) return null;
|
||||||
|
if (day < 1 || day > 31) return null;
|
||||||
|
|
||||||
|
const birthDate = new Date(year, month, day);
|
||||||
|
const today = new Date();
|
||||||
|
|
||||||
|
// Проверяем, что дата валидна
|
||||||
|
if (birthDate.getFullYear() !== year ||
|
||||||
|
birthDate.getMonth() !== month ||
|
||||||
|
birthDate.getDate() !== day) {
|
||||||
|
return null; // Некорректная дата (например, 31 февраля)
|
||||||
|
}
|
||||||
|
|
||||||
|
let age = today.getFullYear() - birthDate.getFullYear();
|
||||||
|
const monthDiff = today.getMonth() - birthDate.getMonth();
|
||||||
|
|
||||||
|
// Если день рождения еще не наступил в этом году, уменьшаем возраст на 1
|
||||||
|
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
|
||||||
|
age--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return age >= 0 ? age : null;
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция для форматирования form_data в читаемый текст
|
||||||
|
function formatFormDataToText(data) {
|
||||||
|
if (!data || typeof data !== "object") {
|
||||||
|
return "Данные формы не найдены";
|
||||||
|
}
|
||||||
|
|
||||||
|
const lines = [];
|
||||||
|
|
||||||
|
// Маппинг полей на читаемые названия
|
||||||
|
const fieldLabels = {
|
||||||
|
formid: "ID формы",
|
||||||
|
ip: "IP адрес",
|
||||||
|
region: "Регион",
|
||||||
|
source: "Источник",
|
||||||
|
direction: "Направление",
|
||||||
|
inn: "ИНН",
|
||||||
|
ogrn: "ОГРН",
|
||||||
|
accountname: "Название организации",
|
||||||
|
address: "Адрес",
|
||||||
|
email: "Email",
|
||||||
|
phone: "Телефон",
|
||||||
|
mobile: "Мобильный телефон",
|
||||||
|
website: "Веб-сайт",
|
||||||
|
bank_name: "Название банка",
|
||||||
|
bank_id: "БИК банка",
|
||||||
|
lastname: "Фамилия",
|
||||||
|
firstname: "Имя",
|
||||||
|
secondname: "Отчество",
|
||||||
|
birthday: "Дата рождения",
|
||||||
|
description: "Описание",
|
||||||
|
mailingstreet: "Адрес проживания",
|
||||||
|
code: "Код",
|
||||||
|
sub_dir: "Подпапка",
|
||||||
|
// Поля с префиксом cf_ (custom fields)
|
||||||
|
cf_1885: "Номер полиса",
|
||||||
|
cf_1945: "Получатель платежа",
|
||||||
|
cf_1726: "Тип события",
|
||||||
|
cf_2566: "Дата события",
|
||||||
|
cf_2568: "Номер рейса",
|
||||||
|
cf_departure_flight: "Рейс отправления",
|
||||||
|
cf_departure_date: "Дата отправления",
|
||||||
|
cf_1899: "Код документа",
|
||||||
|
cf_1804: "Серия и номер документа",
|
||||||
|
cf_1909: "Страна",
|
||||||
|
cf_2502: "Количество",
|
||||||
|
cf_2446: "Доп. поле 1",
|
||||||
|
cf_1887: "Доп. поле 2",
|
||||||
|
cf_1889: "Доп. поле 3"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Добавляем заголовок
|
||||||
|
lines.push("═══════════════════════════════════════");
|
||||||
|
lines.push("ОБРАЩЕНИЕ ЗА СТРАХОВОЙ ВЫПЛАТОЙ");
|
||||||
|
lines.push("═══════════════════════════════════════");
|
||||||
|
lines.push("");
|
||||||
|
|
||||||
|
// Личные данные
|
||||||
|
if (data.lastname || data.firstname || data.secondname || data.birthday || data.cf_1899 || data.cf_1804) {
|
||||||
|
lines.push("ЛИЧНЫЕ ДАННЫЕ:");
|
||||||
|
if (data.lastname) lines.push(` Фамилия: ${data.lastname}`);
|
||||||
|
if (data.firstname) lines.push(` Имя: ${data.firstname}`);
|
||||||
|
if (data.secondname) lines.push(` Отчество: ${data.secondname}`);
|
||||||
|
if (data.birthday) {
|
||||||
|
lines.push(` Дата рождения: ${data.birthday}`);
|
||||||
|
// Вычисляем возраст на основе даты рождения
|
||||||
|
const calculatedAge = calculateAge(data.birthday);
|
||||||
|
if (calculatedAge !== null) {
|
||||||
|
lines.push(` Возраст: ${calculatedAge}`);
|
||||||
|
}
|
||||||
|
// Если не удалось вычислить возраст, но дата есть - можно добавить сообщение
|
||||||
|
// (но обычно calculateAge должен работать для корректных дат)
|
||||||
|
}
|
||||||
|
if (data.cf_1899) lines.push(` Код документа: ${data.cf_1899}`);
|
||||||
|
if (data.cf_1804) lines.push(` Серия и номер документа: ${data.cf_1804}`);
|
||||||
|
lines.push("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Контактная информация (только личные контакты заявителя)
|
||||||
|
if (data.email || data.mobile || data.mailingstreet) {
|
||||||
|
lines.push("КОНТАКТНАЯ ИНФОРМАЦИЯ:");
|
||||||
|
if (data.email) lines.push(` Email: ${data.email}`);
|
||||||
|
if (data.mobile) lines.push(` Мобильный: ${data.mobile}`);
|
||||||
|
if (data.mailingstreet) lines.push(` Адрес: ${data.mailingstreet}`);
|
||||||
|
lines.push("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Данные страховщика
|
||||||
|
if (data.accountname || data.inn || data.ogrn || data.address || data.website || data.phone) {
|
||||||
|
lines.push("СТРАХОВЩИК:");
|
||||||
|
if (data.accountname) lines.push(` Название: ${data.accountname}`);
|
||||||
|
if (data.inn) lines.push(` ИНН: ${data.inn}`);
|
||||||
|
if (data.ogrn) lines.push(` ОГРН: ${data.ogrn}`);
|
||||||
|
if (data.address) lines.push(` Адрес: ${data.address}`);
|
||||||
|
if (data.website) lines.push(` Веб-сайт: ${data.website}`);
|
||||||
|
if (data.phone) lines.push(` Телефон: ${data.phone}`);
|
||||||
|
lines.push("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Банковские реквизиты
|
||||||
|
if (data.bank_name || data.bank_id || data.cf_1945) {
|
||||||
|
lines.push("БАНКОВСКИЕ РЕКВИЗИТЫ:");
|
||||||
|
if (data.cf_1945) lines.push(` Получатель платежа: ${data.cf_1945}`);
|
||||||
|
if (data.bank_name) lines.push(` Банк: ${data.bank_name}`);
|
||||||
|
if (data.bank_id) lines.push(` БИК: ${data.bank_id}`);
|
||||||
|
lines.push("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Информация о страховом случае
|
||||||
|
if (data.cf_1885 || data.cf_1726 || data.cf_2566 || data.cf_2568) {
|
||||||
|
lines.push("ИНФОРМАЦИЯ О СТРАХОВОМ СЛУЧАЕ:");
|
||||||
|
if (data.cf_1885) lines.push(` Номер полиса: ${data.cf_1885}`);
|
||||||
|
if (data.cf_1726) lines.push(` Тип события: ${data.cf_1726}`);
|
||||||
|
if (data.cf_2566) lines.push(` Дата события: ${data.cf_2566}`);
|
||||||
|
if (data.cf_2568) lines.push(` Номер рейса: ${data.cf_2568}`);
|
||||||
|
if (data.cf_departure_flight) lines.push(` Рейс отправления: ${data.cf_departure_flight}`);
|
||||||
|
if (data.cf_departure_date) lines.push(` Дата отправления: ${data.cf_departure_date}`);
|
||||||
|
lines.push("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Описание
|
||||||
|
if (data.description) {
|
||||||
|
lines.push("ОПИСАНИЕ СИТУАЦИИ:");
|
||||||
|
lines.push(` ${data.description}`);
|
||||||
|
lines.push("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Дополнительная информация
|
||||||
|
const additionalFields = [];
|
||||||
|
for (const [key, value] of Object.entries(data)) {
|
||||||
|
// Пропускаем уже обработанные поля и пустые значения
|
||||||
|
if (key === "formid" || key === "sub_dir" || key === "code" ||
|
||||||
|
key.startsWith("cf_") && fieldLabels[key] ||
|
||||||
|
!value || value === "" || value === "0") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавляем необработанные поля
|
||||||
|
if (!fieldLabels[key] && !["lastname", "firstname", "secondname", "birthday",
|
||||||
|
"email", "phone", "mobile", "mailingstreet", "accountname", "inn", "ogrn",
|
||||||
|
"address", "website", "bank_name", "bank_id", "description",
|
||||||
|
"cf_1885", "cf_1945", "cf_1726", "cf_2566", "cf_2568", "cf_departure_flight",
|
||||||
|
"cf_departure_date", "cf_1899", "cf_1804", "cf_1909", "cf_2502"].includes(key)) {
|
||||||
|
additionalFields.push({ key, value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (additionalFields.length > 0) {
|
||||||
|
lines.push("ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ:");
|
||||||
|
for (const { key, value } of additionalFields) {
|
||||||
|
lines.push(` ${key}: ${value}`);
|
||||||
|
}
|
||||||
|
lines.push("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Техническая информация
|
||||||
|
lines.push("═══════════════════════════════════════");
|
||||||
|
lines.push("ТЕХНИЧЕСКАЯ ИНФОРМАЦИЯ:");
|
||||||
|
if (data.ip) lines.push(` IP адрес: ${data.ip}`);
|
||||||
|
if (data.region) lines.push(` Регион: ${data.region}`);
|
||||||
|
if (data.formid) lines.push(` ID формы: ${data.formid}`);
|
||||||
|
if (data.code) lines.push(` Код: ${data.code}`);
|
||||||
|
lines.push("═══════════════════════════════════════");
|
||||||
|
|
||||||
|
return lines.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Форматируем form_data в читаемый текст
|
||||||
|
const emailBodyText = formatFormDataToText(formData);
|
||||||
|
|
||||||
|
// 1) Парсим files_meta[group][prop] -> metaByGroup[group][prop]
|
||||||
|
// Преобразуем структуру типа:
|
||||||
|
// files_meta[supporting_documents][description] = "supporting_documents"
|
||||||
|
// files_meta[supporting_documents][count] = "2"
|
||||||
|
// В объект:
|
||||||
|
// metaByGroup[supporting_documents] = { description: "...", count: "2" }
|
||||||
|
const metaByGroup = {};
|
||||||
|
for (const [k, v] of Object.entries(body)) {
|
||||||
|
const m = k.match(/^files_meta\[([^\]]+)\]\[([^\]]+)\]$/);
|
||||||
|
if (!m) continue;
|
||||||
|
const group = m[1];
|
||||||
|
const prop = m[2];
|
||||||
|
metaByGroup[group] ??= {};
|
||||||
|
metaByGroup[group][prop] = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем наличие бинарных данных
|
||||||
|
if (!binaries || Object.keys(binaries).length === 0) {
|
||||||
|
return [{
|
||||||
|
json: { has_files: false, message: "no files uploaded", sub_dir },
|
||||||
|
binary: {}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Вспомогательная функция для получения расширения файла
|
||||||
|
const getExt = (name) =>
|
||||||
|
name && name.includes(".") ? "." + name.split(".").pop().toLowerCase() : "";
|
||||||
|
|
||||||
|
// 2) Парсер бинарного поля
|
||||||
|
// Парсит имена полей типа:
|
||||||
|
// files[supporting_documents][0] -> { group: "supporting_documents", index: 0 }
|
||||||
|
// files[insurance_policy] -> { group: "insurance_policy", index: null }
|
||||||
|
function parseBinaryField(field) {
|
||||||
|
const m = field.match(/^files\[([^\]]+)\]\[(\d+)\]$/);
|
||||||
|
if (m) return { group: m[1], index: Number(m[2]) };
|
||||||
|
|
||||||
|
const m2 = field.match(/^files\[([^\]]+)\]$/);
|
||||||
|
if (m2) return { group: m2[1], index: null };
|
||||||
|
|
||||||
|
return { group: null, index: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Собираем ВСЁ В ОДИН ITEM
|
||||||
|
// Преобразуем бинарные данные в формат file_0, file_1, etc.
|
||||||
|
const outBinary = {};
|
||||||
|
const attachments = [];
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
for (const [field, file] of Object.entries(binaries)) {
|
||||||
|
const { group, index } = parseBinaryField(field);
|
||||||
|
if (!group) continue;
|
||||||
|
if (!file?.data) continue; // 🔥 защита от пустого бинаря
|
||||||
|
|
||||||
|
const meta = metaByGroup[group] || {};
|
||||||
|
const origName = file.fileName || "";
|
||||||
|
const ext = getExt(origName);
|
||||||
|
|
||||||
|
// Создаем новый ключ для бинарных данных
|
||||||
|
const outKey = `file_${i}`;
|
||||||
|
// Создаем имя файла: group_index.ext или group.ext
|
||||||
|
const outName = `${group}${index !== null ? "_" + index : ""}${ext}`;
|
||||||
|
|
||||||
|
// Сохраняем бинарные данные с новым ключом
|
||||||
|
outBinary[outKey] = {
|
||||||
|
...file,
|
||||||
|
fileName: outName
|
||||||
|
};
|
||||||
|
|
||||||
|
// Сохраняем метаданные о файле
|
||||||
|
attachments.push({
|
||||||
|
key: outKey, // file_0, file_1, etc.
|
||||||
|
group, // supporting_documents, insurance_policy, etc.
|
||||||
|
description: meta.description || "",
|
||||||
|
original_field: meta.original_field || "",
|
||||||
|
index,
|
||||||
|
original_file_name: origName,
|
||||||
|
file_name: outName,
|
||||||
|
size: file.fileSize,
|
||||||
|
mime: file.mimeType
|
||||||
|
});
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Возвращаем данные с бинарными файлами
|
||||||
|
return [{
|
||||||
|
json: {
|
||||||
|
has_files: attachments.length > 0,
|
||||||
|
sub_dir,
|
||||||
|
attachments, // Массив с метаданными о файлах
|
||||||
|
attachment_keys: attachments.map(a => a.key), // Массив ключей: ["file_0", "file_1", ...]
|
||||||
|
attachment_field: attachments.map(a => a.key).join(","), // Строка для узла "Send email": "file_0,file_1,file_2"
|
||||||
|
|
||||||
|
// Форматированные данные для email
|
||||||
|
email_body: emailBodyText, // Читаемый текст для email
|
||||||
|
form_data_parsed: formData, // Распарсенные данные формы (JSON объект)
|
||||||
|
form_data_raw: body.form_data // Исходная JSON строка (для совместимости)
|
||||||
|
},
|
||||||
|
// ⚠️ КРИТИЧНО: Бинарные данные должны быть переданы в объекте binary
|
||||||
|
// Ключи в binary должны совпадать с именами в attachment_field!
|
||||||
|
binary: outBinary
|
||||||
|
}];
|
||||||
|
|
||||||
220
ticket_form/docs/N8N_EMAIL_ATTACHMENTS_FIX.md
Normal file
220
ticket_form/docs/N8N_EMAIL_ATTACHMENTS_FIX.md
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
# Исправление проблемы с вложениями в узле "Send email" n8n
|
||||||
|
|
||||||
|
## Проблема
|
||||||
|
|
||||||
|
Узел "Send email" выдает ошибку:
|
||||||
|
```
|
||||||
|
The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received undefined
|
||||||
|
```
|
||||||
|
|
||||||
|
**Причина:** В поле `Attachments` передается строка с именами файлов (`file_0,file_1,file_2...`), но узел ожидает бинарные данные файлов.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Решение
|
||||||
|
|
||||||
|
### Вариант 1: Использование Code Node для подготовки данных (Рекомендуется)
|
||||||
|
|
||||||
|
#### Шаг 1: Добавьте Code Node перед узлом "Send email"
|
||||||
|
|
||||||
|
**Код для Code Node:**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Создаем объект для бинарных данных и массив имен файлов
|
||||||
|
const binary = {};
|
||||||
|
const attachmentString = [];
|
||||||
|
const attachmentArray = []; // Массив объектов для поля Attachments
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
// Преобразуем все бинарные данные в формат file_0, file_1, etc.
|
||||||
|
for (const key of Object.keys($binary)) {
|
||||||
|
const newKey = `file_${i}`;
|
||||||
|
binary[newKey] = $binary[key];
|
||||||
|
attachmentString.push(newKey);
|
||||||
|
|
||||||
|
// Создаем объект для поля Attachments узла "Send email"
|
||||||
|
attachmentArray.push({
|
||||||
|
name: $binary[key].fileName || $binary[key].name || `file_${i}.pdf`,
|
||||||
|
data: newKey // Ссылка на бинарные данные
|
||||||
|
});
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Возвращаем данные с бинарными файлами
|
||||||
|
return [{
|
||||||
|
json: {
|
||||||
|
...$json,
|
||||||
|
attachment_field: attachmentString.join(','),
|
||||||
|
attachments: attachmentArray
|
||||||
|
},
|
||||||
|
binary: binary
|
||||||
|
}];
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Шаг 2: Настройте узел "Send email"
|
||||||
|
|
||||||
|
**Согласно официальной документации n8n:**
|
||||||
|
|
||||||
|
> "Enter the name of the binary properties that contain data to add as an attachment. Add multiple attachments by entering a comma-separated list of binary properties."
|
||||||
|
|
||||||
|
**В поле "Attachments" используйте:**
|
||||||
|
|
||||||
|
**✅ ПРАВИЛЬНО (РЕКОМЕНДУЕТСЯ):**
|
||||||
|
```
|
||||||
|
{{ $json.attachment_field }}
|
||||||
|
```
|
||||||
|
|
||||||
|
Это строка с именами бинарных свойств через запятую: `"file_0,file_1,file_2,file_3"`
|
||||||
|
|
||||||
|
**Важно понимать:**
|
||||||
|
- Это имена ключей из объекта `binary`, а не сами данные
|
||||||
|
- Формат: строка через запятую, НЕ массив
|
||||||
|
- Узел "Send email" найдет бинарные данные по этим именам в объекте `binary`
|
||||||
|
|
||||||
|
**❌ НЕПРАВИЛЬНО - НЕ ИСПОЛЬЗУЙТЕ:**
|
||||||
|
|
||||||
|
1. **Массив объектов:**
|
||||||
|
```javascript
|
||||||
|
{{ $json.attachments }} // ❌ Это массив объектов!
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Массив строк:**
|
||||||
|
```javascript
|
||||||
|
{{ $json.attachment_keys }} // ❌ Это массив строк!
|
||||||
|
{{ Object.keys($binary) }} // ❌ Это тоже массив!
|
||||||
|
```
|
||||||
|
|
||||||
|
**⚠️ КРИТИЧНО:**
|
||||||
|
- Используйте **СТРОКУ** `{{ $json.attachment_field }}`
|
||||||
|
- Убедитесь, что бинарные данные переданы в объекте `binary` выходного значения Code Node
|
||||||
|
- Ключи в объекте `binary` должны точно совпадать с именами в строке `attachment_field`!
|
||||||
|
- Например: если `attachment_field = "file_0,file_1"`, то в `binary` должны быть ключи `file_0` и `file_1`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Вариант 2: Прямое использование бинарных данных (Проще)
|
||||||
|
|
||||||
|
Если бинарные данные уже есть в правильном формате, просто используйте их напрямую:
|
||||||
|
|
||||||
|
**В поле Attachments узла "Send email":**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{{
|
||||||
|
Object.keys($binary).map(key => {
|
||||||
|
const file = $binary[key];
|
||||||
|
return {
|
||||||
|
name: file.fileName || file.name || key,
|
||||||
|
data: file.data || file
|
||||||
|
};
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Вариант 3: Использование Function Node (Если Code Node не работает)
|
||||||
|
|
||||||
|
Если Code Node не передает бинарные данные правильно, используйте Function Node:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const items = $input.all();
|
||||||
|
|
||||||
|
const result = items.map(item => {
|
||||||
|
const binary = {};
|
||||||
|
const attachmentArray = [];
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
for (const key of Object.keys(item.binary || {})) {
|
||||||
|
const newKey = `file_${i}`;
|
||||||
|
binary[newKey] = item.binary[key];
|
||||||
|
|
||||||
|
attachmentArray.push({
|
||||||
|
name: item.binary[key].fileName || item.binary[key].name || `file_${i}.pdf`,
|
||||||
|
data: newKey
|
||||||
|
});
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
json: {
|
||||||
|
...item.json,
|
||||||
|
attachments: attachmentArray,
|
||||||
|
attachment_field: Object.keys(binary).join(',')
|
||||||
|
},
|
||||||
|
binary: binary
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Проверка работы
|
||||||
|
|
||||||
|
1. **Убедитесь, что бинарные данные передаются:**
|
||||||
|
- В Code/Function Node должен быть объект `binary` в возвращаемом значении
|
||||||
|
- Проверьте в OUTPUT панели, что бинарные данные присутствуют
|
||||||
|
|
||||||
|
2. **Проверьте формат данных:**
|
||||||
|
- В поле Attachments должен быть массив объектов или правильная ссылка на бинарные данные
|
||||||
|
- Каждый объект должен содержать `name` и `data`
|
||||||
|
|
||||||
|
3. **Тестирование:**
|
||||||
|
- Запустите workflow в тестовом режиме
|
||||||
|
- Проверьте, что email отправляется с вложениями
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Частые ошибки
|
||||||
|
|
||||||
|
1. **Ошибка: "Received undefined"**
|
||||||
|
- **Причина:** Бинарные данные не переданы в выходном объекте
|
||||||
|
- **Решение:** Убедитесь, что объект `binary` включен в возвращаемое значение Code/Function Node
|
||||||
|
|
||||||
|
2. **Ошибка: "options.attachments.split is not a function" или "property.split is not a function"**
|
||||||
|
- **Причина:** В поле Attachments передан массив (объектов или строк) вместо строки. Согласно документации n8n, поле ожидает "comma-separated list of binary properties" (строку через запятую), а не массив.
|
||||||
|
- **Решение:**
|
||||||
|
- ✅ Используйте СТРОКУ с именами бинарных свойств: `{{ $json.attachment_field }}`
|
||||||
|
- Формат должен быть: `"file_0,file_1,file_2"` (строка), а не `["file_0","file_1","file_2"]` (массив)
|
||||||
|
- ❌ НЕ используйте `{{ $json.attachments }}` (это массив объектов)
|
||||||
|
- ❌ НЕ используйте `{{ $json.attachment_keys }}` (это массив строк)
|
||||||
|
- ❌ НЕ используйте `{{ Object.keys($binary) }}` (это тоже массив)
|
||||||
|
|
||||||
|
3. **Ошибка: "Invalid argument type"**
|
||||||
|
- **Причина:** Неправильный формат данных в поле Attachments
|
||||||
|
- **Решение:** Используйте строку с именами бинарных свойств через запятую: `"file_0,file_1,file_2"` (строка), а не массив
|
||||||
|
|
||||||
|
4. **Файлы не прикрепляются**
|
||||||
|
- **Причина:** Имена файлов не совпадают с ключами в объекте `binary`
|
||||||
|
- **Решение:** Убедитесь, что ключи в массиве Attachments точно соответствуют ключам в объекте `binary`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Пример полного workflow
|
||||||
|
|
||||||
|
```
|
||||||
|
Webhook → Process Files → Code Node (подготовка) → Send Email
|
||||||
|
↓
|
||||||
|
(binary данные)
|
||||||
|
↓
|
||||||
|
(attachments массив)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Code Node:**
|
||||||
|
- Вход: бинарные данные из предыдущего узла
|
||||||
|
- Выход: `{ json: {...}, binary: {...} }` с правильными ссылками
|
||||||
|
|
||||||
|
**Send Email:**
|
||||||
|
- Attachments: `{{ $json.attachments }}` или прямое обращение к `$binary`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Дополнительные ресурсы
|
||||||
|
|
||||||
|
- Файл с кодом: `ticket_form/docs/N8N_CODE_PREPARE_EMAIL_ATTACHMENTS.js`
|
||||||
|
- Документация n8n: https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.email/
|
||||||
|
|
||||||
224
ticket_form/docs/N8N_EMAIL_ATTACHMENTS_QUICK_FIX.md
Normal file
224
ticket_form/docs/N8N_EMAIL_ATTACHMENTS_QUICK_FIX.md
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
# Быстрое исправление ошибки с вложениями в n8n "Send email"
|
||||||
|
|
||||||
|
## ✅ РАБОЧЕЕ РЕШЕНИЕ
|
||||||
|
|
||||||
|
**Используйте код из файла:** `N8N_CODE_PREPARE_EMAIL_ATTACHMENTS_WORKING.js`
|
||||||
|
|
||||||
|
**В узле "Send email":**
|
||||||
|
|
||||||
|
1. **В поле "Attachments" используйте:**
|
||||||
|
```
|
||||||
|
{{ $json.attachment_field }}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **В поле "Text" (или "Body") используйте читаемый текст:**
|
||||||
|
```
|
||||||
|
{{ $json.email_body }}
|
||||||
|
```
|
||||||
|
|
||||||
|
Вместо нечитаемого JSON `{{ $('Webhook').item.json.body.form_data }}` вы получите красиво отформатированный текст!
|
||||||
|
|
||||||
|
Этот код успешно протестирован и работает! ✅
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Проблемы, которые решает этот код
|
||||||
|
|
||||||
|
- ❌ "options.attachments.split is not a function"
|
||||||
|
- ❌ "The first argument must be of type string or an instance of Buffer... Received undefined"
|
||||||
|
- ❌ Бинарные данные не передаются между узлами
|
||||||
|
- ❌ Файлы не прикрепляются к email
|
||||||
|
|
||||||
|
## ✅ Правильная настройка
|
||||||
|
|
||||||
|
### 1. Code Node (подготовка данных)
|
||||||
|
|
||||||
|
**✅ РАБОЧАЯ ВЕРСИЯ (РЕКОМЕНДУЕТСЯ):**
|
||||||
|
|
||||||
|
Используйте код из файла `N8N_CODE_PREPARE_EMAIL_ATTACHMENTS_WORKING.js`
|
||||||
|
|
||||||
|
Этот код:
|
||||||
|
- ✅ Успешно обработан и протестирован
|
||||||
|
- ✅ Правильно обрабатывает бинарные данные из webhook
|
||||||
|
- ✅ Создает правильный `attachment_field` для узла "Send email"
|
||||||
|
- ✅ Сохраняет бинарные данные в правильном формате
|
||||||
|
|
||||||
|
**Альтернативные варианты (если основной не подходит):**
|
||||||
|
|
||||||
|
**Вариант A: Code Node (если бинарные данные нужно переименовать)**
|
||||||
|
|
||||||
|
Используйте код из файла `N8N_CODE_PREPARE_EMAIL_ATTACHMENTS.js`
|
||||||
|
|
||||||
|
**Вариант B: Code Node (простая версия - если бинарные данные уже в правильном формате)**
|
||||||
|
|
||||||
|
Используйте код из файла `N8N_CODE_PREPARE_EMAIL_ATTACHMENTS_SIMPLE.js`
|
||||||
|
|
||||||
|
**Вариант C: Function Node (если Code Node не передает binary)**
|
||||||
|
|
||||||
|
Используйте код из файла `N8N_FUNCTION_PREPARE_EMAIL_ATTACHMENTS.js`
|
||||||
|
|
||||||
|
### 2. Узел "Send email"
|
||||||
|
|
||||||
|
**В поле "Attachments" используйте:**
|
||||||
|
```
|
||||||
|
{{ $json.attachment_field }}
|
||||||
|
```
|
||||||
|
|
||||||
|
**⚠️ ВАЖНО:** Это должна быть **СТРОКА**, а не массив!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 Проверка
|
||||||
|
|
||||||
|
### Шаг 1: Проверьте OUTPUT Code Node
|
||||||
|
|
||||||
|
После выполнения Code Node проверьте OUTPUT панель:
|
||||||
|
|
||||||
|
1. **Должно быть поле `attachment_field`** (строка):
|
||||||
|
```
|
||||||
|
"attachment_field": "file_0,file_1,file_2,file_3"
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Должен быть объект `binary`** с бинарными данными:
|
||||||
|
```
|
||||||
|
binary: {
|
||||||
|
file_0: { data: ..., fileName: "..." },
|
||||||
|
file_1: { data: ..., fileName: "..." },
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Шаг 2: Проверьте настройки узла "Send email"
|
||||||
|
|
||||||
|
1. Откройте узел "Send email"
|
||||||
|
2. Найдите поле **"Attachments"** (не "Attachment"!)
|
||||||
|
3. Убедитесь, что там указано: `{{ $json.attachment_field }}`
|
||||||
|
4. **НЕ используйте** выражения типа:
|
||||||
|
- `{{ $json.attachments }}` ❌
|
||||||
|
- `{{ $json.attachment_keys }}` ❌
|
||||||
|
- `{{ Object.keys($binary) }}` ❌
|
||||||
|
|
||||||
|
### Шаг 3: Проверьте передачу бинарных данных
|
||||||
|
|
||||||
|
Убедитесь, что бинарные данные передаются между узлами:
|
||||||
|
|
||||||
|
1. Code Node должен возвращать:
|
||||||
|
```javascript
|
||||||
|
return [{
|
||||||
|
json: { ... },
|
||||||
|
binary: { file_0: ..., file_1: ... } // ⚠️ Это обязательно!
|
||||||
|
}];
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Узел "Send email" должен быть подключен **напрямую** к Code Node (без промежуточных узлов, которые могут потерять бинарные данные)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🐛 Если ошибка "Received undefined" все еще возникает
|
||||||
|
|
||||||
|
**Эта ошибка означает, что бинарные данные не передаются в узел "Send email"!**
|
||||||
|
|
||||||
|
### ⚠️ Проблема: Бинарные данные теряются между узлами
|
||||||
|
|
||||||
|
**Симптомы:**
|
||||||
|
- В OUTPUT Code Node нет раздела "Binary"
|
||||||
|
- Ошибка: "The first argument must be of type string or an instance of Buffer... Received undefined"
|
||||||
|
- `attachment_field` есть, но файлы не прикрепляются
|
||||||
|
|
||||||
|
**Решения:**
|
||||||
|
|
||||||
|
### Вариант 1: Используйте Function Node вместо Code Node
|
||||||
|
|
||||||
|
Function Node лучше сохраняет бинарные данные:
|
||||||
|
1. Замените Code Node на **Function Node**
|
||||||
|
2. Используйте код из `N8N_FUNCTION_PREPARE_EMAIL_ATTACHMENTS.js`
|
||||||
|
3. Проверьте OUTPUT - должен быть раздел "Binary"
|
||||||
|
|
||||||
|
### Вариант 2: Используйте простую версию кода
|
||||||
|
|
||||||
|
Если бинарные данные уже приходят с правильными именами (`file_0`, `file_1`, etc.):
|
||||||
|
1. Используйте код из `N8N_CODE_PREPARE_EMAIL_ATTACHMENTS_SIMPLE.js`
|
||||||
|
2. Этот код просто передает бинарные данные без изменений
|
||||||
|
|
||||||
|
### Вариант 3: Проверьте цепочку узлов
|
||||||
|
|
||||||
|
Убедитесь, что между узлом с бинарными данными и "Send email" нет узлов, которые могут потерять binary:
|
||||||
|
- ❌ "Set" node может потерять binary
|
||||||
|
- ❌ "Edit Fields" может потерять binary
|
||||||
|
- ✅ "Code" / "Function" node сохраняет binary (если правильно настроен)
|
||||||
|
- ✅ Прямое подключение лучше всего
|
||||||
|
|
||||||
|
### Вариант 4: Проверьте имя поля в узле "Send email"
|
||||||
|
|
||||||
|
В некоторых версиях n8n поле может называться по-другому:
|
||||||
|
- "Attachments" (множественное число)
|
||||||
|
- "Attachment" (единственное число)
|
||||||
|
- "Attachments Field"
|
||||||
|
|
||||||
|
Проверьте все варианты.
|
||||||
|
|
||||||
|
### Вариант 2: Используйте Function Node вместо Code Node
|
||||||
|
|
||||||
|
Если Code Node не передает бинарные данные правильно, используйте Function Node:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const items = $input.all();
|
||||||
|
|
||||||
|
return items.map(item => {
|
||||||
|
const binary = {};
|
||||||
|
const attachmentString = [];
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
for (const key of Object.keys(item.binary || {})) {
|
||||||
|
const newKey = `file_${i}`;
|
||||||
|
binary[newKey] = item.binary[key];
|
||||||
|
attachmentString.push(newKey);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
json: {
|
||||||
|
...item.json,
|
||||||
|
attachment_field: attachmentString.join(',')
|
||||||
|
},
|
||||||
|
binary: binary
|
||||||
|
};
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Вариант 3: Проверьте версию n8n
|
||||||
|
|
||||||
|
В разных версиях n8n узел "Send email" может работать по-разному. Проверьте документацию для вашей версии:
|
||||||
|
- n8n 1.0+: обычно ожидает строку
|
||||||
|
- n8n 0.x: может ожидать массив
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Пример правильной настройки
|
||||||
|
|
||||||
|
```
|
||||||
|
Webhook → Process Files → Code Node → Send Email
|
||||||
|
↓
|
||||||
|
(attachment_field: "file_0,file_1,...")
|
||||||
|
↓
|
||||||
|
(binary: {file_0: {...}, file_1: {...}})
|
||||||
|
↓
|
||||||
|
[Send Email: Attachments = {{ $json.attachment_field }}]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ❓ Частые вопросы
|
||||||
|
|
||||||
|
**Q: Почему ошибка "split is not a function"?**
|
||||||
|
A: Узел "Send email" пытается вызвать `.split()` на поле Attachments, значит он ожидает строку, а получает массив или объект.
|
||||||
|
|
||||||
|
**Q: Почему файлы не прикрепляются?**
|
||||||
|
A: Проверьте, что:
|
||||||
|
1. Бинарные данные переданы в объекте `binary`
|
||||||
|
2. Ключи в `binary` совпадают с именами в `attachment_field`
|
||||||
|
3. Узел "Send email" подключен напрямую к Code Node
|
||||||
|
|
||||||
|
**Q: Можно ли использовать массив?**
|
||||||
|
A: Нет, узел "Send email" в вашей версии n8n ожидает строку. Используйте `{{ $json.attachment_field }}`.
|
||||||
|
|
||||||
199
ticket_form/docs/N8N_EMAIL_ATTACHMENTS_TROUBLESHOOTING.md
Normal file
199
ticket_form/docs/N8N_EMAIL_ATTACHMENTS_TROUBLESHOOTING.md
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
# Диагностика проблемы с вложениями в n8n "Send email"
|
||||||
|
|
||||||
|
## ✅ Симптом: В OUTPUT Code Node есть раздел "Binary" с файлами
|
||||||
|
|
||||||
|
Если бинарные данные есть в OUTPUT Code Node, но ошибка "Received undefined" все еще возникает, проблема в том, как узел "Send email" получает эти данные.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 Пошаговая диагностика
|
||||||
|
|
||||||
|
### Шаг 1: Проверьте цепочку узлов
|
||||||
|
|
||||||
|
**Вопрос:** Есть ли промежуточные узлы между Code Node и "Send email"?
|
||||||
|
|
||||||
|
```
|
||||||
|
Code Node → [промежуточные узлы?] → Send email
|
||||||
|
```
|
||||||
|
|
||||||
|
**Проверка:**
|
||||||
|
- ❌ Если есть узлы типа "Set", "Edit Fields", "IF" → они могут потерять binary
|
||||||
|
- ✅ Узел "Send email" должен быть подключен **напрямую** к Code Node
|
||||||
|
|
||||||
|
**Решение:** Уберите промежуточные узлы или переместите их после "Send email".
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Шаг 2: Проверьте поле "Attachments" в узле "Send email"
|
||||||
|
|
||||||
|
**Откройте узел "Send email" и проверьте:**
|
||||||
|
|
||||||
|
1. **Поле называется "Attachments" (множественное число)?**
|
||||||
|
- ✅ Правильно: "Attachments"
|
||||||
|
- ❌ Неправильно: "Attachment" (единственное число)
|
||||||
|
|
||||||
|
2. **Что указано в поле "Attachments"?**
|
||||||
|
- ✅ Правильно: `{{ $json.attachment_field }}`
|
||||||
|
- ❌ Неправильно: `{{ $json.attachments }}`
|
||||||
|
- ❌ Неправильно: `{{ $json.attachment_keys }}`
|
||||||
|
- ❌ Неправильно: `{{ Object.keys($binary) }}`
|
||||||
|
|
||||||
|
3. **Проверьте значение поля:**
|
||||||
|
- Нажмите на поле "Attachments"
|
||||||
|
- Должна появиться строка вида: `"file_0,file_1,file_2"`
|
||||||
|
- Если видите массив `["file_0","file_1"]` → это неправильно!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Шаг 3: Проверьте INPUT узла "Send email"
|
||||||
|
|
||||||
|
**Откройте INPUT панель узла "Send email":**
|
||||||
|
|
||||||
|
1. **Должен быть раздел "Binary"** с файлами `file_0`, `file_1`, etc.
|
||||||
|
- ✅ Если есть → бинарные данные дошли до узла
|
||||||
|
- ❌ Если нет → бинарные данные потерялись между узлами
|
||||||
|
|
||||||
|
2. **Проверьте раздел "JSON":**
|
||||||
|
- Должно быть поле `attachment_field: "file_0,file_1,file_2"`
|
||||||
|
- Если поля нет → проблема в Code Node
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Шаг 4: Проверьте версию n8n
|
||||||
|
|
||||||
|
**Разные версии n8n могут работать по-разному:**
|
||||||
|
|
||||||
|
- **n8n 1.0+**: Обычно ожидает строку в поле Attachments
|
||||||
|
- **n8n 0.x**: Может ожидать массив
|
||||||
|
|
||||||
|
**Проверьте версию:** Настройки → О системе
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ Решения
|
||||||
|
|
||||||
|
### Решение 1: Используйте прямое подключение
|
||||||
|
|
||||||
|
```
|
||||||
|
Webhook → Process Files → Code Node → Send Email
|
||||||
|
↑
|
||||||
|
(напрямую, без промежуточных узлов)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Убедитесь, что:**
|
||||||
|
- Нет узлов между Code Node и Send email
|
||||||
|
- Code Node возвращает `binary: binary`
|
||||||
|
- Send email подключен напрямую к Code Node
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Решение 2: Проверьте синтаксис в поле Attachments
|
||||||
|
|
||||||
|
**Попробуйте разные варианты:**
|
||||||
|
|
||||||
|
**Вариант A (рекомендуется):**
|
||||||
|
```
|
||||||
|
{{ $json.attachment_field }}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Вариант B (если вариант A не работает):**
|
||||||
|
```
|
||||||
|
{{ $json.attachment_field.toString() }}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Вариант C (если бинарные данные есть в INPUT):**
|
||||||
|
```
|
||||||
|
{{ Object.keys($binary).join(',') }}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Вариант D (прямое указание, если файлов немного):**
|
||||||
|
```
|
||||||
|
file_0,file_1,file_2
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Решение 3: Используйте Function Node вместо Code Node
|
||||||
|
|
||||||
|
Если Code Node не передает binary правильно:
|
||||||
|
|
||||||
|
1. Замените Code Node на **Function Node**
|
||||||
|
2. Используйте код из `N8N_FUNCTION_PREPARE_EMAIL_ATTACHMENTS.js`
|
||||||
|
3. Проверьте, что в OUTPUT Function Node есть раздел "Binary"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Решение 4: Проверьте настройки узла "Send email"
|
||||||
|
|
||||||
|
**В настройках узла "Send email" (вкладка "Settings"):**
|
||||||
|
|
||||||
|
1. **"Keep Only Set Fields"** должна быть **выключена**
|
||||||
|
- Если включена → узел может игнорировать бинарные данные
|
||||||
|
|
||||||
|
2. **"Continue On Fail"** - не влияет на binary, но проверьте
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Решение 5: Используйте альтернативный способ
|
||||||
|
|
||||||
|
Если ничего не помогает, попробуйте использовать HTTP Request для отправки email:
|
||||||
|
|
||||||
|
1. Используйте HTTP Request node вместо "Send email"
|
||||||
|
2. Настройте SMTP API (если доступно)
|
||||||
|
3. Передайте бинарные данные через multipart/form-data
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Чек-лист проверки
|
||||||
|
|
||||||
|
- [ ] В OUTPUT Code Node есть раздел "Binary" с файлами
|
||||||
|
- [ ] В INPUT "Send email" есть раздел "Binary" с файлами
|
||||||
|
- [ ] Нет промежуточных узлов между Code Node и Send email
|
||||||
|
- [ ] В поле "Attachments" указано: `{{ $json.attachment_field }}`
|
||||||
|
- [ ] Значение `attachment_field` - это строка, а не массив
|
||||||
|
- [ ] Имена файлов в `attachment_field` совпадают с ключами в binary
|
||||||
|
- [ ] Версия n8n поддерживает такой формат
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🐛 Частые ошибки
|
||||||
|
|
||||||
|
### Ошибка 1: "Received undefined"
|
||||||
|
|
||||||
|
**Причина:** Узел "Send email" не может найти бинарные данные по указанным именам.
|
||||||
|
|
||||||
|
**Решение:**
|
||||||
|
1. Проверьте, что имена в `attachment_field` точно совпадают с ключами в binary
|
||||||
|
2. Убедитесь, что нет пробелов: `"file_0,file_1"` (правильно), а не `"file_0, file_1"` (неправильно)
|
||||||
|
|
||||||
|
### Ошибка 2: "property.split is not a function"
|
||||||
|
|
||||||
|
**Причина:** В поле Attachments передан массив вместо строки.
|
||||||
|
|
||||||
|
**Решение:**
|
||||||
|
- Используйте `{{ $json.attachment_field }}` (строка)
|
||||||
|
- НЕ используйте `{{ $json.attachment_keys }}` (массив)
|
||||||
|
|
||||||
|
### Ошибка 3: Бинарные данные есть в Code Node, но нет в Send email
|
||||||
|
|
||||||
|
**Причина:** Промежуточный узел потерял binary.
|
||||||
|
|
||||||
|
**Решение:**
|
||||||
|
- Уберите промежуточные узлы
|
||||||
|
- Подключите Send email напрямую к Code Node
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💡 Дополнительные советы
|
||||||
|
|
||||||
|
1. **Используйте Execute Workflow для тестирования:**
|
||||||
|
- Создайте простой workflow только с Code Node и Send email
|
||||||
|
- Проверьте, работает ли он изолированно
|
||||||
|
|
||||||
|
2. **Проверьте логи n8n:**
|
||||||
|
- Логи могут показать, где именно теряются бинарные данные
|
||||||
|
|
||||||
|
3. **Попробуйте другой узел для отправки email:**
|
||||||
|
- Некоторые узлы лучше работают с бинарными данными
|
||||||
|
- Попробуйте "Gmail" или "Outlook" узлы, если они доступны
|
||||||
|
|
||||||
51
ticket_form/docs/N8N_FUNCTION_PREPARE_EMAIL_ATTACHMENTS.js
Normal file
51
ticket_form/docs/N8N_FUNCTION_PREPARE_EMAIL_ATTACHMENTS.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// ============================================================================
|
||||||
|
// n8n Function Node: Подготовка бинарных данных для отправки email с вложениями
|
||||||
|
// ============================================================================
|
||||||
|
// Используйте этот код в Function Node (не Code Node!)
|
||||||
|
// Function Node лучше работает с бинарными данными в некоторых версиях n8n
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Получаем все элементы из входных данных
|
||||||
|
const items = $input.all();
|
||||||
|
|
||||||
|
// Обрабатываем каждый элемент
|
||||||
|
return items.map(item => {
|
||||||
|
// Получаем бинарные данные из элемента
|
||||||
|
const inputBinary = item.binary || {};
|
||||||
|
|
||||||
|
// Получаем JSON данные
|
||||||
|
const jsonData = item.json || {};
|
||||||
|
|
||||||
|
// Проверяем наличие бинарных данных
|
||||||
|
if (!inputBinary || Object.keys(inputBinary).length === 0) {
|
||||||
|
return {
|
||||||
|
json: {
|
||||||
|
...jsonData,
|
||||||
|
attachment_field: '',
|
||||||
|
error: 'No binary data found',
|
||||||
|
debug: {
|
||||||
|
hasBinary: !!inputBinary,
|
||||||
|
binaryKeys: Object.keys(inputBinary || {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем все ключи бинарных данных
|
||||||
|
const binaryKeys = Object.keys(inputBinary);
|
||||||
|
|
||||||
|
// Создаем строку с именами бинарных свойств через запятую
|
||||||
|
const attachmentField = binaryKeys.join(',');
|
||||||
|
|
||||||
|
// ⚠️ КРИТИЧНО: Возвращаем объект с json И binary
|
||||||
|
// В Function Node бинарные данные передаются через поле binary
|
||||||
|
return {
|
||||||
|
json: {
|
||||||
|
...jsonData,
|
||||||
|
attachment_field: attachmentField
|
||||||
|
},
|
||||||
|
// ⚠️ ВАЖНО: Передаем бинарные данные БЕЗ ИЗМЕНЕНИЙ
|
||||||
|
binary: inputBinary
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
Reference in New Issue
Block a user