yanghangmvp/sap-abap-pitfalls

Biblioteca de armadilhas e soluções para desenvolvimento ABAP (reutilizável entre projetos). Compilada a partir de mais de 100 relatórios de entrega e registros diários de correção de erros de projetos SAP DEV ERP, com aproximadamente 120 itens de "Fenômeno → Causa Raiz → Solução", além de um checklist de início de projeto em 5 fases.

O que é sap-abap-pitfalls?

Este skill é uma base de conhecimento prática e referência de bolso para desenvolvedores ABAP que trabalham em projetos SAP. Ele compila mais de 120 itens de 'Fenômeno → Causa Raiz → Solução' extraídos de mais de 100 relatórios de entrega e logs de correção diária de projetos reais. O conteúdo está organizado em 11 seções principais, cobrindo desde armadilhas de ativação (ADT/MCP), restrições de sintaxe ABAP, uma lista de falhas silenciosas (onde o sistema retorna sucesso mas os dados não são alterados), até transações e LUW, pop-ups, F4, ALV, interfaces JSON e gateway, criação de objetos DDIC, técnicas de diagnóstico com ST22 e armadilhas de scripts de patch. O diferencial é seu checklist de início de projeto em 5 fases (reconhecimento, pré-ação, autocodificação, entrega e encerramento), que serve como guia para novos projetos. É útil para ler antes de iniciar um novo projeto ABAP e para diagnosticar problemas comuns como 'compilação bem-sucedida, mas ativação falhou', 'ativação bem-sucedida, mas execução incorreta', 'retorno S, mas valor não mudou', 'mensagem de erro não relacionada à causa real', 'botão não responde', ou 'dados misteriosamente deslocados'. O skill suporta plataformas como Claude Code, Cursor e Codex, e deve ser carregado como uma skill de agente de IA para desenvolvedores SAP.

Funciona com~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/yanghangmvp/MCP_ABAP/tree/HEAD/skills/sap-abap-pitfalls

Installed? Explore more Programação e Desenvolvimento skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Biblioteca de Armadilhas e Soluções para Desenvolvimento ABAP

Propósito: Leia atentamente antes de iniciar um novo projeto; ao solucionar problemas, consulte pelos sintomas. Este skill contém apenas experiências reutilizáveis entre projetos. Endereços de ambiente, números de solicitação e listas de objetos estão no escopo geral do projeto (ex.: sap-abap-project-ops). A lista completa de 120 itens (com trechos de código e fontes) está em references/casebook.md.

Como usar: Novo projeto iniciando → vá diretamente para §11 Checklist de início de projeto (execute em ordem cronológica); Projeto em andamento com problema → §10 Consulta rápida por sintoma para localizar a seção; Quer se aprofundar em um item específico com código completo e fonte → references/casebook.md.

Três meta-regras (mais importantes do que qualquer armadilha individual):

  1. A mensagem de erro frequentemente não está relacionada à causa real — O ponto onde o compilador ABAP reporta o erro é geralmente "onde ele conseguiu analisar", não "onde você errou". Primeiro, presuma que é uma questão de ordem de parágrafos/ordem de palavras-chave/incompatibilidade de tipos, e não de sintaxe em si.
  2. O mais perigoso no SAP não é um erro, é uma falha silenciosa — A tripla ilusão de "retorno S, ativação 200, compilação bem-sucedida" pode esconder dados sendo corrompidos silenciosamente. Para tudo que envolva BAPI, SELECT INTO TABLE, EXTENSIONIN, elementos de texto ou campos de enhancement, sempre faça uma conciliação com o banco de dados e não confie apenas no valor de retorno.
  3. Se falhar, consulte ST22, não adivinhe — A Error analysis de um short dump fornece diretamente o procedure "XXX" "(FORM)" e a classe de exceção CX_SY_*, o que é uma ordem de grandeza mais rápido do que testar FORM por FORM.

