Registration

Dear SAP Community Member,
In order to fully benefit from what the SAP Community has to offer, please register at:
http://scn.sap.com
Thank you,
The SAP Community team.
Skip to end of metadata
Go to start of metadata

1. Introdução

1.1 Detalhes

1.1.1 Área(s) de Aplicação

Nota Fiscal Eletrônica - NFe (SLL-NFE 1.0)
SAP Process Integration (PI) 7.0

1.1.2 Autor

Ricardo Guedes

Created on: 12/02/2008
Biografia do(s) Autor(es):
Trabalho na Neoris desde 06/2007 como consultor de SAP PI e SAP Portal onde atuo em projetos de Nota Fiscal Eletrônica, de integrações de sistemas SAP/não-SAP e implantação de portais corporativos.

1.1.3 Referências

Configuração do B2B de NFe usando RFC Lookup
Configuração do Enhanced Receiver Determination
Thread do Fórum com a discussão sobre este tema

1.1.4 Colaboração

Colaboraram para o desenvolvimento desta solução:

Raphael Xavier
Henrique Pinto

1.2 Cenário

Em diversos projetos de implantação de Nota Fiscal Eletrônica (NFe) faz-se necessário o envio dos arquivos XML das NFes para os clientes. Para que os envios possam ser feitos por diversas formas e não apenas uma, desenvolvimentos adicionais são necessários aos já criados para os envios por apenas uma forma.

Qualquer item que venha a melhorar a solução será bem vindo.

2. Preparação

2.1 Etapas da solução

Para implementar esta solução, serão necessárias:

  • Tabela para armazenar os dados necessários referentes a cada uma das formas de envio. Por exemplo, endereço de e-mail, link do WebService, pasta de destino para o arquivo, etc;
  • RFC para buscar os dados;
  • Cenário do B2B de saída (NTB2B);
  • Alteração da User-Defined Function (UDF) do message mapping do cenário de B2B;
  • Criar um Adapter Module para trocar o nome do arquivo anexo do e-mail e inserir um texto no corpo do e-mail.
  • Configurar e implementar o Adapter Specific Message Attributes (ASMA) para passar os parâmetros para os adaptadores dinamicamente;
  • Implementar o Enhanced Receiver Determination para escolher dinamicamente qual serviço receberá a mensagem;

3. Desenvolvimento ABAP

3.1 Tabela para armazenar os dados

É necessária a utilização de uma tabela para armazenar as informações sobre qual forma o cliente deseja receber os XMLs de NFes emitidas.

Como é necessário utilizar os campos CNPJ e B2B Ativo, a sugestão é extender a tabela /XNFE/TB2B, fazendo os includes de acordo com a necessidade. Neste caso, foram adicionados os seguintes campos:

  • Nome (para melhor identificar os clientes na tabela);
  • Forma de envio (1-E-mail, 2-WebService e 3-Arquivo);
  • E-mail;
  • Pasta;
  • URL do WebService;
  • Action do WebService;
  • Usuário (para logar no WebService);
  • Senha (para logar no WebService).

Para facilitar, crie um programa e uma transação para fazer a manutenção dos dados adicionais desta tabela

3.2 RFC para busca dos dados

Esta é a mesma RFC como a criada no cenário tradicional de B2B de saída, porém, além o campo de e-mail que ela costuma retormar, adicionar os outros campos desejados no retorno da RFC para que eles sejam utilizados no UDF.

4. Integration Repository

4.1 Criação do Software Component Version (SWCV)

Fazer a criação do SWCV assim como no cenário normal de um B2B. Vide a área de referências que indica a criação de um cenário de B2B.

4.2 Message Mapping

O message mapping terá uma diferença: uma quarta variável foi adicionada, pois ao mesmo tempo da RFC Lookup, foi feita a leitura do XML para identificar a chave de acesso da NFe emitida, assim o nome do arquivo poderia ser determinado.

4.2.1 User-Defined Function (UDF)

Para os cenários de envio de XML via B2B por e-mail foi criada uma RFC Lookup para que, durante o message mapping, seja feita uma chamada à RFC criada anteriormente, passando como parâmetro de entrada o CNPJ do destinatário e obtendo como retorno os campos adicionais da tabela, com os dados referentes à opção de recebimento das NFes pelo cliente.

O código utilizado para o UDF pode ser visto a seguir:

