diff --git a/ticket_form/docs/N8N_CODE_PREPARE_EMAIL_ATTACHMENTS.js b/ticket_form/docs/N8N_CODE_PREPARE_EMAIL_ATTACHMENTS.js new file mode 100644 index 00000000..40bbe20a --- /dev/null +++ b/ticket_form/docs/N8N_CODE_PREPARE_EMAIL_ATTACHMENTS.js @@ -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]; + diff --git a/ticket_form/docs/N8N_CODE_PREPARE_EMAIL_ATTACHMENTS_SIMPLE.js b/ticket_form/docs/N8N_CODE_PREPARE_EMAIL_ATTACHMENTS_SIMPLE.js new file mode 100644 index 00000000..ab7937b0 --- /dev/null +++ b/ticket_form/docs/N8N_CODE_PREPARE_EMAIL_ATTACHMENTS_SIMPLE.js @@ -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 +}]; + diff --git a/ticket_form/docs/N8N_CODE_PREPARE_EMAIL_ATTACHMENTS_WORKING.js b/ticket_form/docs/N8N_CODE_PREPARE_EMAIL_ATTACHMENTS_WORKING.js new file mode 100644 index 00000000..abb58433 --- /dev/null +++ b/ticket_form/docs/N8N_CODE_PREPARE_EMAIL_ATTACHMENTS_WORKING.js @@ -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 +}]; + diff --git a/ticket_form/docs/N8N_EMAIL_ATTACHMENTS_FIX.md b/ticket_form/docs/N8N_EMAIL_ATTACHMENTS_FIX.md new file mode 100644 index 00000000..3b87685d --- /dev/null +++ b/ticket_form/docs/N8N_EMAIL_ATTACHMENTS_FIX.md @@ -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/ + diff --git a/ticket_form/docs/N8N_EMAIL_ATTACHMENTS_QUICK_FIX.md b/ticket_form/docs/N8N_EMAIL_ATTACHMENTS_QUICK_FIX.md new file mode 100644 index 00000000..c2c4de61 --- /dev/null +++ b/ticket_form/docs/N8N_EMAIL_ATTACHMENTS_QUICK_FIX.md @@ -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 }}`. + diff --git a/ticket_form/docs/N8N_EMAIL_ATTACHMENTS_TROUBLESHOOTING.md b/ticket_form/docs/N8N_EMAIL_ATTACHMENTS_TROUBLESHOOTING.md new file mode 100644 index 00000000..124386fa --- /dev/null +++ b/ticket_form/docs/N8N_EMAIL_ATTACHMENTS_TROUBLESHOOTING.md @@ -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" узлы, если они доступны + diff --git a/ticket_form/docs/N8N_FUNCTION_PREPARE_EMAIL_ATTACHMENTS.js b/ticket_form/docs/N8N_FUNCTION_PREPARE_EMAIL_ATTACHMENTS.js new file mode 100644 index 00000000..852acdfd --- /dev/null +++ b/ticket_form/docs/N8N_FUNCTION_PREPARE_EMAIL_ATTACHMENTS.js @@ -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 + }; +}); +