quarta-feira, 26 de janeiro de 2011

Design de Aplicações - Parte 2

Organização da Aplicação

Tal como acontece com o dicionário de dados, o programador deve olhar para os programas standard antes de iniciar o código. Se a aplicação tiver um look and feel equivalente ao SAP Standard então estará a facilitar a adaptação do utilizador final e a própria manutenção da aplicação. Mas nem sempre…
Os sistemas SAP sempre foram considerados pouco user-friendly. Por esse motivo a SAP decidiu analisar algumas das transacções mais utilizadas, estudar o comportamento e desejos dos utilizadores e transformá-las em algo mais simples de usar. Daí surgiu um novo SAP GUI com uma nova estética e as transacções Enjoy SAP.

Transacções MB1B e a correspondente Enjoy MIGO
Hoje em dia existem outros interfaces com o utilizador mais evoluídos em termos gráficos que o SAP GUI e que pretendem ser a forma preferencial de aceder a SAP, quer através do portal por ITS, BSP ou WebDynpro, ou até interfaces para equipamentos móveis pelas (futuras) soluções Sybase. Existe também um esforço de integrar as diferentes soluções SAP num único frontend, o SAP Netweaver Business Client, o que também pode influenciar o aspecto das soluções de forma a que aceder ao SAP Business One seja semelhante a aceder ao SAP Business Suite ou SAP Business ByDesign ou SAP Business All-in-One.
SAP Netweaver Business Client

O foco deste post é específico em termos de apresentação ao SAP GUI mas tendo em consideração de que poderá ser necessário expandir a aplicação para outros tipos de interface. Nesse sentido, os melhores exemplos que o programador deve procurar seguir são as transacções Enjoy SAP.

Apresentação vs Execução

Uma das regras mais importantes a seguir e que tem um profundo impacto na manutenção do programa consiste em saber separar o que faz parte da apresentação do que é execução.
O principal objectivo desta separação é garantir não só a reusabilidade da execução para outros interfaces ou outros tipos de utilização como também facilitar a leitura e alterações à aplicação.
A execução deve ser balizada numa classe ou grupo de funções e deve incluir a gravação do documento e as diversas validações funcionais que possam existir. As ajudas de pesquisa, por exemplo, e o despoletamento de mensagens de erro já devem ser da responsabilidade da apresentação.

sexta-feira, 21 de janeiro de 2011

Design de Aplicações - Parte 1

Regra geral, o desenvolvimento em ABAP limita-se a pequenas alterações que não necessitam de grande trabalho de design, como seja
  1. Correcções a código Z
  2. Criação de relatórios
  3. Alteração de exits, BADI’s
  4. Tratamento de input’s e output’s como IDOC’s, RFC’s, Webservices
  5. Alteração de output’s de impressão ou envio de email com Smartforms, SAPScripts, PDF Forms
  6. Automatização de fluxos de processos e processos em background
  7. Criação de parametrização Z

O desenvolvimento em si de aplicações complexas já não é tão frequente, o que por si só justifica o facto de que não é fácil encontrar programadores que construam um dicionário de dados com qualidade e um programa com design adequado.
Este documento pretende contribuir para a forma de pensar do leitor no momento de desenho aplicacional.
E antes sequer de começar é preciso analisar a necessidade da própria aplicação. É possível usar o standard com a activação de EXIT’s ou BADI’s? Existe já alguma aplicação desenvolvida que pode ser expandida? Se possível deve-se recorrer ao standard uma vez que se garante que o processo funciona em qualquer situação e reduz custos de manutenção. Se houver alguma aplicação desenvolvida e que pode ser adaptada sem grande esforço então será essa a opção a escolher. A manutenção de aplicações replicadas deve ser evitada.

Dicionário de dados

O desenho de tabelas em SAP deve seguir algumas regras de boas práticas de forma a:
  • Reduzir a quantidade de objectos criados;
  • Facilitar a leitura por outros membros da equipa;
  • Respeitar a rastreabilidade de alterações;
  • Obviamente, garantir a boa construção respeitando pelo menos a 3ª forma normal das bases de dados relacionais.

