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):
- 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.
- 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. - Se falhar, consulte ST22, não adivinhe — A
Error analysisde um short dump fornece diretamente oprocedure "XXX" "(FORM)"e a classe de exceçãoCX_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)
- 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. - Elementos de texto também fazem parte da baseline;
get_sourcenão inclui TEXTPOOL. Antes de enviar texto de forma destrutiva, você deveread_textelementspara 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. - Nunca use diretamente UPDATE / MODIFY em tabelas padrão. Para alterar documentos FI já contabilizados, use
FI_DOCUMENT_CHANGE(grupoFACG,T_ACCHGsó preencheFDNAME+NEWVAL, não passeI_BUZEIno cabeçalho, só em itens). - Não crie números de solicitação por conta própria. Se faltar
CORR_NRao 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. - Não use classes
Update*para fazer chamadas exploratórias em interfaces. Elas realmente escrevem — já houve caso de usarUpdateFunctionModuleapenas 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. - Quatro passos para entrega: Gerar → Enviar código fonte → Enviar elementos de texto (
PROG/PXdevem ser ativados separadamente) → Gerar status GUI (CUA). Se os elementos de texto não forem ativados, em tempo de execuçãoTEXT-xxxestará sempre vazio. - 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 ascripts/work/ADT/ZTEST_*. Após a remoção, um diff deve provar que o corpo do código em si não foi alterado. - Objetos de teste/sonda devem ser excluídos imediatamente após o teste. Primeiro
get_sourcepara arquivar embackup/→ excluir → verificarTADIRpara 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. - 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.
- 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=activee inativa.TRDIR-RSTATnã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 é prefixochkl:+<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; sepush_textelementsfizer apenasGET→LOCK→PUT→UNLOCKsem ACTIVATE, o PUT escreve apenas a versão inativa. Ao enviarPROG/P, o SAP considera que não há conteúdo ativável e retornaactivationExecuted="false"sem nenhuma mensagem ⇒ falha silenciosa. A interface de ativação não aceitacorrNr(resulta em 400). - O PUT de
push_textelementssubstitui 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. OTE_FILEdeve incluir todas asselectionsexistentes 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; ②UpdateDataElementreportalocked by another userquando é, na verdade, um 400 "já existe" — oisAlreadyExistsError()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;UpdateDomainnã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/SEOCLSENQestão associados à sessão ADT;DEQUEUE_ALL,_scope='3'eDEQUEUE_EESDICTsão todos ineficazes. Após confirmar comENQUEUE_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
Acceptdo ADT LOCK deve incluir o datatype — Enviar apenasapplication/xmlfaz com que o SAP já tenha bloqueado com sucesso, mas retorne 406 e você não receba oLOCK_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 defail()do script pula ofinally⇒ lock de enqueue residual — Após saída anormal de scripts de push, o lockSEOCLSENQpode 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 useprocess.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/programssempre 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, executeINSERT REPORT 'X' FROM lt_src STATE 'A' PROGRAM TYPE 'I'.(STATEdeve vir antes dePROGRAM 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*_proxye definaNO_PROXY=*; para solicitações de escrita, sempre useproxy: 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_STATUSnão pode ser usado para determinar, apenasRS_CUA_INTERNAL_FETCH(passe todas as 10 TABLES obrigatórias, useORDER BY ppositionpara 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(reportaNumeric literais não podem ser usados como nomes de campo) ⇒ useTYPE 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 500Type conflict during a call of a FORM⇒ para comprimentos variados, use genéricoTYPE c; ③ Não pode usarTYPE STANDARD TABLE OFinline (o parâmetro formal é contado como 8, reportaNúmero diferente de parâmetros) ⇒ primeiro declare comTYPES; ④FIELD-SYMBOLdeclarado dentro de um FORM é local, para usar entre FORMs deve ser global. FORM ... TABLESdeve vir antes deUSING/CHANGING— EscreverUSING ... CHANGING ... TABLESreportaField "TABLES" is unknown.(trata a palavra-chave como nome de parâmetro formal). O mesmo se aplica ao lado da chamada comPERFORM.- A ordem dos parágrafos em
CALL FUNCTIONé obrigatória —EXPORTING → IMPORTING → CHANGING → TABLES → EXCEPTIONS. ColocarTABLESantes deIMPORTINGreportaUnable to interpret "EV_OBJNR", sem indicar que é um problema de ordem. Parâmetros formaisEXPORTINGda função chamada devem estar no parágrafoIMPORTINGdo chamador; se invertidos, reportaCALL_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 — Reportaos elementos na "SELECT LIST" devem ser separados por vírgulas. O mesmo vale para múltiplas atribuições emUPDATE ... SET; constantes emWHERE(incluindoCONSTANTS) também precisam de@. - A posição de
INTOeUP TO n ROWSdeve seguir o erro do ativador — Instruções com JOIN/subconsultas/expressões SQL estão em modo estrito, forçandoINTOno final (SELECT 563);UP TO n ROWSdeve vir antes deWHERE. 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; removerLENGTHcausa DUMPInvalid subfield accessquando o índice está fora dos limites. A solução correta é usarcl_abap_container_utilities=>fill_container_c( )(inverso da função padrãoREAD_CONTAINER_C).- Em
DESCRIBE FIELD,TYPEdeve vir antes deCOMPONENTS— Mesmo que você só queira o número de componentes, deve escrever:DESCRIBE FIELD cs_data TYPE lv_type COMPONENTS lv_count. CONSTANTSdeve terVALUE—CONSTANTS: c_x TYPE flag.reporta"VALUE ..." not specified.MESSAGE TEXT-xxx TYPE 'S' WITH nnão é válido — Símbolos de texto não podem ser usados como texto de mensagem comWITH. 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/char220são todos inválidos, reportamType "X" is unknown; sempre useTYPE c LENGTH n. Adivinhar o nome do tipo errado = um ciclo de ativação desperdiçado; se não tiver certeza, consulteDD04Louget_source. class=>methodsó pode ser usado para métodos estáticos — Métodos de instância exigemCREATE OBJECT lo. lo->method( ), caso contrário, reportasó 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— ReportaField "|" is unknown; atribua a uma variável primeiro. CALL TRANSACTION ... WITH f1 v1não é sintaxe válida —WITHsó aceitaAUTHORITY-CHECK. A passagem de parâmetros só pode ser feita viaUSING bdcdata; a primeira tela do SM30 tem apenas um campo de nome de tabela (VIMDYNFLDSnão tem campo de modo de atualização); para entrar no modo de modificação, useVIEW_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 caractereDO strlen TIMES+lv_txt+lv_i(1).CONCATENATEnão aceita tipos numéricos —lv_cnt TYPE ireporta deve ser do tipo caractere; funções embutidas (strlen/lines) não podem ser usadas diretamente dentro de&&(reportaA 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 reportaInstrução não acessível(CheckProgramnão reporta); comentários entreENDMETHODe o próximoMETHODem uma classe reportamcomentários desconhecidos que não podem ser armazenados. - Nomes de
SELECT-OPTIONSdevem 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 camposelectionsnos 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
SELECTcom lista explícita de colunasINTO TABLE <tipo de tabela predefinido>faz atribuição por posição — Se os tipos forem compatíveis, não reporta erro nem aviso:DOMVALdeDD07Tcai emDOMNAME,WKGBTRdeCOEPcai emMEGBTR(valor fica sempre vazio),ZTEPIC3Areportacomponente "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; ouINTO CORRESPONDING FIELDS OF TABLE.- Campos da tabela direita de um
LEFT JOINusados emWHERE⇒ semântica degenera paraINNER 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 oON. DELETE ADJACENT DUPLICATES ... COMPARING <campos não chave>pode deletar dados — A tabela de condições A017 tem chave primária incluindoDATBI,KNUMHnão é chave; deduplicar comCOMPARING knumhdeleta todos os períodos de validade subsequentes para o mesmo KNUMH. PrimeiroSORT BYtodas as chaves e depoisCOMPARINGtodas as chaves.AT NEW/AT END OFagrupa por adjacência física; após reescrita do ALV, certamente falha —REUSE_ALV_*reordenat_outtabin-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.APPENDalterasy-tabix— ApósAPPEND INITIAL LINE ... ASSIGNING <fs>, obtersy-tabixretorna 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çalv_tabix = sy-tabix.READ TABLE ... WITH KEYsem sucesso definesy-tabixcomo 0 — Em seguida,APPEND sy-tabix TO lt_deladiciona à lista de exclusão, eDELETE ... INDEX 0causa DUMPTABLE_INVALID_INDEX. Armazene o número da linha no início do loop e, antes de deletar, façaIF idx >= 1 AND idx <= lines( ).- Declaração inline
DATA(...)dentro de umIF; se a condição não for atendida, a tabela interna nunca é criada ⇒LOOPsubsequente 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
xde um BAPI deve ser passada em pares com a estrutura original — Passardepreciationareassemdepreciationareasx⇒ ao preencher AFASL, reportaNenhum parâmetro de alteração fornecido para a área de depreciação 01. Faltar a estruturaxnã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_CHANGEusaGET_CHANGEABLE_FIELDSe só processa campos ondeWHERE rollname = 'CHAR1' OR 'CHAR01'; campos comBAPIUPDATEsã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 paraBAPIUPDATE. BAPIPAREX-VALUEPART1tem apenas 240 caracteres — A função padrão lê sequencialmenteVALUEPART1~4(total 960) por offset; preencher apenas a primeira parte causa truncamento silencioso no final (emBAPE_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 emBAPISDITMtem como campo de referênciaKWMENG; a categoria de item correspondente não tem configuração emTVEPZ⇒ não gera linha de planejamento, oBAPISCHDLenviado é silenciosamente descartado, retorna S masKWMENG=0,NETWR=0,VBEP0 linhas. UseTVEPZpara verificar se há entrada como critério, não useTVAP-LFREL(tantoZ001quantoZ014têmLFRELvazio), nemVBTYP. Mesma armadilha no lado da leitura:VBAP-KWMENGé sempre 0, quantidade está emVBAP-ZMENG⇒ escopo de retorno: "seKWMENGnão estiver vazio, use-o; se vazio, volte paraZMENG". - 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 fazmove-correspondingdo valorEXTENSIONINparaE_VBAKKOM; seVBELNfor deixado vazio, o número do documento é sobrescrito para vazio ⇒V1 045seguido porV4 219(a causa raiz está na mensagem E antes deV4 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 reportaV2 068 sem categoria de documento mas com C(&2vazio). Número de linha como 0 ou documento inexistente é a mesma mensagem, mas&3estará vazio. A categoria deve ser obtida do próprio documento de referênciaVBAK-VBTYP, não do cabeçalhoVGTYP. SD_SALESDOCUMENT_CHANGErequer que as duas tabelas de entrada sejam ordenadas separadamente — Para linhas a serem excluídas, tantoITEM_INquantoITEM_INXdevem terITM_NUMBERpreenchido; preencher apenasITEM_INXcausa desalinhamento e reporta uma mensagem totalmente não relacionadaT184 Nenhuma categoria de item disponível.BAPI_USER_EXISTENCE_CHECKneste sistema retorna 0 para qualquer conta (atéZZZZZZ999passa) ⇒ a verificação é inútil, documentos atribuídos a usuários inválidos ficam invisíveis em ambas as interfaces. Consulte diretamenteUSR02+ proteção de comprimento (0 < len <= 12).DELETE_TEXTpara um texto inexistente geraRAISING NOT_FOUND— A função padrão anexa esta mensagem E aBAPIRET2, que é considerada uma falha porfrm_check_return⇒ a operação de "limpar" transforma a interface em E. Antes de limpar, consulteSTXH(TDOBJECT+TDNAME+TDID+TDSPRAS); se não existir, pule (o que também torna a operação idempotente). Além disso: emSD_SALES_DOCU_MAINTAIN, o código de função para limpar é003(DELETE_TEXT); passar&&/005apenas 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'enquantoZP01está emSTUNR=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 direcionarUPDATEFLAGparaI/U. - Ao dividir novamente um documento na interface,
OBJ_KEYretorna apenas um número de documento ⇒ o número de documentos é subreportado, e a escrita por linha aponta para o documento errado. O layout deOBJ_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).BAPIACHE09não tem campoEXCH_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 linhaCURR_TYPE='10'também deve terexch_rate, tanto na linha de detalhe quanto na linha de contrapartida.- Para documentos com moeda dupla, cada
ITEMNO_ACCdeve ter duas entradasCURRENCYAMOUNT, e o subtotal de cada tipo de moeda deve ser 0 — Faltar a linha10reportaInformação de moeda inconsistente; a linha de contrapartida gerada pela divisão também deve ser complementada. UMSKZnã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 enhancementEXTENSION2, ou o documento inteiro viaPOSTING_INTERFACE_*(entrada em lote FB01).- O parâmetro formal
NAMEdeREAD_TEXTéTDOBNAME(70), o parâmetro realTDNAME(16) causa conflito de tipo em tempo de execução — Compilação bem-sucedida, parâmetros vazios funcionam, mas ao encontrar dados causa 500Type conflict during a function module call. Escreva o parâmetro formal comoTYPE tdobnameou leia diretamente a tabela clusterSTXL.
3.3 Texto / Descrição / Título de Coluna Ficando Vazio
- Campo usa tipo anônimo inline ⇒
DD03Tfica sem texto ⇒ coluna do ALV fica completamente em branco — O campo deve ter um data element (após anexar,DD03Tter 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 tipoSTRU/TTYPnunca terão texto emDD03T(o mesmo se aplica a estruturas padrão SAP). Ofields[].descriptiondeCreateStructurenão entra emDD03T. - Elementos de texto não ativados / apenas versão inativa ⇒ em tempo de execução
TEXT-xxxestá 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 usarAS4LOCAL='A'(caso contrário, obtém linhas inativas),ORDER BY VALPOS(caso contrário, obtém ordem lexicográfica1,10,11,2,3…) e fazer fallback de idioma para o idioma mestre do domínio emDD01L-DOMMASTER(o nome do campo não éMASTERLANG). MESSAGE ... WITHtem um limite de aproximadamente 70 caracteres para a mensagem inteira ⇒ Erros BAPI longos são truncados silenciosamente para algo comoField Reas..., desviando a investigação. Use um prefixo curto + modelo de string seguido do texto original (≤220 caracteres, alinhado comBAPI_MSG).BAPIACGL09-ITEM_TEXT(50) /BAPIACHE09-HEADER_TXT(25) trunca silenciosamente à direita — Ao concatenar "prefixo + sufixo", primeiro trunque o prefixo paracomprimento - 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-CHECKgeralmente significa sem autorização ⇒ registros relacionados permanentemente invisíveis — As roles normalmente não concedemEKGRP = 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 IDcontrola 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;ROLLBACKconsome o snapshot dos parâmetros de entrada deindx=01junto (emboraindx=02ainda permaneça). Além disso, o FM de contabilização subjacente internamente também executaBAPI_TRANSACTION_ROLLBACK, então "remover seu próprio ROLLBACK" ou "mudar a ordem" não funciona. ⇒ Solução: insira uma instruçãoCOMMIT WORKindependente antes da única ação de escrita no banco de dados e antes de chamar o BAPI; oROLLBACKpermanece 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'deSD_SALESDOCUMENT_CHANGEcoloca a atualização na fila de update; antes do commit, não é possível consultar ⇒ removerCOMMIT WORK AND WAITfaz com que a verificação de releituraSELECTrestante julgue erroneamente "indicador não atualizado". No ramo de falha de contabilização, umROLLBACK WORKdeve 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_respnão é visível na plataforma; oserializedo monitor não usaname_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 linhasave( )escrita no único local onde a mensagem é gerada (final defrm_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
RETURNCODEdePOPUP_GET_VALUESé contraintuitiva —'A'= Cancelar; espaço = Confirmar eFIELDSforam preenchidos (emLSPO4U01:IF ok_code_save = 'CANC'. returncode = 'A'. ELSE. fill_values. returncode = space.). EscreverIF 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_VALUESsó 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 usarSKA1-SAKNRemprestado — 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, usePOPUP_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âmetrosUSING(tabname/fieldname/display_flag/returncode/value), escreva o valor selecionado de volta no 5º parâmetro e definareturncodecomo'F'para que o pop-up seja atualizado (não é possível obtersy-stepl, então não use preenchimento dynprofield).POPUP_GET_VALUESnão suporta ocultar campos por condição —SVALsó pode controlarFIELD_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_TABparaF4IF_INT_TABLE_VALUE_REQUEST, cada linha deve permitir derivarINTTYPE— A cadeia de derivação éTABNAME → INTTYPE → DATATYPE → DOMNAME → ROLLNAME...(truncado para tradução).