REDA One Centro de ajuda
PT
Idioma
English EN Español ES Português PT Italiano em breve Français FR Deutsch em breve العربية em breve
reda.one ↗
Início / REDA Financials AI / API e integração / Desenvolvimento personalizado / Desenvolvimento em Apex na REDA

Desenvolvimento em Apex na REDA

O Apex personalizado ao redor da REDA Financials segue uma regra de ouro: construa contra a superfície global e os objetos, nunca contra os componentes internos. Aqui está o conjunto de ferramentas estável e os padrões que o utilizam bem.

A superfície global — treze classes e interfaces

Em um pacote gerenciado, apenas o Apex global fica visível para o seu código; tudo o que é public vive atrás da barreira do namespace. A REDA Financials mantém essa superfície suportada deliberadamente pequena — treze tipos globais para código de clientes e parceiros. Cinco são os pontos de entrada do dia a dia; os demais são os contratos de extensão de nota fiscal eletrônica e os tipos de dados que eles trocam:

ClasseTipoPara que serve
refin.GlobalTaxServiceglobal with sharingFachada sobre os motores de imposto e de retenção na fonte; dois métodos estáticos de cálculo, sem DML
refin.PDFGeneratorAPIglobal with sharingGeração de PDF — uma ação invocável de Fluxo mais um método Apex global
refin.BankFeedProviderglobal abstractContrato de extensão para provedores de feed bancário (Plaid, Belvo, …) — você o implementa, o pacote o chama
refin.EInvoiceProviderglobal abstractContrato de extensão para provedores de nota fiscal eletrônica (Sovos, Peppol, ZATCA, SAT, SDI, …)
refin.OCREmailHandlerglobal, implementa Messaging.InboundEmailHandlerManipulador de e-mail de entrada que transforma anexos em PDF em registros da Caixa de entrada de OCR para automação de contas a pagar
refin.EInvoiceStatusPollSchedulableglobal with sharing, implementa SchedulableA superfície de agendamento voltada ao assinante para a consulta de status de nota fiscal eletrônica: agende com o seu próprio cron em vez de usar o cartão do Console de administração
refin.EInvoiceTrailCreatorglobal with sharingCria uma trilha de nota fiscal eletrônica para faturas de venda contabilizadas; também exposta como a ação de Flow Create E-Invoice Trail
refin.EInvoiceQueueProviderinterface globalContrato de fila para extensões de nota fiscal eletrônica. Foi adicionado ao lado de EInvoiceProvider e não como métodos dele, porque uma classe abstrata global já publicada não pode ganhar métodos sem quebrar quem a implementa
refin.EInvoiceDocumentProviderinterface globalContrato de recuperação de documentos para extensões de nota fiscal eletrônica
refin.EInvoiceProvisionerinterface globalContrato de provisionamento para extensões de nota fiscal eletrônica
refin.EInvoiceDocument · refin.EInvoiceEvent · refin.EInvoiceProvisionResultclasses globais (DTOs)Os tipos de transferência de dados que os contratos acima recebem e devolvem

Os contratos abstratos de provedor são abordados em Pacotes de extensão; este artigo foca nas duas classes que você chama. Todas as classes de API são entregues na versão de API 65.0, e vale a promessa padrão de pacote gerenciado: uma vez lançada, uma assinatura global não pode ser removida nem alterada sob os seus pés — ela é o contrato estável.

O que você não pode chamar

A camada de serviço interna que faz o trabalho de verdade — contabilização, importação de extrato bancário, renderização de PDF, chamadas de IA e de câmbio, atualizações de status em massa, carga inicial de impostos — é public, não global. O Apex do assinante não consegue chamar essas classes de forma alguma: o compilador simplesmente não as enxerga através da fronteira do namespace, e sua forma interna pode mudar em qualquer versão. Se um recurso de que você precisa vive apenas atrás de uma classe pública, a rota suportada é o comportamento que ela viabiliza — contabilização pelo campo de status, geração de PDF via refin.PDFGeneratorAPI, carga inicial de impostos via o Assistente de Configuração de Impostos empacotado — não a classe em si.

O namespace importa. Sempre qualifique as chamadas com o namespace refin — refin.GlobalTaxService.calculateTax(...), refin.PDFGeneratorAPI.generateOne(...). Esse é o nome que a sua organização enxerga para as classes dentro do pacote gerenciado.