public String retornaDados(String cnpj,String busSystem,String commChannel,String xml,Container container){


imports com.sap.aii.mapping.api.*;com.sap.aii.mapping.lookup.*;java.util.Map;javax.xml.parsers.*;java.io.*;org.w3c.dom.*;



java.util.Map map;
AbstractTrace trace = container.getTrace();

Channel channel = null;
RfcAccessor accessor = null;
map = container.getTransformationParameters();

//envia a NFe para um administrador no caso de falha
String email = "nfe@nfe.com.br";
int forma = 0;
String pastaDestino = "";
String webserviceUrl = "";
String webserviceUsuario = "";
String webserviceAction = "";

try {
	// Retorna o Communication Channel
	channel = LookupService.getChannel(busSystem, commChannel);
	trace.addWarning("Channel: " + channel);

	// Get a RFC accessor for the channel
	accessor = LookupService.getRfcAccessor(channel);

	// Define a Mensagem de requisicao para a RFC
	String req = "<ns0:ZPIF_NFE_CLIENTE xmlns:ns0='urn:sap-com:document:sap:rfc:functions'><ZCNPJ>" + cnpj + "</ZCNPJ></ns0:ZPIF_NFE_CLIENTE>";

	// Create the xml inputstream
	InputStream inputStream = new ByteArrayInputStream(req.getBytes("UTF-8"));

	// Create xml payload
	XmlPayload payload = LookupService.getXmlPayload(inputStream);

	// Execute lookup
	XmlPayload result = accessor.call(payload);

	// Get the response
	InputStream resp = result.getContent();

	// Parse the response
	//Campo ZFORMA
	DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
	Document doc = builder.parse(resp);
	Node nodeForma = (Node) doc.getElementsByTagName("ZFORMA").item(0);
	if (nodeForma.hasChildNodes() && !nodeForma.getFirstChild().getNodeValue().equals("")) {
		forma = Integer.parseInt(nodeForma.getFirstChild().getNodeValue());
	}

	//Campo ZEMAIL
	Node nodeEmail = (Node) doc.getElementsByTagName("ZEMAIL").item(0);
	if (nodeEmail.hasChildNodes() && !nodeEmail.getFirstChild().getNodeValue().equals("")) {
		email = nodeEmail.getFirstChild().getNodeValue();
	}

	//Campo ZPASTA_DESTINO
	Node nodePasta = (Node) doc.getElementsByTagName("ZPASTA_DESTINO").item(0);
	if (nodePasta.hasChildNodes() && !nodePasta.getFirstChild().getNodeValue().equals("")) {
		pastaDestino = nodePasta.getFirstChild().getNodeValue();
	}

	//Campo ZWEBSERVICE_URL
	Node nodeUrl = (Node) doc.getElementsByTagName("ZWEBSERVICE_URL").item(0);
	if (nodeUrl.hasChildNodes() && !nodeUrl.getFirstChild().getNodeValue().equals("")) {
		webserviceUrl = nodeUrl.getFirstChild().getNodeValue();
	}

	//Campo ZWEBSERVICE_USUARIO
	Node nodeUsuario = (Node) doc.getElementsByTagName("ZWEBSERVICE_USU").item(0);
	if (nodeUsuario.hasChildNodes() && !nodeUsuario.getFirstChild().getNodeValue().equals("")) {
		webserviceUsuario = nodeUsuario.getFirstChild().getNodeValue();
	}

	//Campo ZWEBSERVICE_ACTION
	Node nodeAction = (Node) doc.getElementsByTagName("ZWEBSERVICE_ACT").item(0);
	if (nodeAction.hasChildNodes() && !nodeAction.getFirstChild().getNodeValue().equals("")) {
		webserviceAction = nodeAction.getFirstChild().getNodeValue();
	}

} catch (Exception e) {
	trace.addWarning("Falha na RFC: " + e);
} finally {

	// Close the accessor in order to free resources.
	if (accessor!=null) {
	try {
		accessor.close();
	} catch (Exception e) {
		trace.addWarning("Erro ao finalizar o accessor: " + e);
	}
}
}

// Definir qual rota o XML sera enviado
if (forma == 1){
	// Envio via E-MAIL
	int posicaoId = xml.indexOf("Id=");
	String chaveAcesso = xml.substring(posicaoId + 7, posicaoId + 51);
	String filename = chaveAcesso + ".xml";

	map.put(StreamTransformationConstants.RECEIVER_SERVICE, "EMAIL");

	// Preenche o dynamic configuration para o campo "para" no mail adapter
	DynamicConfigurationKey toKey = DynamicConfigurationKey.create("http://sap.com/xi/XI/System/Mail", "THeaderTO");
	DynamicConfigurationKey attachfilenameKey = DynamicConfigurationKey.create("http://sap.com/xi/XI/System/File", "FileName");

	DynamicConfiguration conf = (DynamicConfiguration) container.getTransformationParameters().get(StreamTransformationConstants.DYNAMIC_CONFIGURATION);

	conf.put(toKey, email);
	conf.put(attachfilenameKey, filename);
}

else if(forma == 2){
	// Envio via WEBSERVICE
	map.put(StreamTransformationConstants.RECEIVER_SERVICE, "WEBSERVICE");

	// Preenche o dynamic configuration para os parametros do SOAP adapter
	DynamicConfigurationKey urlKey = DynamicConfigurationKey.create("http://sap.com/xi/XI/System/SOAP", "TServerLocation");
	DynamicConfigurationKey userKey = DynamicConfigurationKey.create("http://sap.com/xi/XI/System/SOAP", "TAuthKey");
	DynamicConfigurationKey actionKey = DynamicConfigurationKey.create("http://sap.com/xi/XI/System/SOAP", "THeaderSOAPACTION");

	DynamicConfiguration conf = (DynamicConfiguration) container.getTransformationParameters().get(StreamTransformationConstants.DYNAMIC_CONFIGURATION);

	conf.put(urlKey, webserviceUrl);
	conf.put(userKey, webserviceUsuario);
	conf.put(actionKey, webserviceAction);
}

else if (forma == 3){
	// Envio via Arquivo
	int posicaoId = xml.indexOf("Id=");
	String chaveAcesso = xml.substring(posicaoId + 7, posicaoId + 51);
	String filename = chaveAcesso + ".xml";

	map.put(StreamTransformationConstants.RECEIVER_SERVICE, "FILE");

	// Preenche o dynamic configuration para os parametros do File adapter
	DynamicConfigurationKey filenameKey = DynamicConfigurationKey.create("http://sap.com/xi/XI/System/File", "FileName");
	DynamicConfigurationKey pastadestinoKey = DynamicConfigurationKey.create("http://sap.com/xi/XI/System/File", "Directory");

	DynamicConfiguration conf = (DynamicConfiguration) container.getTransformationParameters().get(StreamTransformationConstants.DYNAMIC_CONFIGURATION);

	conf.put(filenameKey, filename);
	conf.put(pastadestinoKey, pastaDestino);
}

// Since this is a dummy mapping, return the same value from input
return cnpj;


4.2.2 Message Mapping do Enhanced Receiver Determination

Para definir qual serviço receberá a mensagem, utilizamos o recurso Enhanced Receiver Determination. Para isso, criamos o seguinte message mapping. Para o campo "Party" adicionamos a constante referente à party referente ao B2B, que será vista mais adiante:

Para o campo "Service", criar outro UDF com RFC Lookup, para que, de acordo com a forma de recebimento escolhida, ele determine qual serviço receberá a mensagem:

O código utilizado para o UDF pode ser visto a seguir (desta vez, a variável que recebe a string do XML não se faz necessária)::

java.util.Map map;
AbstractTrace trace = container.getTrace();
Channel channel = null;
RfcAccessor accessor = null;
map = container.getTransformationParameters();
int forma = 0;
String servico = "";


try {


	// Retorna o Communication Channel
	channel = LookupService.getChannel(busSystem, commChannel);


	// Get a RFC accessor for the channel

	accessor = LookupService.getRfcAccessor(channel);


	 // Define a Mensagem de requisicao para a RFC
	 String req = "<ns0:ZPIF_NFE_CLIENTE xmlns:ns0='urn:sap-com:document:sap:rfc:functions'><ZCNPJ>" + cnpj + "</ZCNPJ></ns0:ZPIF_NFE_CLIENTE>";


	// Create the xml inputstream
	InputStream inputStream = new ByteArrayInputStream(req.getBytes("UTF-8"));


	// Create xml payload
	XmlPayload payload = LookupService.getXmlPayload(inputStream);


	// Execute lookup
	XmlPayload result = accessor.call(payload);


	 // Get the response
	InputStream resp = result.getContent();


	// Parse the response
	//Campo ZFORMA
	DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
	Document doc = builder.parse(resp);
	Node nodeForma = (Node) doc.getElementsByTagName("ZFORMA").item(0);
	if (nodeForma.hasChildNodes() && \!nodeForma.getFirstChild().getNodeValue().equals("")) {
		forma = Integer.parseInt(nodeForma.getFirstChild().getNodeValue());
	}

} catch (Exception e) {
	trace.addWarning("Falha na RFC: " + e);
} finally {
	// Close the accessor in order to free resources.
	if (accessor\!=null) {
		try { 
			accessor.close();
		} catch (Exception e) {
			trace.addWarning("Erro ao finalizar o accessor: " + e);
		}
}
}
// Definir qual rota o XML sera enviado
if (forma == 1){
	// Envio via E-MAIL
	servico = "EMAIL";
}
else if(forma == 2){
	// Envio via WEBSERVICE
	servico = "WEBSERVICE";
}
else if (forma == 3){
	// Envio via Arquivo
	servico = "FILE";
}

return servico;

4.3 Interface Mapping

Criar um interface mapping que tem como mensagem de origem a Message Inteface "NTB2B_procNFe_OB" e como destino o Message Interface "ReceiverDetermination", encontrado no namespace "http://sap.com/xi/XI/System":

5. Desenvolvimento Java

5.1 Criação do Adapter Module

Nesta parte é necessário desenvolver um código em Java. Ele é relativamente simples de ser implementado. O código pode ser visto a seguir:

package br.com.neoris.nfe.attachment;

import com.sap.aii.af.mp.module.*;
import com.sap.aii.af.ra.ms.api.*;
import com.sap.aii.af.service.auditlog.*;
import javax.ejb.*;

public class SetAttachmentNameBean implements SessionBean, Module {

	private SessionContext myContext;

	private AuditMessageKey amk;

	public SetAttachmentNameBean() { }

	public void ejbRemove() { }

	public void ejbActivate() {}

	public void ejbPassivate() { }

	public void setSessionContext(SessionContext context) {
		myContext = context;
	}

	public void ejbCreate() throws CreateException {
	}

	public ModuleData process(ModuleContext moduleContext, ModuleData inputModuleData) throws ModuleException {
		Message msg = (Message) inputModuleData.getPrincipalData();

		if (msg.getMessageDirection() == MessageDirection.INBOUND) {
			amk = new AuditMessageKey(msg.getMessageId(), AuditDirection.INBOUND);
		} else {
			amk = new AuditMessageKey(msg.getMessageId(), AuditDirection.OUTBOUND);
		}

		Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS, "NFe: Entrando no modulo para definir nome do arquivo...");

		try {
			Payload text = msg.getDocument();
			byte xmlContent[] = text.getContent();
			String fileName = msg.getMessageProperty("http://sap.com/xi/XI/System/File", "FileName");
			StringBuffer content = new StringBuffer();
			content.append("Prezado cliente, \r\n\r\n");
			content.append("Segue arquivo anexo referente a sua Nota Fiscal Eletr\u00F4nica.\r\n\r\n");
			content.append("\r\n\r\n");
			content.append("Att,\r\n");
			content.append("Nome da Empresa\r\n");
			content.append("Ramo de Atividade");
			text.setContent(content.toString().getBytes("UTF-8"));
			text.setContentType("text/plain;charset=\"UTF-8\";");
			Payload payload = msg.createPayload();
			payload.setContent(xmlContent);


			if (fileName == null) {
				fileName = "default.xml";
			}


			payload.setContentType("application/xml");
			payload.setName(fileName);
			payload.setDescription(fileName);
			msg.addAttachment(payload);
			inputModuleData.setPrincipalData(msg);


			} catch (Exception e) {
				throw new ModuleException(e);
			}

			return inputModuleData;

	}

}