A primeira regra a usar é verificar a necessidade da tabela. Se os dados podem ser guardados em algum lugar, preferencialmente standard, então a nova tabela não deve ser criada. Por exemplo, dados dependentes do utilizador podem ser guardados como parâmetros nos dados mestre de utilizador. Dados de materiais podem ser guardados no sistema de classificação. Dados mestre de área de vendas podem ser guardados num append à tabela TVTA.
Criar uma tabela tem custos muito elevados de manutenção e deve ser evitado a todo o custo. Se não se puder evitar e for uma tabela de parametrização então deve ser colocada de forma a ser facilmente visível a qualquer novo consultor que necessite a efectuar, por exemplo, pode ser colocada na SPRO.
A segunda regra é respeitar as convenções de nomenclatura, inclusivamente a definida para o projecto. Segue de seguida uma lista de algumas regras adicionais que podem não estar presentes na global:
  • Utilizar nomes de campos e elementos de dado standard sempre que possível (por exemplo WERKS e não CENTRO). Para além do nome ser mais óbvio para outros, esta prática permite ao programador interiorizar o modelo de dados da SAP. Esta regra pode ser quebrada para interfaces como BAPI’s e RFC’s mas mesmo assim existe a convenção para os BAPI’s onde aparece um nome mais completo e em inglês. Por exemplo, o nº de fornecedor é LIFNR (Lieferant Nummer em alemão) nas tabelas standard e VENDOR (em inglês) para os BAPI’s.
  • Se não existe um nome standard para o campo da tabela criar um elemento de dados Z e o respectivo domínio Z, preencher com detalhe as denominações do campo e sempre que possível restringir os valores. Não criar todos os objectos é preguiça e reduz desnecessariamente a qualidade de informação do campo.
  • Criar e associar sempre que necessário ajudas de pesquisa para os campos novos.

A terceira regra diz respeito à rastreabilidade. Todas as tabelas devem ter de alguma forma associada a informação de:
  • Utilizador que criou registo;
  • Utilizador que modificou registo;
  • Data de criação de registo;
  • Data de modificação de registo;
  • Hora de criação de registo;
  • Hora de modificação de registo.

Também é boa prática em alguns casos que as tabelas de parametrização tenham um intervalo de validade:
  • Data de fim de validade, deve ser campo chave;
  • Data de início de validade.

A quarta regra refere-se ao respeito da 3ª forma normal. Geralmente um conjunto de novas tabelas aplicacionais pretende guardar um documento, e na maioria dos casos um documento é composto por:
  • Cabeçalho;
  • Item;
  • Algumas vezes, Subitem.

Cada um destes conjuntos de informação deve ser colocado numa tabela em separado. Pode ainda ocorrer mais divisão de informação consoante a natureza da mesma. Por exemplo, pode haver duas tabelas ao nível do item para tipos de item distintos.

Segue um exemplo de organização de tabelas como é efectuado no SAP Standard para guardar os Documentos de Compra:

O Cabeçalho tem dados como o fornecedor e a data, o Item a informação do material e quantidade solicitada, as Divisões de Remessa a quantidade do item solicitada por data de entrega desejada e finalmente as confirmações têm a quantidades e datas previstas de entrega que o fornecedor confirmou.

segunda-feira, 10 de janeiro de 2011

Upload e Download de Ficheiros de Estrutura Variável


A maioria dos programadores ABAP com alguma experiência conhece os métodos para fazer upload ou download de ficheiros. Mas nem todos os usam de uma forma prática. Neste artigo vou descrever a forma mais simples para usar estes métodos e uma forma mais complexa que permite o tratamento de ficheiros de comprimento de linha variável.
Mas primeiro, uma parte importante, a chamada à pesquisa do nome do ficheiro.

Pesquisa de ficheiro