refin.GlobalTaxService — o cálculo de impostos como serviço

Dois métodos estáticos dão ao seu código os mesmos motores que os documentos usam, sem efeitos colaterais:

  • calculateTax(TaxRequest) — imposto indireto (IVA/GST/imposto sobre vendas). Informe entidade, conta, data, lado ('Sales'/'Purchase') e linhas (valor + produto ou perfil fiscal explícito, com sua própria lineKey para correlação); receba de volta linhas de imposto por linha totalmente resolvidas — alíquota, valor, sinalizador de recuperabilidade e a conta contábil em que cada imposto seria contabilizado.
  • calculateWithholdingTax(WHTRequest) — o motor de retenção na fonte, mesma estrutura: linhas entram, retenção por linha sai (alíquota, base, valor, motivos de isenção, conta contábil).
  • O contrato: sem DML, nenhum registro criado, nunca lança exceção — os erros retornam como success=false com uma mensagem. A persistência é inteiramente sua: perfeito para cotações, calculadoras, estimativas pré-contabilização ou verificações de paridade com sistemas externos.

A referência completa

Ambos os pontos de entrada são estáticos:

global static TaxResponse calculateTax(TaxRequest request)
global static WHTResponse calculateWithholdingTax(WHTRequest request)

A documentação da classe declara o contrato claramente: "Nenhum DML é executado. Nenhum registro de Source_Line_Tax ou WHT_Transaction é criado. Quem chama a classe é responsável pela persistência e pela contabilização." Uma resposta "sempre é retornada, nunca lança exceção para quem chamou" — problemas de validação (entidade, conta, data, lado ou linhas ausentes, ou um valor de lado inválido) retornam como success=false com o motivo em errorMessage, na mesma linguagem de validação que os próprios documentos usam. Existe uma classe GlobalTaxServiceException na superfície global, mas, sob o contrato normal, você lê a resposta em vez de capturar exceções.

Os DTOs de requisição e resposta são classes globais com membros globais — tudo abaixo é seu para definir ou ler:

DTOMembroO que contém
TaxRequestentityIdA entidade cuja configuração fiscal se aplica
accountIdA conta de cliente ou fornecedor na transação
transactionDateData para a qual as alíquotas e regras são resolvidas
side'Sales' ou 'Purchase'
fiscalOperationId / linesSubstituição opcional de operação fiscal; a lista de linhas TaxLineRequest
TaxLineRequestlineKeySua chave de correlação — devolvida na linha de resultado correspondente
amountO valor da linha sobre o qual calcular
productId / taxProfileIdO produto direciona a busca do perfil fiscal; um taxProfileId explícito substitui e ignora essa busca
taxBase'Exclusive of Tax' (padrão), 'Inclusive of Tax' ou 'Tax Exempt'
TaxResponsesuccess / errorMessageO contrato de resultado — verifique success antes de ler os resultados
nexusEstablished / totalTaxAmount / totalEmbeddedTaxAmount / linesResultados em nível de cabeçalho mais o detalhe por linha
TaxResultLinelineKey / taxTypeId / taxRuleIdQual linha, qual imposto, qual regra foi acionada
taxBase / taxRate / taxAmount / isTaxIncludedInPrice / isRecoverable / sequenceO cálculo resolvido, exatamente como a contabilização o calcularia
glAccountId / deductionGlAccountIdA conta contábil em que o imposto seria contabilizado; a conta de dedução é não nula apenas para impostos embutidos (por-dentro)

O conjunto de retenção na fonte espelha isso:

DTOMembroO que contém
WHTRequestentityId / accountId / transactionDate / side / documentCurrencyCode / linesMesma estrutura de cabeçalho do imposto; documentCurrencyCode é opcional e usado na conversão do limite de multimoeda
WHTLineRequestlineKey / lineSubtotal / lineTaxAmount / ruleOverrideIdlineTaxAmount alimenta o modo Base Inclui Imposto; ruleOverrideId prevalece sobre a regra padrão da conta
WHTResponsesuccess / errorMessage / totalWHTAmount / linestotalWHTAmount soma apenas as linhas não isentas
WHTResultLinelineKey / ruleId / rateId / rate / base / amount / isExempt / exemptReason / glAccountIdRetenção por linha, incluindo por que uma linha está isenta