0. Dez Regras de Ouro (Violação Implica Retrabalho)

  1. Antes de modificar qualquer objeto existente, o primeiro passo é ler a versão ativa do sistema de produção. Usuários frequentemente modificam objetos de produção diretamente no SE80 sem registrar no arquivo. O arquivo local deve ser usado apenas para comparação, não como baseline. get_source → diff com backup para confirmar 0 diferenças → aplicar patch → reler e fazer diff novamente → sobrescrever o arquivo.
  2. Elementos de texto também fazem parte da baseline; get_source não inclui TEXTPOOL. Antes de enviar texto de forma destrutiva, você deve read_textelements para obter o texto completo da versão ativa, fazer diff com o JSON local, mesclar manualmente e depois enviar. Caso contrário, alterações de texto feitas pelo usuário no SE38 serão silenciosamente sobrescritas, e o diff do código fonte não mostrará nada.
  3. Nunca use diretamente UPDATE / MODIFY em tabelas padrão. Para alterar documentos FI já contabilizados, use FI_DOCUMENT_CHANGE (grupo FACG, T_ACCHG só preenche FDNAME+NEWVAL, não passe I_BUZEI no cabeçalho, só em itens).
  4. Não crie números de solicitação por conta própria. Se faltar CORR_NR ao enviar objetos transportáveis ⇒ pare e reporte, espere o usuário fornecer o número; antes de criar um novo objeto TADIR, primeiro reporte "tipo + nome sugerido + resultado da verificação de duplicidade no TADIR + justificativa para continuação da numeração", e só crie após aprovação. Para alterações subsequentes em objetos já associados a uma solicitação, reutilize o número original, sem perguntar novamente.
  5. Não use classes Update* para fazer chamadas exploratórias em interfaces. Elas realmente escrevem — já houve caso de usar UpdateFunctionModule apenas com o corpo da função "para testar", sobrescrevendo o código fonte inativo com uma versão truncada de 620 caracteres. Sondas devem usar apenas interfaces somente leitura.
  6. Quatro passos para entrega: Gerar → Enviar código fonte → Enviar elementos de texto (PROG/PX devem ser ativados separadamente) → Gerar status GUI (CUA). Se os elementos de texto não forem ativados, em tempo de execução TEXT-xxx estará sempre vazio.
  7. Comentários em código de produção: mantenha a estrutura, remova a narrativa. Mantenha o cabeçalho do programa (unidade/autor/data/resumo/registro de alterações) + linhas de borda *&--- + banners de FORM + documentação de parâmetros formais -->/<-- + comentários curtos de tipo de condição; remova justificativa de design, descrição de escopo, testes conjuntos, motivos de TODO, referências a scripts/work/ADT/ZTEST_*. Após a remoção, um diff deve provar que o corpo do código em si não foi alterado.
  8. Objetos de teste/sonda devem ser excluídos imediatamente após o teste. Primeiro get_source para arquivar em backup/ → excluir → verificar TADIR para garantir que está limpo. Exclua apenas os que você mesmo criou nesta rodada; não mexa em objetos criados por outros, já associados a pacotes/solicitações ou DEMOs em uso.
  9. Primeiro verifique locks, depois faça backup, antes de escrever no SAP. Arquivos de credenciais não devem ser transmitidos, exibidos ou escritos em entregáveis.
  10. Quando o usuário disser "os dados estão ok", assuma ainda mais que o programa está com problema. Use o "valor do erro + número do documento" para retroceder as características de entrada, escreva uma sonda para replicar o erro literalmente; se a replicação for bem-sucedida, a causa raiz está identificada.