A não ser que o ficheiro tenha um nome e localização específica, a pesquisa de ficheiro deve ser obrigatória. Não preparar a pesquisa é um erro grotesco que limita a operacionalidade e usabilidade do programa. Um programador deve sempre simplificar o trabalho do utilizador final e isso implica primeiro que tudo que o programador se pergunte como se faz no SAP standard. Ou melhor, o bom standard…
Portanto, normalmente o nome do ficheiro é pedido por parâmetro e a pesquisa é efectuada por F4.

*Primeira consideração, o parâmetro deve ter cumprimento suficiente.
*No exemplo, o parâmetro tem um comprimento de 1024 caracteres,
*mostrando apenas 30. O tamanho máximo do nome do ficheiro varia
*consoante o formato do file system, mas por motivos de simplificação
*vamos considerar 1024 suficiente. Não é nada espectável um nome mais
*longo logo o esforço de tratamento é desnecessário.
PARAMETERSp_file LIKE file_table-filename LENGTH 30.

*A chamada F4 é tratada no evento ON VALUE-REQUEST.
AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_file.
  
PERFORM p_file_f4.

*&---------------------------------------------------------------------*
*&      Form  p_file_f4
*&---------------------------------------------------------------------*
*       Diálogo de Pesquisa de Ficheiro
*----------------------------------------------------------------------*
FORM p_file_f4.
  
DATAl_title TYPE string,
        l_rc 
TYPE i.
  
DATAlt_filetable TYPE filetable.
  
FIELD-SYMBOLS<fs_line> TYPE file_table.

  l_title 
'Ficheiro'.
  
CLEAR lt_filetable[].
  
CALL METHOD cl_gui_frontend_services=>file_open_dialog
    
EXPORTING
      window_title            
l_title
    
CHANGING
      file_table              
lt_filetable
      rc                      
l_rc
    
EXCEPTIONS
      file_open_dialog_failed 
1
      cntl_error              
2
      error_no_gui            
3
      not_supported_by_gui    
4
      
OTHERS                  5.
*não é previsto que caso o código esteja correcto haja erros no
*diálogo, logo não os tratei.
*Pelo motivo indicado em cima, o nome do ficheiro deve estar todo na
*primeira linha.
  LOOP AT lt_filetable ASSIGNING <fs_line>.
    
IF sy-tabix 2.
*mensagem de erro para limitar o nome do ficheiro
      