Como as contas contábeis são resolvidas. As contas contábeis de impostos indiretos vêm dos registros de Mapeamento de Tipo de Imposto para o Razão (refin__Tax_Type_GL_Mapping__c): um mapeamento específico da entidade prevalece sobre um mapeamento em nível de organização, indexado pelo tipo de imposto e pela recuperabilidade referente ao lado da requisição; impostos embutidos recebem adicionalmente uma conta de dedução do lado 'Sales Deduction' / 'Purchase Deduction' do mapeamento. As contas contábeis de retenção vêm da Conta Contábil na Alíquota de Retenção na Fonte correspondente, recorrendo à conta padrão de Retenção a Pagar da entidade quando não há correspondência. Em outras palavras, a resposta informa não apenas quanto, mas onde isso cairia — a mesma resolução que a contabilização realiza.

Uma chamada mínima, ao estilo Meridian — a entidade dos EUA precificando a linha de aluguel de um locatário antes de qualquer fatura existir:

refin.GlobalTaxService.TaxRequest req = new refin.GlobalTaxService.TaxRequest();
req.entityId = meridianUsEntityId;
req.accountId = brightlineAccountId;
req.transactionDate = Date.today();
req.side = 'Sales';

refin.GlobalTaxService.TaxLineRequest line = new refin.GlobalTaxService.TaxLineRequest();
line.lineKey = 'unit-4b-rent';
line.amount = 9000;
line.productId = officeRentProductId;
req.lines = new List<refin.GlobalTaxService.TaxLineRequest>{ line };

refin.GlobalTaxService.TaxResponse res = refin.GlobalTaxService.calculateTax(req);
if (res.success) {
    for (refin.GlobalTaxService.TaxResultLine t : res.lines) {
        System.debug(t.lineKey + ' -> rate ' + t.taxRate + ', tax ' + t.taxAmount
            + ', GL ' + t.glAccountId);
    }
} else {
    System.debug(res.errorMessage);
}

O gêmeo de retenção segue o mesmo ritmo. A entidade da Índia da Meridian, estimando o TDS sobre a fatura de um prestador antes de ela ser lançada: monte um WHTRequest com side = 'Purchase', um WHTLineRequest por linha da fatura carregando lineSubtotal, chame calculateWithholdingTax e leia cada WHTResultLine — rate, base, amount e isExempt com seu exemptReason quando um certificado ou limite isenta a linha. Como nada é persistido, a equipe de contas a pagar pode informar o valor líquido a pagar ao fornecedor e os números vão bater com o documento contabilizado até o último centavo.

refin.PDFGeneratorAPI — documentos a partir de código

generateOne(sourceRecordId, sourceObjectApiName) renderiza e anexa o PDF de uma Fatura de venda, Nota de crédito, Recebimento, Desembolso ou Ordem de compra contabilizados — retornando os IDs de conteúdo e, onde o território habilita links públicos, a URL. O gêmeo em Fluxo é abordado em Integração de Fluxos do Salesforce.

A assinatura:

global static PDFResult generateOne(Id sourceRecordId, String sourceObjectApiName)

Nas próprias palavras da classe, "pacotes de extensão chamam isso a partir do Apex quando precisam de geração programática de PDF fora do contexto de Fluxo." O sourceObjectApiName deve ser um dos objetos de origem suportados:

Nome da API do objetoDocumentoObservação
refin__Sales_Invoice__cFatura de vendaNotas de crédito também passam por aqui — uma Nota de crédito é um tipo de Fatura de venda (refin__Sales_Invoice_Type__c), então você passa o nome do objeto de fatura
refin__Cash_Receipt__cRecebimento—
refin__Cash_Disbursement__cDesembolso—
refin__Purchase_Order__cOrdem de compra—

O PDF gerado é anexado ao registro de origem como um ContentVersion com um ContentDocumentLink — um File normal do Salesforce que você pode consultar, compartilhar ou enviar por e-mail. O publicUrl do resultado só é preenchido "se a Entidade tiver a URL Pública Automática habilitada; caso contrário, é nulo" — portanto, o código que envia links por e-mail aos clientes deve verificar se é nulo em vez de presumir que existe uma URL. E você pode nem precisar de código: cada entidade traz alternadores de geração automática por tipo de documento (refin__Auto_Gen_PDF_Sales_Invoice__c, refin__Auto_Gen_PDF_Cash_Receipt__c, refin__Auto_Gen_PDF_Cash_Disbursement__c, refin__Auto_Gen_PDF_Purchase_Order__c, refin__Auto_Gen_PDF_Credit_Memo__c) — o generateOne é para os momentos fora desses casos, como regenerar sob demanda ou anexar a um envio personalizado.