1. Canais de Ativação e Escrita (ADT / MCP)

  • HTTP 200 ≠ Ativação bem-sucedida — O sucesso ou falha está no body: você deve verificar explicitamente activationExecuted="false" e lançar um erro; a única maneira confiável de verificar a ativação é reler o include e comparar as versões ?version=active e inativa. TRDIR-RSTAT não é preenchido no S/4.
  • Falso sucesso é o mais difícil de detectar — Scripts antigos usavam regex para capturar mensagens por <chklst:shortText> / <msg>text</msg>, mas a estrutura real é prefixo chkl: + <shortText><txt> + msg com atributos ⇒ nenhuma mensagem é capturada, e relatar "ativação bem-sucedida" três vezes seguidas pode ser apenas uma casca vazia de 131 caracteres. Se não for possível capturar mensagens, deve ser tratado como falha.
  • Elementos de texto são objetos ativáveis independentes PROG/PX — Ativar o programa pai não os ativa; se push_textelements fizer apenas GET→LOCK→PUT→UNLOCK sem ACTIVATE, o PUT escreve apenas a versão inativa. Ao enviar PROG/P, o SAP considera que não há conteúdo ativável e retorna activationExecuted="false" sem nenhuma mensagem ⇒ falha silenciosa. A interface de ativação não aceita corrNr (resulta em 400).
  • O PUT de push_textelements substitui todo o bloco; a ordem de envio não pode ser invertida — Para novos símbolos: primeiro elementos de texto, depois código fonte; para deletar símbolos: primeiro código fonte, depois elementos de texto. O TE_FILE deve incluir todas as selections existentes também.
  • Quando os elementos de texto existem apenas na versão inativa, o programa precisa ser regenerado para ter efeito — Apenas enviar elementos de texto + enviar novamente o código fonte idêntico não atualizará. Altere uma linha do código fonte (mesmo um comentário) e reenvie, ou recrie o programa.
  • Três tipos de "falso lock": ① A mensagem "Já bloqueado na solicitação yyy do usuário xxx" é, na verdade, falta de envio de transport_request/corrNr; ② UpdateDataElement reporta locked by another user quando é, na verdade, um 400 "já existe" — o isAlreadyExistsError() do handler só reconhece mensagens em inglês, não detecta a versão em chinês, e cai no fallback 403 que é traduzido como "bloqueado" (⇒ em ambiente ZH, o MCP não pode modificar um data element existente; UpdateDomain não tem essa verificação prévia, então não é afetado); ③ 403 … not specified for this client é client errado.
  • Lock real só pode ser removido via SM12 — Os três tipos de lock ESDICT/ESRDIRE/SEOCLSENQ estão associados à sessão ADT; DEQUEUE_ALL, _scope='3' e DEQUEUE_EESDICT são todos ineficazes. Após confirmar com ENQUEUE_READ, peça ao usuário para excluir manualmente no SM12 e execute novamente imediatamente; não espere o timeout da sessão.
  • Duas mensagens 403 têm significados completamente diferentes — User XXX is currently editing ZFIDxxx = um humano está com o SE80 aberto, espere; Object ... locked in task DEVK90011x not specified for this client = client errado, troque para o client da solicitação e reenvie. Confundir as duas fará você perder tempo.
  • O cabeçalho Accept do ADT LOCK deve incluir o datatype — Enviar apenas application/xml faz com que o SAP já tenha bloqueado com sucesso, mas retorne 406 e você não receba o LOCK_HANDLE ⇒ vazamento de lock, todas as solicitações subsequentes reportarão "Usuário X atualmente editando", enganando você a pensar que outra pessoa está ocupando. Valor correto: application/vnd.sap.as+xml;dataname=com.sap.ad.lock.result.
  • process.exit() dentro de fail() do script pula o finally ⇒ lock de enqueue residual — Após saída anormal de scripts de push, o lock SEOCLSENQ pode permanecer por 40 minutos, fazendo com que todos os LOCKs subsequentes retornem 403 como se "o usuário estivesse com o SE80 aberto". Ao escrever scripts, sempre execute UNLOCK antes de sair ou use process.exitCode = 1.
  • Não force a exclusão de locks de edição do usuário — Excluir locks via ADT pode perder modificações não salvas do outro lado.
  • Includes são recursos ADT independentes — POSTs para /programs/programs sempre criam programas executáveis (TRDIR-SUBC=1), que não podem ser alterados depois; includes devem usar /sap/bc/adt/programs/includes (elemento raiz <include:abapInclude>). O programa principal e o include devem estar na mesma solicitação de ativação, caso contrário, reportarão um ao outro como não definido.
  • INSERT REPORT ... STATE 'A' cria includes com o código fonte escrito como versão active — Se depois você modificar a versão inativa, a ativação do programa principal ainda compilará com a versão active antiga, e os erros apontarão para o código antigo. Após a modificação, execute INSERT REPORT 'X' FROM lt_src STATE 'A' PROGRAM TYPE 'I'. (STATE deve vir antes de PROGRAM TYPE) para atualizar a versão active.
  • Proxies locais podem interromper solicitações ADT — A aparência é idêntica a "não é possível conectar ao SAP" (socket hang up / falha de CSRF). Antes de spawnar um subprocesso MCP, exclua *_proxy e defina NO_PROXY=*; para solicitações de escrita, sempre use proxy: false.
  • ADT não possui recursos para dynpro / GUI Status / Menu — No entanto, a CUA pode ser manipulada por código (RS_CUA_INTERNAL_FETCH + _WRITE, state='A', a escrita já tem efeito). D020S é o cabeçalho da tela dynpro, não é a tabela de status GUI; RS_CUA_GET_STATUS não pode ser usado para determinar, apenas RS_CUA_INTERNAL_FETCH (passe todas as 10 TABLES obrigatórias, use ORDER BY pposition para evitar truncamento em 25 linhas).