Em caso de dúvidas de como criar um Adapter Module, acesse a página de Como Criar um Adapter Module na seção Referências. Após compilar o código, fazer o deploy do mesmo no SDM (lembrando que para este desenvolvimento utilizamos o PI 7.0).

6. Integration Directory

6.1 Criação das Party's e Communication Channels

Criar uma Party para o B2B. Ela abrigará todas as formas de envio de NFe, neste caso, EMAIL, FILE (para arquivo ou FTP) e WEBSERVICE e um receiver communication channel para cada caso:

As configurações dos communication channels foram feitas conforme as figuras a seguir (Notem que os flags para utilização do ASMA estão ativos, assim os parâmetros que determinamos como dinâmicos podem ser enviados aos adaptadores):

Mail Adapter (note na aba Module a importação do Custom Adapter Module criado anteriormente):

SOAP Adapter:

File Adapter:

6.2 Cenário NTB2B

Criadas as Party's, Services e Adapters (communication channels) para cada uma das três formas de envio, alterar o parâmetro "Type of Receiver Determination" para "Extended" no Receiver Determination e escolher o Interface Mapping criado anteriormente:

7. Testes

Para testar cada um dos cenários, ativar o B2B para um determinado CNPJ e na tabela criada, colocar qual a forma de envio que deseja enviar o XML.

Envio via E-Mail:

Envio via WebService:

Envio via Arquivo:


  • No labels

2 Comments

  1. Anonymous

    Parabéns Guedes.

    Excelente trabalho sobre NFE e na disseminação do WIKI em português.

    Abs,

    Fábio Fernandes

    Diretor Neoris / SAP Mentor

  2. Excelente trabalho Guedes.

    Adorei a qualidade das informações dá pra implementar sem ter que buscar informações complementares em outras fontes.

    Abraços, Fernando Da Rós