O acesso à classe não é pré-concedido. Nenhum dos cinco conjuntos de permissões empacotados lista as classes globais (PDFGeneratorAPI, GlobalTaxService, OCREmailHandler) em seu acesso a classes Apex. Se quem chama a classe precisa de acesso explícito depende de como e onde o código é executado — verifique em um sandbox com um usuário realista antes de lançar, e conceda acesso no seu próprio conjunto de permissões se as chamadas forem bloqueadas.

Contabilizando a partir do Apex — o campo de status é a API

Não existe um método post() para chamar, por design: o motor de contabilização é executado na fase de trigger da própria atualização do objeto de origem. Seu Apex contabiliza um documento da mesma forma que um usuário faz — mude refin__Status__c de 'Draft' para 'Posted' com uma atualização de DML, e toda a maquinaria é executada: os motores de imposto e retenção na fonte, a gravação da taxa de câmbio na transição para Posted, e todas as proteções (portão de aprovação, validação de período, bloqueios de entidade/conta contábil inativa). Uma contabilização recusada aparece como uma exceção de DML carregando a mensagem exata da proteção — trate isso como o contrato da API, não como ruído.

O padrão confiável de criação é em duas etapas: inserir como Draft (cabeçalho e linhas) e, em seguida, atualizar o status para 'Posted' em uma segunda chamada. Na inserção, um status nulo assume 'Draft' por padrão, os números de documento são atribuídos automaticamente a partir da sequência de documentos da entidade (predefinir o Name significa numeração manual, e a exclusividade é validada), e os padrões da entidade são preenchidos automaticamente — livro razão, período contábil e data de vencimento a partir da condição de pagamento do fornecedor. Observe que alterar a entidade ou o tipo de fatura em um Draft reordena sua numeração, então faça suas edições antes de contabilizar.

Não existe um atalho de integração. Todos os 52 triggers empacotados — um por objeto transacional — são executados no DML da API exatamente como na interface, e não existe nenhuma flag que você possa usar para desativá-los. A classe base TriggerHandler tem, sim, um bypass interno usado pelos caminhos de migração do próprio motor, mas ele é public, não global: invisível ao cruzar o namespace, então o código do assinante e das extensões não consegue alcançá-lo. Os registros contabilizados ficam bloqueados, exceto para campos de fluxo de trabalho na lista de permissões, como refin__Approval_Status__c, e o Cancelamento é imutável. Nunca escreva linhas de refin__Accounting_Transaction__c você mesmo; as proteções existem precisamente para impedir lançamentos contábeis forjados manualmente.

Reagindo a eventos contábeis

Seus triggers e Fluxos disparados por registro nos objetos refin__ coexistem tranquilamente com os do próprio pacote (a ordem entre pacotes e a automação da organização segue as regras padrão da plataforma — mantenha os seus idempotentes). O padrão de maior valor: reagir a transições de status — um trigger na Fatura de venda que percebe Draft→Posted pode alimentar seu sistema downstream com o documento já totalmente validado e numerado. Reaja depois do trabalho do pacote: leia o estado final do registro em vez de tentar se intercalar.

As regras que mantêm você seguro em atualizações

FaçaNão faça
Chame as classes globais documentadasUse reflexão em classes public do pacote — elas são invisíveis entre namespaces e mudam sem aviso
Contabilize atualizando refin__Status__c (os motores rodam por você)Escreva linhas de refin__Accounting_Transaction__c você mesmo — as proteções existem precisamente para impedir lançamentos contábeis forjados manualmente
Respeite os erros de DML como o contrato da API (as mensagens literais das proteções)Engula falhas de contabilização — elas são verdades contábeis, não ruído
Estenda dados via conjuntos de campos e seus próprios camposDependa do texto de ajuda de campos do pacote ou de detalhes internos da interface
Exemplo prático: uma calculadora de locação. O LWC personalizado "cotação de locação" da Meridian chama GlobalTaxService.calculateTax com valores de linha em rascunho para mostrar a um locatário o aluguel com imposto incluso antes de qualquer fatura existir — mesmas alíquotas, mesmas regras, zero registros criados. Quando o contrato é assinado, a integração cria o modelo recorrente real e os números batem até o centavo, porque é um único motor.