2. Restrições de Sintaxe (Compilação de Primeira)

  • Quatro proibições para parâmetros formais de FORM: ① Não pode escrever TYPE c LENGTH n (reporta Numeric literais não podem ser usados como nomes de campo) ⇒ use TYPE char<n> ou tipo DDIC; ② Parâmetro formal de comprimento fixo recebendo parâmetro real de comprimento diferente causa erro de incompatibilidade em tempo de compilação, exceção para símbolos de texto, mas em tempo de execução ainda ocorre 500 Type conflict during a call of a FORM ⇒ para comprimentos variados, use genérico TYPE c; ③ Não pode usar TYPE STANDARD TABLE OF inline (o parâmetro formal é contado como 8, reporta Número diferente de parâmetros) ⇒ primeiro declare com TYPES; ④ FIELD-SYMBOL declarado dentro de um FORM é local, para usar entre FORMs deve ser global.
  • FORM ... TABLES deve vir antes de USING/CHANGING — Escrever USING ... CHANGING ... TABLES reporta Field "TABLES" is unknown. (trata a palavra-chave como nome de parâmetro formal). O mesmo se aplica ao lado da chamada com PERFORM.
  • A ordem dos parágrafos em CALL FUNCTION é obrigatória — EXPORTING → IMPORTING → CHANGING → TABLES → EXCEPTIONS. Colocar TABLES antes de IMPORTING reporta Unable to interpret "EV_OBJNR", sem indicar que é um problema de ordem. Parâmetros formais EXPORTING da função chamada devem estar no parágrafo IMPORTING do chamador; se invertidos, reporta CALL_FUNCTION_PARM_UNKNOWN — muito fácil de interpretar erroneamente como "interface FM não gerada".
  • SELECT com múltiplos campos deve usar vírgula como separador; variáveis host com @ devem ser escapadas — Reporta os elementos na "SELECT LIST" devem ser separados por vírgulas. O mesmo vale para múltiplas atribuições em UPDATE ... SET; constantes em WHERE (incluindo CONSTANTS) também precisam de @.
  • A posição de INTO e UP TO n ROWS deve seguir o erro do ativador — Instruções com JOIN/subconsultas/expressões SQL estão em modo estrito, forçando INTO no final (SELECT 563); UP TO n ROWS deve vir antes de WHERE. A ordem varia entre diferentes instruções; confiar na memória pode levar a erros.
  • ASSIGN ... CASTING TYPE c LENGTH <variável> não é suportado — O comprimento deve ser um literal; remover LENGTH causa DUMP Invalid subfield access quando o índice está fora dos limites. A solução correta é usar cl_abap_container_utilities=>fill_container_c( ) (inverso da função padrão READ_CONTAINER_C).
  • Em DESCRIBE FIELD, TYPE deve vir antes de COMPONENTS — Mesmo que você só queira o número de componentes, deve escrever: DESCRIBE FIELD cs_data TYPE lv_type COMPONENTS lv_count.
  • CONSTANTS deve ter VALUE — CONSTANTS: c_x TYPE flag. reporta "VALUE ..." not specified.
  • MESSAGE TEXT-xxx TYPE 'S' WITH n não é válido — Símbolos de texto não podem ser usados como texto de mensagem com WITH. Use um FORM auxiliar: lv_text = pv_text → REPLACE '&' IN lv_text WITH lv_num → MESSAGE lv_text TYPE 'S'.
  • Tipos de comprimento fixo não têm o nome char<n> — TYPE char1000/char55/char220 são todos inválidos, reportam Type "X" is unknown; sempre use TYPE c LENGTH n. Adivinhar o nome do tipo errado = um ciclo de ativação desperdiçado; se não tiver certeza, consulte DD04L ou get_source.
  • class=>method só pode ser usado para métodos estáticos — Métodos de instância exigem CREATE OBJECT lo. lo->method( ), caso contrário, reporta só pode ser especificado com métodos estáticos.
  • Modelos de string (string templates) não podem ser usados como parâmetros reais de PERFORM ... USING — Reporta Field "|" is unknown; atribua a uma variável primeiro.
  • CALL TRANSACTION ... WITH f1 v1 não é sintaxe válida — WITH só aceita AUTHORITY-CHECK. A passagem de parâmetros só pode ser feita via USING bdcdata; a primeira tela do SM30 tem apenas um campo de nome de tabela (VIMDYNFLDS não tem campo de modo de atualização); para entrar no modo de modificação, use VIEW_MAINTENANCE_CALL + action = 'U'.
  • REPLACE ALL OCCURRENCES OF ... WITH '' pode causar loop infinito neste sistema de tipos — Para deletar espaços/caracteres, mude para loop caractere por caractere DO strlen TIMES + lv_txt+lv_i(1).
  • CONCATENATE não aceita tipos numéricos — lv_cnt TYPE i reporta deve ser do tipo caractere; funções embutidas (strlen/lines) não podem ser usadas diretamente dentro de && (reporta A sintaxe para especificação de método é objref->method, e o erro pode enganar parecendo um problema de sintaxe de seta). Para valores numéricos, sempre use modelo de string |...{ lv_cnt }...|.
  • Dentro de uma sonda $TMP, não é possível definir FORMs; comentários fora dos corpos dos métodos não são salvos — A ativação da sonda reporta Instrução não acessível (CheckProgram não reporta); comentários entre ENDMETHOD e o próximo METHOD em uma classe reportam comentários desconhecidos que não podem ser armazenados.
  • Nomes de SELECT-OPTIONS devem ter ≤ 8 caracteres — Um nome de 9 caracteres é rejeitado na primeira ativação. Ao encurtar o nome, você deve alterar também o nome do campo selections nos elementos de texto (variantes antigas com o nome antigo serão ignoradas silenciosamente, apenas um aviso é dado, nenhum erro).