MESSAGE i899(mmWITH 'Nome de ficheiro demasiado' 'comprido'.
      
EXIT.
    
ENDIF.
    p_file 
<fs_line>-filename.
  
ENDLOOP.
ENDFORM.                                                    "p_file_f4

O exemplo em cima é com o diálogo para abertura de ficheiro. O diálogo para gravar ficheiro é o cl_gui_frontend_services=>file_save_dialog e é ligeiramente diferente, fica aqui um exemplo de como usar:

  DATAl_file TYPE stringl_path TYPE stringl_fullpath TYPE string.
  
CALL METHOD cl_gui_frontend_services=>file_save_dialog
    
CHANGING
      filename             
l_file
      path                 
l_path
      fullpath             
l_fullpath
    
EXCEPTIONS
      cntl_error           
1
      error_no_gui         
2
      not_supported_by_gui 
3
      
OTHERS               4.
*também se poderia controlar o comprimento do nome do ficheiro neste ponto
  p_file 
l_fullpath.

Formato simplificado de Upload/Download

Os métodos de upload e download são os bem conhecidos:
  • cl_gui_frontend_services=>gui_upload
  • cl_gui_frontend_services=>gui_download
Ambos funcionam basicamente da mesma forma.
A forma simples de chamar a função depende apenas do tipo de campos a passar. Se todos os campos tiverem um formato equiparado a texto então basta usar a tabela interna directamente. Senão, será necessário preparar uma segunda tabela interna com apenas campos equiparados a texto.
Neste exemplo todas as entradas da tabela de centros estão na tabela interna it_t001w:

DATAit_t001w TYPE TABLE OF t001w.

O conteúdo da tabela é colocado num ficheiro no frontend:

  DATAl_file TYPE string.
  l_file 
p_file.
  
CALL METHOD cl_gui_frontend_services=>gui_download
    
EXPORTING
      filename                
l_file
      write_field_separator     
'X' 
    
CHANGING
      data_tab                
it_t001w
    
EXCEPTIONS
*      .....
      
OTHERS                  24.
  
IF sy-subrc <> 0.
    
MESSAGE e899(mmWITH 'Erro a gravar ficheiro'.
  
ENDIF.

O tratamento de erros deste método é muito mais relevante dado que a probabilidade do erro é maior.
O parâmetro write_field_separator associa o separador TAB entre campos o que vai permitir abrir o ficheiro facilmente em Excel como um ficheiro *.txt tab delimited, logo é um formato preferencial.
Para este exemplo o upload teria o seguinte aspecto:

  DATAl_file TYPE string.
  l_file 
p_file.
  
CALL METHOD cl_gui_frontend_services=>gui_upload
    
EXPORTING
      filename                
l_file
      has_field_separator     
'X'
    
CHANGING
      data_tab                
it_t001w
    
EXCEPTIONS
*      .....
      
OTHERS                  19.
  
IF sy-subrc <> 0.
    
MESSAGE e899(mmWITH 'Erro a abrir ficheiro'.
  
ENDIF.

Portanto, razoavelmente simples, não é? Ok, vamos complicar. E se à partida temos um ficheiro com colunas dinâmicas?

Upload/Download com estruturas variáveis

Existem formas de criar tabelas internas com colunas dinâmicas mas estes métodos estão fora do âmbito deste artigo. Vamos admitir que a tabela pode ter até 59 colunas, que podem ser campos de texto, datas ou numéricos.
Um ponto extremamente importante diz respeito à conversão entre formato interno e externo. Caso existam campos sujeitos a conversão como códigos de material, datas, números, o programador deve fazer uma escolha consciente do formato do ficheiro. Por regra, o ficheiro que está fora de SAP deve usar o formato de output, ao passo que obviamente o programa e a sua tabela interna devem usar o formato de input. No entanto pode haver excepções dado que o ficheiro provavelmente será processado por uma aplicação externa, por exemplo, o Excel. Aí, considerações como o caracter da casa decimal têm de ser avaliadas. O tratamento de formatos internos e externos também não é do âmbito deste documento, mas serão usados alguns casos a título de exemplo.
Dado que não sabemos o formato, a tabela interna usada tem todos os campos como texto de 30 caracteres:

  TYPESBEGIN OF t_data,
          col1
(30),
          col2
(30),
          col3
(30),
*..............
          col59
(30),
         
END OF t_data.
  
DATAl_file TYPE string.
  
DATAlt_data TYPE TABLE OF t_data.
  
DATAls_data TYPE t_data.
  
DATAl_col TYPE sy-tabix.
  
FIELD-SYMBOLS<fs_field_in><fs_field_out>.
  
FIELD-SYMBOLS<fs_t001w> TYPE t001w.
  
TYPESBEGIN OF t_line,
          fieldname 
TYPE dd03l-fieldname,
          
position TYPE dd03l-position,
         
END OF t_line.
  
DATAlt_line TYPE TABLE OF t_line.
  
FIELD-SYMBOLS <fs_line> TYPE t_line.

  
l_file p_file.

*obtem lista de campos para exemplo
*num caso real teríamos por exemplo uma lista reduzida com 4 ou 5 campos
  
SELECT fieldname position INTO TABLE lt_line FROM dd03l
         
WHERE tabname 'T001W' AND comptype 'E'
         
ORDER BY position.

  
LOOP AT it_t001w ASSIGNING <fs_t001w>"linha a linha
    
CLEAR ls_data.
    
LOOP AT lt_line ASSIGNING <fs_line>"coluna a coluna
      
ADD TO l_col.
      
ASSIGN COMPONENT <fs_line>-fieldname OF STRUCTURE <fs_t001w>
             
TO <fs_field_in>"assigna campo in
      
ASSIGN COMPONENT l_col OF STRUCTURE ls_data
             
TO <fs_field_out>"assigna campo out
*rotinas de conversão. Existem formas mais dinamicas de as determinar,
*pelo elemento de dados
      
IF <fs_line>-fieldname 'KUNNR' OR <fs_line>-fieldname 'LIFNR'.
        
CALL FUNCTION 'CONVERSION_EXIT_ALPHA_OUTPUT'
          
EXPORTING
            
input  <fs_field_in>
          
IMPORTING
            
output <fs_field_out>.
      
ELSE"outros elementos não sujeitos a conversão
        <fs_field_out> 
<fs_field_in>.
      
ENDIF.
    
ENDLOOP.
    
CLEAR l_col.
    
APPEND ls_data TO lt_data.
  
ENDLOOP.

Após o carregamento da tabela de output, os dados são enviados sem mais tratamento:

  
CALL METHOD cl_gui_frontend_services=>gui_download
    
EXPORTING
      filename                
l_file
      write_field_separator   
'X'
    
CHANGING
      data_tab                
lt_data
    
EXCEPTIONS
      file_write_error        
1
      no_batch                
2
*..............
      
OTHERS                  24.
  
IF sy-subrc <> 0.
    
MESSAGE e899(mmWITH 'Erro a gravar ficheiro'.
  
ENDIF.

Por sua vez upload é feito para esta tabela de output:

  CALL METHOD cl_gui_frontend_services=>gui_upload
    
EXPORTING
      filename                
l_file
      has_field_separator     
'X'
    
CHANGING
      data_tab                
lt_data
    
EXCEPTIONS
      file_open_error         
1
      file_read_error         
2
*..............
      
OTHERS                  19.

Agora os dados são passados da tabela de output para a tabela real, de input, pelo processo inverso de conversão:

  DATAlt_data TYPE TABLE OF t_data.
  
DATAls_data TYPE t_data.
  
DATAl_col TYPE sy-tabix.
  
FIELD-SYMBOLS<fs_field_in><fs_field_out>.
  
FIELD-SYMBOLS<fs_t001w> TYPE t001w.
  
TYPESBEGIN OF t_line,
          fieldname 
TYPE dd03l-fieldname,
          
position TYPE dd03l-position,
         
END OF t_line.
  
DATAlt_line TYPE TABLE OF t_line.
  
FIELD-SYMBOLS <fs_line> TYPE t_line.
*obtem lista de campos para exemplo
*num caso real teríamos por exemplo uma lista reduzida com 4 ou 5 campos
  
SELECT fieldname position INTO TABLE lt_line FROM dd03l
         
WHERE tabname 'T001W' AND comptype 'E'
         
ORDER BY position.

  
LOOP AT it_t001w ASSIGNING <fs_t001w>"linha a linha
    
CLEAR ls_data.
    
LOOP AT lt_line ASSIGNING <fs_line>"coluna a coluna
      
ADD TO l_col.
      
ASSIGN COMPONENT <fs_line>-fieldname OF STRUCTURE <fs_t001w>
             
TO <fs_field_in>"assigna campo in
      
ASSIGN COMPONENT l_col OF STRUCTURE ls_data
             
TO <fs_field_out>"assigna campo out
*rotinas de conversão.
      
IF <fs_line>-fieldname 'KUNNR' OR <fs_line>-fieldname 'LIFNR'.
        
CALL FUNCTION 'CONVERSION_EXIT_ALPHA_OUTPUT'
          
EXPORTING
            
input  <fs_field_in>
          
IMPORTING
            
output <fs_field_out>.
      
ELSE"outros elementos não sujeitos a conversão
        <fs_field_out> 
<fs_field_in>.
      
ENDIF.
    
ENDLOOP.
    
CLEAR l_col.
    
APPEND ls_data TO lt_data.
  
ENDLOOP.