Erros comuns a evitar

ErroPor que prejudicaFaça isto em vez disso
Persistir os resultados do GlobalTaxService como seus próprios registros de impostoO contrato é explícito: nenhum DML é executado e "quem chama a classe é responsável pela persistência e pela contabilização" — mas linhas de imposto construídas manualmente se desviam do que a contabilização vai calcularUse a resposta para exibição e lógica de decisão; deixe a contabilização do documento escrever os registros reais de imposto
Envolver o DML de atualização de status em um try/catch genérico que registra o erro e continuaUma contabilização recusada é uma resposta contábil; engoli-la deixa documentos silenciosamente não contabilizadosCapture, exponha a mensagem literal a uma pessoa ou a uma fila, e trate o não contabilizado como um item de trabalho
Chamar generateOne para objetos que ele não suportaO conjunto suportado é exatamente SI, CR, CD, PO (notas de crédito usam o tipo SI) — qualquer outro objeto falhaVerifique a tabela de objetos suportados acima antes de conectar um botão a ele
Reagir a Draft→Posted antes de o pacote terminarSua lógica lê um estado parcialmente contabilizado — números ou linhas de livro razão ainda não finaisReaja em after-update sobre o estado final, ou de forma assíncrona (padrões queueable/platform-cache) depois que a transação for confirmada
Testar contra um sandbox sem licença ativaAs gravações contábeis falham com a mensagem de licença, e a equipe passa a depurar "bugs" fantasmasAtive a licença em todo sandbox e configure o usuário de execução antes de testar código de integração

O que vem a seguir

  1. Pacotes de extensão — os contratos de provedor para ISVs.
  2. Lightning Web Components — a contraparte de interface.
  3. Visão geral da API — o mapa.
Documentação oficial do Salesforce O Apex é a linguagem da plataforma Salesforce — tudo sobre sua sintaxe, limites e testes está no Apex Developer Guide. Os links abrem em uma nova aba.

Perguntas frequentes

Sim — atualize o status na sua transação; o motor de contabilização é executado na mesma fase de trigger da transação. Faça o orçamento de CPU de acordo (os motores de impostos não são gratuitos) e trate o caminho de exceção de DML: uma contabilização recusada sofre rollback com a mensagem da proteção (guard).

As alavancas suportadas são de configuração: contas padrão da entidade, mapeamentos de impostos para o livro razão, contas contábeis por tipo de ativo, estruturas de relatórios. Deliberadamente não existe um hook de código dentro da contabilização — o determinismo é o produto. A lógica personalizada de contas contábeis deve ficar antes da contabilização (escolhendo as contas nas linhas) ou depois (lançamentos contábeis).

Leia errorMessage — é a mesma linguagem de validação usada pelos documentos (entidade ausente, lado inválido, sem linhas). Quem chama a classe decide se isso vira uma mensagem para o usuário ou uma correção de dados.

Os métodos de cálculo do GlobalTaxService têm o compromisso contratual de não lançar: os resultados "sempre são retornados, nunca lançam exceção para quem chamou" — os erros trafegam em success=false mais errorMessage. Existe uma classe GlobalTaxServiceException na superfície global, mas a sua codificação defensiva deve se basear na flag de resposta, não em blocos catch.

PDFGeneratorAPI e EInvoiceTrailCreator são concedidos pelos dez conjuntos de permissões empacotados, porque as ações de Fluxo empacotadas os chamam. GlobalTaxService, BankFeedProvider, OCREmailHandler e os tipos de contrato de nota fiscal eletrônica não são concedidos por nenhum — conceda você mesmo o acesso à classe Apex ao usuário que faz a chamada. Verifique na sua organização; a exigência varia conforme a forma como o código é invocado.

Opere-os pela guia Operações do Console de Administração — essa é a superfície de controle suportada, com estado da programação, resultados e logs de auditoria em um só lugar. As classes de lote em si não fazem parte do contrato global.

Isso foi útil?