3. Lista de Falhas Silenciosas (A Seção Mais Cara)

Sempre que "retorna S / ativação 200 / compilação ok" mas o resultado está errado, comece por aqui.

3.1 Dados Deslocados e Perdidos

  • SELECT com lista explícita de colunas INTO TABLE <tipo de tabela predefinido> faz atribuição por posição — Se os tipos forem compatíveis, não reporta erro nem aviso: DOMVAL de DD07T cai em DOMNAME, WKGBTR de COEP cai em MEGBTR (valor fica sempre vazio), ZTEPIC3A reporta componente "ZJK" não compatível com "ZJYRQ" (identificado via _act_dbg). Uma armadilha que se repetiu em 5 relatórios. Três soluções: SELECT * / INTO TABLE @DATA(...) inline (estrutura é definida pela lista) / crie uma estrutura estreita com a mesma ordem de colunas; ou INTO CORRESPONDING FIELDS OF TABLE.
  • Campos da tabela direita de um LEFT JOIN usados em WHERE ⇒ semântica degenera para INNER JOIN — Quando a tabela direita não tem correspondência, o valor é NULL e a condição falha. Mova as condições de filtro para o ON.
  • DELETE ADJACENT DUPLICATES ... COMPARING <campos não chave> pode deletar dados — A tabela de condições A017 tem chave primária incluindo DATBI, KNUMH não é chave; deduplicar com COMPARING knumh deleta todos os períodos de validade subsequentes para o mesmo KNUMH. Primeiro SORT BY todas as chaves e depois COMPARING todas as chaves.
  • AT NEW / AT END OF agrupa por adjacência física; após reescrita do ALV, certamente falha — REUSE_ALV_* reordena t_outtab in-place (acionado quando o usuário ordena por uma coluna de mensagem); itens do mesmo grupo deixam de ser adjacentes, AT END OF é acionado várias vezes. Para agrupamento, mude para "loop externo pelas chaves de agrupamento × loop interno coletando por chave", e a fronteira do grupo deve ser apenas a chave de negócio.
  • APPEND altera sy-tabix — Após APPEND INITIAL LINE ... ASSIGNING <fs>, obter sy-tabix retorna o índice da linha adicionada, não o número da linha original ⇒ o intervalo é ampliado, 128 linhas viram 558 (mesmo registro repetido 43 vezes). No início do loop, faça lv_tabix = sy-tabix.
  • READ TABLE ... WITH KEY sem sucesso define sy-tabix como 0 — Em seguida, APPEND sy-tabix TO lt_del adiciona à lista de exclusão, e DELETE ... INDEX 0 causa DUMP TABLE_INVALID_INDEX. Armazene o número da linha no início do loop e, antes de deletar, faça IF idx >= 1 AND idx <= lines( ).
  • Declaração inline DATA(...) dentro de um IF; se a condição não for atendida, a tabela interna nunca é criada ⇒ LOOP subsequente causa short dump. Mova a declaração para o cabeçalho do método ou, antes de usar, faça uma verificação de vazio.

