UNPKG

n8n-rdstation-crm

Version:

Nós personalizados do n8n para integração com a API do RD Station CRM (v1)

553 lines (508 loc) 18.4 kB
import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription, NodeOperationError, } from 'n8n-workflow'; import { OptionsWithUri } from 'request-promise-native'; /** * Classe para o nó de Empresas do RD Station CRM * Este nó permite interações com a API de Empresas (Organizations) do RD Station CRM. * As empresas são entidades jurídicas que podem agrupar contatos e negócios no CRM. */ export class RdStationCrmCompanies implements INodeType { description: INodeTypeDescription = { // Informações básicas do nó que serão exibidas na interface do n8n displayName: 'RD Station CRM Empresas', name: 'rdStationCrmCompanies', icon: 'file:rdstation.svg', // Ícone a ser exibido no n8n group: ['transform'], // Grupo onde o nó aparecerá na interface version: 1, // Versão do nó subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', // Subtítulo dinâmico baseado nos parâmetros description: 'Operações com empresas no RD Station CRM', // Descrição do nó defaults: { name: 'RD Station CRM Empresas', // Nome padrão quando o nó é adicionado }, inputs: ['main'], // Define que o nó aceita uma entrada principal outputs: ['main'], // Define que o nó tem uma saída principal credentials: [ { // Define que o nó requer credenciais do tipo rdStationCrmApi name: 'rdStationCrmApi', required: true, // As credenciais são obrigatórias }, ], properties: [ // Seleção do recurso (neste caso, só temos empresas/organizations) { displayName: 'Recurso', // Label exibido na interface name: 'resource', // Nome do parâmetro para referência interna type: 'options', // Tipo do campo (seleção entre opções) noDataExpression: true, // Não permite expressões dinâmicas options: [ { name: 'Empresa', // Nome amigável da opção value: 'organization', // Valor interno da opção (organization é o termo usado na API) }, ], default: 'organization', // Valor padrão selecionado required: true, // Campo obrigatório }, // Seleção da operação a ser realizada sobre as empresas { displayName: 'Operação', // Label do campo na interface name: 'operation', // Nome do parâmetro internamente type: 'options', // Campo de seleção entre opções noDataExpression: true, // Não permite expressões dinâmicas displayOptions: { show: { // Mostra este campo apenas quando o recurso for 'organization' resource: [ 'organization', ], }, }, options: [ // Lista de operações disponíveis para empresas { name: 'Criar', // Operação de criação value: 'create', description: 'Criar uma nova empresa', action: 'Criar uma empresa', }, { name: 'Atualizar', // Operação de atualização value: 'update', description: 'Atualizar uma empresa existente', action: 'Atualizar uma empresa', }, { name: 'Listar', // Operação de listagem value: 'getAll', description: 'Listar empresas', action: 'Listar empresas', }, { name: 'Obter', // Operação para obter detalhes value: 'get', description: 'Obter uma empresa pelo ID', action: 'Obter uma empresa', }, { name: 'Listar Contatos da Empresa', // Operação para listar contatos associados value: 'getContacts', description: 'Listar contatos associados a uma empresa', action: 'Listar contatos de uma empresa', }, ], default: 'getAll', // Operação padrão }, // Campos para a operação LISTAR EMPRESAS { displayName: 'Parâmetros de Listagem', // Título da seção name: 'listParameters', // Nome para referência interna type: 'collection', // Tipo coleção permite múltiplos parâmetros agrupados placeholder: 'Adicionar Parâmetros', // Texto de placeholder default: {}, // Valor padrão (vazio) displayOptions: { show: { // Mostra apenas quando recurso é 'organization' e operação é 'getAll' resource: [ 'organization', ], operation: [ 'getAll', ], }, }, options: [ // Opções de filtragem e paginação para listagem { displayName: 'Limite', // Controle de quantos itens retornar name: 'limit', type: 'number', default: 50, description: 'Número máximo de empresas a retornar', }, { displayName: 'Página', // Para navegação entre páginas name: 'page', type: 'number', default: 1, description: 'Número da página para retornar', }, { displayName: 'Busca por Nome', // Busca textual no nome name: 'q', type: 'string', default: '', description: 'Buscar empresas por nome', }, { displayName: 'ID do Segmento', // Filtrar por segmento de mercado name: 'organization_segment_id', type: 'string', default: '', description: 'Filtrar empresas por segmento específico', }, { displayName: 'ID do Usuário Responsável', // Filtrar por responsável name: 'user_id', type: 'string', default: '', description: 'Filtrar empresas por usuário responsável', }, { displayName: 'Ordenação', // Campo para ordenar resultados name: 'order', type: 'options', options: [ { name: 'Nome', // Ordem alfabética por nome value: 'name', }, { name: 'Data de Criação', // Ordena por quando foi criada value: 'created_at', }, ], default: 'name', description: 'Campo pelo qual ordenar os resultados', }, { displayName: 'Direção da Ordenação', // Crescente ou decrescente name: 'direction', type: 'options', options: [ { name: 'Ascendente', // A-Z ou mais antigo para mais recente value: 'asc', }, { name: 'Descendente', // Z-A ou mais recente para mais antigo value: 'desc', }, ], default: 'asc', description: 'Direção da ordenação (crescente ou decrescente)', }, ], }, // Campos para a operação OBTER EMPRESA ou LISTAR CONTATOS DA EMPRESA { displayName: 'ID da Empresa', // Campo para informar ID da empresa name: 'organizationId', type: 'string', // Tipo texto para o ID default: '', required: true, // Campo obrigatório displayOptions: { show: { // Mostrar apenas quando operação for 'get', 'update' ou 'getContacts' resource: [ 'organization', ], operation: [ 'get', 'update', 'getContacts', ], }, }, description: 'ID da empresa a ser consultada/atualizada', }, // Campos comuns para CRIAR e ATUALIZAR empresas { displayName: 'Dados da Empresa', // Seção para dados da empresa name: 'organizationData', type: 'collection', // Agrupamento de campos placeholder: 'Adicionar Dados', // Texto de placeholder default: {}, // Valor padrão (vazio) displayOptions: { show: { resource: [ 'organization', ], operation: [ 'create', 'update', ], }, }, options: [ // Campos de dados da empresa { displayName: 'Nome', // Nome da empresa name: 'name', type: 'string', default: '', description: 'Nome da empresa (obrigatório para criação, mínimo 2 caracteres, deve ser único)', }, { displayName: 'ID do Segmento', // Segmento de mercado name: 'segment_id', type: 'string', default: '', description: 'ID do segmento da empresa', }, { displayName: 'Site', // Website da empresa name: 'website', type: 'string', default: '', description: 'URL do site da empresa', }, { displayName: 'E-mail', // E-mail geral da empresa name: 'email', type: 'string', default: '', description: 'E-mail de contato geral da empresa', }, { displayName: 'Endereço', // Endereço físico name: 'address', type: 'string', default: '', description: 'Endereço da empresa', }, ], }, // Telefones da empresa (para CRIAR e ATUALIZAR) { displayName: 'Telefones', // Seção para números de telefone name: 'phones', placeholder: 'Adicionar Telefone', // Texto de placeholder type: 'fixedCollection', // Coleção com estrutura fixa typeOptions: { multipleValues: true, // Permite múltiplos telefones }, displayOptions: { show: { resource: [ 'organization', ], operation: [ 'create', 'update', ], }, }, default: {}, // Valor padrão (vazio) options: [ { name: 'phonesValues', // Nome interno da coleção displayName: 'Telefone', // Título exibido na interface values: [ { displayName: 'Número', // Campo para número de telefone name: 'phone', type: 'string', default: '', description: 'Número de telefone da empresa', required: true, // Número é obrigatório se um telefone for adicionado }, { displayName: 'Tipo', // Categorização do telefone name: 'type', type: 'options', options: [ { name: 'Trabalho', // Telefone comercial/principal value: 'work', }, { name: 'Fax', // Número de fax value: 'fax', }, { name: 'Outro', // Qualquer outro tipo value: 'other', }, ], default: 'work', // Padrão é telefone comercial description: 'Tipo de telefone', }, ], }, ], }, ], }; /** * Método principal que executa o nó com base na operação selecionada * Este método é chamado pelo n8n quando o fluxo é executado */ async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> { const items = this.getInputData(); // Obtém os dados de entrada const returnData: INodeExecutionData[] = []; // Array para armazenar resultados // URL base da API do RD Station CRM const baseUrl = 'https://crm.rdstation.com/api/v1'; const resource = this.getNodeParameter('resource', 0) as string; // Obtém o recurso selecionado const operation = this.getNodeParameter('operation', 0) as string; // Obtém a operação selecionada // Para cada item de entrada, processa a operação solicitada for (let i = 0; i < items.length; i++) { try { // Configuração comum para todas as requisições HTTP const options: OptionsWithUri = { uri: '', // Uri inicial vazio, será definido conforme a operação headers: { 'Accept': 'application/json', // Aceita respostas em JSON 'Content-Type': 'application/json', // Envia dados em formato JSON }, json: true, // Processa automaticamente respostas JSON resolveWithFullResponse: true, // Retorna o objeto de resposta completo }; // Vamos determinar qual operação executar com base no recurso e operação selecionados if (resource === 'organization') { if (operation === 'getAll') { // Operação: LISTAR EMPRESAS // Preparando a requisição para listar empresas options.method = 'GET'; // Método HTTP GET para listar options.uri = `${baseUrl}/organizations`; // Endpoint de organizations (empresas) // Obtendo e aplicando parâmetros de filtro/paginação definidos pelo usuário const listParameters = this.getNodeParameter('listParameters', i) as { limit?: number; page?: number; q?: string; organization_segment_id?: string; user_id?: string; order?: string; direction?: string; }; const qs: Record<string, any> = {}; // Objeto para parâmetros de query string // Adicionando os parâmetros à query string se estiverem definidos if (listParameters.limit) qs.limit = listParameters.limit; if (listParameters.page) qs.page = listParameters.page; if (listParameters.q) qs.q = listParameters.q; if (listParameters.organization_segment_id) qs.organization_segment_id = listParameters.organization_segment_id; if (listParameters.user_id) qs.user_id = listParameters.user_id; if (listParameters.order) qs.order = listParameters.order; if (listParameters.direction) qs.direction = listParameters.direction; options.qs = qs; // Anexa os parâmetros à requisição } else if (operation === 'get') { // Operação: OBTER EMPRESA ESPECÍFICA // Obtém o ID da empresa a ser consultada const organizationId = this.getNodeParameter('organizationId', i) as string; // Verifica se o ID da empresa foi fornecido if (!organizationId) { throw new NodeOperationError(this.getNode(), 'É necessário fornecer um ID de empresa válido!', { itemIndex: i }); } // Configurando a requisição para buscar uma empresa específica options.method = 'GET'; options.uri = `${baseUrl}/organizations/${organizationId}`; } else if (operation === 'getContacts') { // Operação: LISTAR CONTATOS DA EMPRESA // Obtém o ID da empresa para listar seus contatos const organizationId = this.getNodeParameter('organizationId', i) as string; // Verifica se o ID da empresa foi fornecido if (!organizationId) { throw new NodeOperationError(this.getNode(), 'É necessário fornecer um ID de empresa válido!', { itemIndex: i }); } // Configurando a requisição para buscar contatos de uma empresa específica options.method = 'GET'; options.uri = `${baseUrl}/organizations/${organizationId}/contacts`; } else if (operation === 'create') { // Operação: CRIAR EMPRESA // Configurando requisição para criar empresa options.method = 'POST'; // Método HTTP POST para criação options.uri = `${baseUrl}/organizations`; // Endpoint de organizations // Obtendo os dados da empresa const organizationData = this.getNodeParameter('organizationData', i) as { name?: string; segment_id?: string; website?: string; email?: string; address?: string; }; // Verificando se o nome foi fornecido (obrigatório para criação) if (!organizationData.name || organizationData.name.length < 2) { throw new NodeOperationError( this.getNode(), 'O nome da empresa é obrigatório e deve ter pelo menos 2 caracteres!', { itemIndex: i }, ); } // Preparando o corpo da requisição const body: any = { ...organizationData }; // Adicionando telefones ao corpo const phonesCollection = this.getNodeParameter('phones', i) as { phonesValues?: Array<{ phone: string; type: string }>; }; if (phonesCollection.phonesValues && phonesCollection.phonesValues.length > 0) { body.phones = phonesCollection.phonesValues; } options.body = body; // Anexa o corpo à requisição } else if (operation === 'update') { // Operação: ATUALIZAR EMPRESA // Obtém o ID da empresa a ser atualizada const organizationId = this.getNodeParameter('organizationId', i) as string; // Verifica se o ID da empresa foi fornecido if (!organizationId) { throw new NodeOperationError( this.getNode(), 'É necessário fornecer um ID de empresa válido para atualização!', { itemIndex: i }, ); } // Configurando a requisição para atualizar a empresa options.method = 'PUT'; // Método HTTP PUT para atualização options.uri = `${baseUrl}/organizations/${organizationId}`; // Endpoint com ID da empresa // Obtendo os dados a serem atualizados const organizationData = this.getNodeParameter('organizationData', i) as { name?: string; segment_id?: string; website?: string; email?: string; address?: string; }; // Preparando o corpo da requisição const body: any = { ...organizationData }; // Adicionando telefones ao corpo, se fornecidos const phonesCollection = this.getNodeParameter('phones', i) as { phonesValues?: Array<{ phone: string; type: string }>; }; if (phonesCollection.phonesValues && phonesCollection.phonesValues.length > 0) { body.phones = phonesCollection.phonesValues; } // Verifica se há campos para atualizar if (Object.keys(body).length === 0) { throw new NodeOperationError( this.getNode(), 'É necessário fornecer pelo menos um campo para atualizar a empresa!', { itemIndex: i }, ); } options.body = body; // Anexa o corpo à requisição } } // Executa a requisição HTTP com as opções configuradas e autenticação const response = await this.helpers.requestWithAuthentication.call( this, 'rdStationCrmApi', // Usa as credenciais configuradas options, // Opções da requisição ); // Extrai os dados da resposta e adiciona ao retorno const responseData = response.body; returnData.push({ json: responseData, // Dados da resposta em formato JSON pairedItem: { item: i }, // Mantém o pareamento com o item de entrada }); } catch (error) { // Trata e reporta erros ocorridos durante a execução if (this.continueOnFail()) { // Se configurado para continuar mesmo com falhas returnData.push({ json: { error: error instanceof Error ? error.message : String(error), // Inclui a mensagem de erro }, pairedItem: { item: i }, }); continue; // Continua para o próximo item } throw error; // Senão, propaga o erro para interromper a execução } } return [returnData]; // Retorna os dados processados } }