3.2 BAPI / Funções Padrão „Retornam S ou Não Reportam Erro, Mas Não Gravam"

  • A estrutura x de um BAPI deve ser passada em pares com a estrutura original — Passar depreciationareas sem depreciationareasx ⇒ ao preencher AFASL, reporta Nenhum parâmetro de alteração fornecido para a área de depreciação 01. Faltar a estrutura x não causa erro de sintaxe, só é acionado quando as condições de dados são atendidas.
  • O data element do campo de identificação da estrutura de enhancement SD deve ser CHAR1 — SD_SALESDOCUMENT_CHANGE usa GET_CHANGEABLE_FIELDS e só processa campos onde WHERE rollname = 'CHAR1' OR 'CHAR01'; campos com BAPIUPDATE são todos excluídos ⇒ retorna S, valor não muda, zero erros, enquanto o caminho de criação não usa esta lista ⇒ típico "criação escreve, modificação não escreve". Não pode ser alterado de volta para BAPIUPDATE.
  • BAPIPAREX-VALUEPART1 tem apenas 240 caracteres — A função padrão lê sequencialmente VALUEPART1~4 (total 960) por offset; preencher apenas a primeira parte causa truncamento silencioso no final (em BAPE_VBAP, testado com 271 caracteres, 3 campos finais vazios, sem erro). Divida em quatro partes de 240.
  • Tipo de pedido de débito/crédito/sem linha de planejamento: quantidade só reconhece TARGET_QTY — Nenhum campo em BAPISDITM tem como campo de referência KWMENG; a categoria de item correspondente não tem configuração em TVEPZ ⇒ não gera linha de planejamento, o BAPISCHDL enviado é silenciosamente descartado, retorna S mas KWMENG=0, NETWR=0, VBEP 0 linhas. Use TVEPZ para verificar se há entrada como critério, não use TVAP-LFREL (tanto Z001 quanto Z014 têm LFREL vazio), nem VBTYP. Mesma armadilha no lado da leitura: VBAP-KWMENG é sempre 0, quantidade está em VBAP-ZMENG ⇒ escopo de retorno: "se KWMENG não estiver vazio, use-o; se vazio, volte para ZMENG".
  • O primeiro campo de BAPE_* é a chave; não passar no modo de modificação resulta em limpeza do número do documento — A função padrão faz move-corresponding do valor EXTENSIONIN para E_VBAKKOM; se VBELN for deixado vazio, o número do documento é sobrescrito para vazio ⇒ V1 045 seguido por V4 219 (a causa raiz está na mensagem E antes de V4 219).
  • Documentos de referência SD são um conjunto de três peças — REF_DOC + REF_DOC_IT + REF_DOC_CA (categoria do documento de referência); passar apenas os dois primeiros reporta V2 068 sem categoria de documento mas com C (&2 vazio). Número de linha como 0 ou documento inexistente é a mesma mensagem, mas &3 estará vazio. A categoria deve ser obtida do próprio documento de referência VBAK-VBTYP, não do cabeçalho VGTYP.
  • SD_SALESDOCUMENT_CHANGE requer que as duas tabelas de entrada sejam ordenadas separadamente — Para linhas a serem excluídas, tanto ITEM_IN quanto ITEM_INX devem ter ITM_NUMBER preenchido; preencher apenas ITEM_INX causa desalinhamento e reporta uma mensagem totalmente não relacionada T184 Nenhuma categoria de item disponível.
  • BAPI_USER_EXISTENCE_CHECK neste sistema retorna 0 para qualquer conta (até ZZZZZZ999 passa) ⇒ a verificação é inútil, documentos atribuídos a usuários inválidos ficam invisíveis em ambas as interfaces. Consulte diretamente USR02 + proteção de comprimento (0 < len <= 12).
  • DELETE_TEXT para um texto inexistente gera RAISING NOT_FOUND — A função padrão anexa esta mensagem E a BAPIRET2, que é considerada uma falha por frm_check_return ⇒ a operação de "limpar" transforma a interface em E. Antes de limpar, consulte STXH (TDOBJECT+TDNAME+TDID+TDSPRAS); se não existir, pule (o que também torna a operação idempotente). Além disso: em SD_SALES_DOCU_MAINTAIN, o código de função para limpar é 003 (DELETE_TEXT); passar &&/005 apenas reescreve o texto como uma linha em branco.
  • Ao adicionar novas condições de preço, não especifique o número da etapa — Codificar COND_ST_NO='001' enquanto ZP01 está em STUNR=010 ⇒ reporta "Tipo de preço não está no número de etapa 001". Deixe vazio para a determinação do preço localizar automaticamente; use "se a condição já existe" para direcionar UPDATEFLAG para I/U.
  • Ao dividir novamente um documento na interface, OBJ_KEY retorna apenas um número de documento ⇒ o número de documentos é subreportado, e a escrita por linha aponta para o documento errado. O layout de OBJ_KEY é Número do documento(10)+Código da empresa(4)+Ano fiscal(4), o ano está em +14(4) (frequentemente mal escrito como +10(4), obtendo o código da empresa).
  • BSEG-BUZEI é NUMC(3), limite físico de 999 linhas por documento; o BAPI na prática só aceita 990 linhas — O limite de divisão é 989 detalhes + 1 contrapartida; a margem deve cobrir 999/1000/1997 (1997, se tratado como >999, resultará em 1000 linhas, causando estouro).
  • BAPIACHE09 não tem campo EXCH_RATE — Com moeda dupla (linhas 00+10), se apenas a linha 00 tiver taxa de câmbio, o sistema redefinirá a taxa de câmbio do cabeçalho com base em OB08 (inserir 7 resultará em 8.28). A linha CURR_TYPE='10' também deve ter exch_rate, tanto na linha de detalhe quanto na linha de contrapartida.
  • Para documentos com moeda dupla, cada ITEMNO_ACC deve ter duas entradas CURRENCYAMOUNT, e o subtotal de cada tipo de moeda deve ser 0 — Faltar a linha 10 reporta Informação de moeda inconsistente; a linha de contrapartida gerada pela divisão também deve ser complementada.
  • UMSKZ não tem campo em nenhuma das três estruturas do BAPI — Para tipos de título (W/S), usar o canal BAPI padrão resultará em descarte silencioso e lançamento em conta errada. Só pode ser gravado via enhancement EXTENSION2, ou o documento inteiro via POSTING_INTERFACE_* (entrada em lote FB01).
  • O parâmetro formal NAME de READ_TEXT é TDOBNAME(70), o parâmetro real TDNAME(16) causa conflito de tipo em tempo de execução — Compilação bem-sucedida, parâmetros vazios funcionam, mas ao encontrar dados causa 500 Type conflict during a function module call. Escreva o parâmetro formal como TYPE tdobname ou leia diretamente a tabela cluster STXL.

3.3 Texto / Descrição / Título de Coluna Ficando Vazio

  • Campo usa tipo anônimo inline ⇒ DD03T fica sem texto ⇒ coluna do ALV fica completamente em branco — O campo deve ter um data element (após anexar, DD03T ter 0 linhas é um estado correto, o texto é movido para cima). O texto de campo de estruturas só pode ser definido via DDL campo a campo com @EndUserText.label; campos aninhados do tipo STRU/TTYP nunca terão texto em DD03T (o mesmo se aplica a estruturas padrão SAP). O fields[].description de CreateStructure não entra em DD03T.
  • Elementos de texto não ativados / apenas versão inativa ⇒ em tempo de execução TEXT-xxx está sempre vazio — Manifesta-se como títulos de coluna do ALV revertendo para o texto curto do DDIC e colunas de status vazias.
  • Domínio define apenas o tipo, sem valores de domínio ⇒ F4 da tela de seleção não exibe candidatos — Ao consultar DD07T, é obrigatório usar AS4LOCAL='A' (caso contrário, obtém linhas inativas), ORDER BY VALPOS (caso contrário, obtém ordem lexicográfica 1,10,11,2,3…) e fazer fallback de idioma para o idioma mestre do domínio em DD01L-DOMMASTER (o nome do campo não é MASTERLANG).
  • MESSAGE ... WITH tem um limite de aproximadamente 70 caracteres para a mensagem inteira ⇒ Erros BAPI longos são truncados silenciosamente para algo como Field Reas..., desviando a investigação. Use um prefixo curto + modelo de string seguido do texto original (≤220 caracteres, alinhado com BAPI_MSG).
  • BAPIACGL09-ITEM_TEXT(50) / BAPIACHE09-HEADER_TXT(25) trunca silenciosamente à direita — Ao concatenar "prefixo + sufixo", primeiro trunque o prefixo para comprimento - comprimento do sufixo.
  • Nomes de componentes DDIC são truncados silenciosamente em 30 caracteres — O mapeamento de chaves JSON pode perder chaves misteriosamente.

3.4 Autorizações e Filtros

  • Passar valor vazio em AUTHORITY-CHECK geralmente significa sem autorização ⇒ registros relacionados permanentemente invisíveis — As roles normalmente não concedem EKGRP = space. Quando um campo de organização está vazio, não faça a verificação e simplesmente permita o acesso: IF lv_ekgrp IS NOT INITIAL. AUTHORITY-CHECK ... ENDIF.. Use filtros no nível de linha em vez de rejeitar a tela inteira; caso contrário, usuários sem autorização não conseguirão nem entrar no código de transação.
  • MODIF ID controla apenas visibilidade; valores já preenchidos em campos ocultos ainda participam da filtragem — Para cenários de ocultação, use RANGE para bloquear explicitamente no lado da obtenção de dados.

4. Transações e LUW

  • EXPORT ... TO DATABASE (log de cluster) é controlado pela transação — "Gravar tabela de base → Criar documento → Falha com rollback" faz com que o log e a tabela de base estejam no mesmo LUW; ROLLBACK consome o snapshot dos parâmetros de entrada de indx=01 junto (embora indx=02 ainda permaneça). Além disso, o FM de contabilização subjacente internamente também executa BAPI_TRANSACTION_ROLLBACK, então "remover seu próprio ROLLBACK" ou "mudar a ordem" não funciona. ⇒ Solução: insira uma instrução COMMIT WORK independente antes da única ação de escrita no banco de dados e antes de chamar o BAPI; o ROLLBACK permanece intacto. Benefício adicional: liquidar seu próprio LUW antes de chamar um BAPI já é uma prática padrão.
  • Se quiser atomicidade, não faça commit separadamente; se quiser commits independentes, mantenha a releitura — O CALL_FROM_BAPI='X' de SD_SALESDOCUMENT_CHANGE coloca a atualização na fila de update; antes do commit, não é possível consultar ⇒ remover COMMIT WORK AND WAIT faz com que a verificação de releitura SELECT restante julgue erroneamente "indicador não atualizado". No ramo de falha de contabilização, um ROLLBACK WORK deve ser adicionado, caso contrário, a atualização SD pendente pode ficar suspensa e ser erroneamente gravada no próximo commit.
  • O snapshot da plataforma só vincula os parâmetros do FM; variáveis locais dentro da função nunca são vinculadas — A mensagem em lv_req/lv_resp não é visível na plataforma; o serialize do monitor não usa name_mappings, então ele gera nomes DDIC em maiúsculas em vez da mensagem de contrato. Construa sua própria tabela de log de mensagens no lado de negócios + uma linha save( ) escrita no único local onde a mensagem é gerada (final de frm_send); o commit independente complementa a deficiência da plataforma de consumir logs devido a rollback.

5. Pop-ups / F4 / ALV / Tela de Seleção

  • A semântica de RETURNCODE de POPUP_GET_VALUES é contraintuitiva — 'A' = Cancelar; espaço = Confirmar e FIELDS foram preenchidos (em LSPO4U01: IF ok_code_save = 'CANC'. returncode = 'A'. ELSE. fill_values. returncode = space.). Escrever IF lv_rc <> 'A' trata "Confirmar" como "Cancelar" ⇒ o fenômeno é clicar em confirmar no pop-up e não acontecer nada (sem mensagem, sem ação, sem dump).
  • POPUP_GET_VALUES só exibe F4 quando o campo está associado a uma search help do DDIC — O critério é F4IF_DETERMINE_SEARCHHELP (0=tem / 2=campo existe mas sem help / 1=campo não existe). Não tente usar SKA1-SAKNR emprestado — isso introduzirá o preenchimento com zeros do ALPHA, '8000' se tornará '000008000', a validação reportará erro falso e o formato de armazenamento mudará. ⇒ Em vez disso, use POPUP_GET_VALUES_USER_HELP + F4_FORMNAME + F4_PROGRAMNAME = sy-repid, sem alterar os atributos do campo. O FORM de saída é chamado pelo FM com 5 parâmetros USING (tabname/fieldname/display_flag/returncode/value), escreva o valor selecionado de volta no 5º parâmetro e defina returncode como 'F' para que o pop-up seja atualizado (não é possível obter sy-stepl, então não use preenchimento dynprofield).
  • POPUP_GET_VALUES não suporta ocultar campos por condição — SVAL só pode controlar FIELD_ATTR (obrigatório/somente leitura). Para carregar apenas o conjunto de campos correspondente ao tipo, se o usuário alterar o tipo, re-exiba o pop-up com base no novo tipo (se o tipo não mudar, apenas saia, sem loop).
  • Ao passar FIELD_TAB para F4IF_INT_TABLE_VALUE_REQUEST, cada linha deve permitir derivar INTTYPE — A cadeia de derivação é TABNAME → INTTYPE → DATATYPE → DOMNAME → ROLLNAME... (truncado para tradução).

Habilidades Relacionadas