REDA One Centre d’aide
FR
Langue
English EN Español ES Português PT Italiano bientôt disponible Français FR Deutsch bientôt disponible العربية bientôt disponible
reda.one ↗
Accueil / REDA Financials AI / API et intégration / Développement personnalisé / Développement Apex sur REDA

Développement Apex sur REDA

Le code Apex personnalisé autour de REDA Financials obéit à une règle d’or : s’appuyer sur la surface globale et les objets, jamais sur les éléments internes. Voici la boîte à outils stable et les modèles qui en tirent le meilleur parti.

La surface globale — treize classes et interfaces

Dans un package géré, seul le code Apex global est visible par votre code ; tout ce qui est public reste derrière la barrière de l’espace de noms. REDA Financials maintient délibérément cette surface prise en charge réduite — treize types globaux pour le code des clients et des partenaires. Cinq d’entre eux portent les points d’entrée du quotidien ; les autres sont les contrats d’extension de facturation électronique et les types de données qu’ils échangent :

ClasseTypeÀ quoi elle sert
refin.GlobalTaxServiceglobal with sharingFaçade sur les moteurs de taxe et de retenue à la source ; deux méthodes de calcul statiques, aucun DML
refin.PDFGeneratorAPIglobal with sharingGénération de PDF — une action invocable de flux et une méthode Apex globale
refin.BankFeedProviderglobal abstractContrat d’extension pour les fournisseurs de flux bancaire (Plaid, Belvo, …) — vous l’implémentez, le package l’appelle
refin.EInvoiceProviderglobal abstractContrat d’extension pour les fournisseurs de facturation électronique (Sovos, Peppol, ZATCA, SAT, SDI, …)
refin.OCREmailHandlerglobal, implémente Messaging.InboundEmailHandlerGestionnaire d’e-mails entrants qui transforme les pièces jointes PDF en enregistrements de boîte de réception OCR pour l’automatisation des comptes fournisseurs
refin.EInvoiceStatusPollSchedulableglobal with sharing, implémente SchedulableLa surface de planification exposée aux abonnés pour l’interrogation du statut des factures électroniques — planifiez-la vous-même avec votre propre expression cron au lieu d’utiliser la carte de la console d’administration
refin.EInvoiceTrailCreatorglobal with sharingCrée un historique de facturation électronique pour les factures de vente comptabilisées — également exposée comme action de flux Create E-Invoice Trail
refin.EInvoiceQueueProviderinterface globaleContrat de file d’attente pour les extensions de facturation électronique. Ajouté à côté de EInvoiceProvider plutôt que sous forme de méthodes de celle-ci, car une classe abstraite globale déjà publiée ne peut pas recevoir de nouvelles méthodes sans casser les implémentations existantes
refin.EInvoiceDocumentProviderinterface globaleContrat de récupération de documents pour les extensions de facturation électronique
refin.EInvoiceProvisionerinterface globaleContrat de provisionnement pour les extensions de facturation électronique
refin.EInvoiceDocument · refin.EInvoiceEvent · refin.EInvoiceProvisionResultclasses globales (DTO)Les types de transfert de données que les contrats de fournisseur ci-dessus transmettent et renvoient

Les contrats de fournisseur abstraits sont présentés dans Packages d’extension ; cet article se concentre sur les deux classes que vous appelez. Toutes les classes de l’API sont livrées en version d’API 65.0, et la garantie standard des packages gérés s’applique : une fois publiées, les signatures globales ne peuvent être ni supprimées ni modifiées à votre insu — elles constituent le contrat stable.

Ce que vous ne pouvez pas appeler

La couche de services interne qui fait le vrai travail — comptabilisation, import des relevés bancaires, rendu des PDF, appels sortants d’IA et de taux de change, mises à jour de statut en masse, initialisation des taxes — est public, et non globale. Le code Apex de l’abonné ne peut pas du tout appeler ces classes : le compilateur ne les voit tout simplement pas au-delà de la frontière de l’espace de noms, et leur structure interne peut changer à chaque version. Si une capacité dont vous avez besoin n’existe que derrière une classe publique, la voie prise en charge est le comportement qu’elle alimente — la comptabilisation via le champ Statut, la génération de PDF via refin.PDFGeneratorAPI, l’initialisation des taxes via l’assistant d’initialisation des taxes fourni avec le package — et non la classe elle-même.

L’espace de noms compte. Qualifiez toujours vos appels avec l’espace de noms refin — refin.GlobalTaxService.calculateTax(...), refin.PDFGeneratorAPI.generateOne(...). C’est sous ce nom que votre organisation voit les classes du package géré.

refin.GlobalTaxService — le calcul des taxes en tant que service

Deux méthodes statiques donnent à votre code accès aux mêmes moteurs que ceux qu’utilisent les documents, sans aucun effet de bord :

  • calculateTax(TaxRequest) — taxe indirecte (TVA/GST/taxe sur les ventes). Fournissez-lui l’entité, le compte, la date, le côté ('Sales'/'Purchase') et les lignes (montant + produit ou profil fiscal explicite, avec votre propre lineKey pour la corrélation) ; vous récupérez, pour chaque ligne, des lignes de taxe entièrement résolues — taux, montant, indicateur de récupérabilité, et le compte général sur lequel chaque taxe serait comptabilisée.
  • calculateWithholdingTax(WHTRequest) — le moteur de retenue à la source, avec la même structure : des lignes en entrée, la retenue de chaque ligne en sortie (taux, base, montant, motifs d’exonération, compte général).
  • Le contrat : aucun DML, aucun enregistrement créé, aucune exception levée — les erreurs reviennent sous la forme success=false avec un message. La persistance vous appartient entièrement : idéal pour les devis, les calculateurs, les estimations avant comptabilisation ou les contrôles de concordance avec des systèmes externes.

La référence complète

Les deux points d’entrée sont statiques :

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

La documentation de la classe énonce clairement le contrat : « Aucun DML n’est effectué. Aucun enregistrement Source_Line_Tax ou WHT_Transaction n’est créé. L’appelant est responsable de la persistance et de la comptabilisation. » Une réponse est « toujours renvoyée, sans jamais lever d’exception vers l’appelant » — les problèmes de validation (entité, compte, date, côté ou lignes manquants, ou valeur de côté non valide) reviennent sous la forme success=false, avec le motif dans errorMessage, dans le même langage de validation que celui des documents eux-mêmes. Une classe GlobalTaxServiceException existe dans la surface globale, mais dans le cadre du contrat normal, vous lisez la réponse plutôt que d’intercepter une exception.

Les DTO de requête et de réponse sont des classes globales dotées de membres globaux — vous pouvez définir ou lire tout ce qui suit :

DTOMembreContenu
TaxRequestentityIdL’entité dont la configuration fiscale s’applique
accountIdLe compte client ou fournisseur de la transaction
transactionDateLa date à laquelle les taux et les règles sont résolus
side'Sales' ou 'Purchase'
fiscalOperationId / linesRemplacement facultatif de l’opération fiscale ; la liste des lignes TaxLineRequest
TaxLineRequestlineKeyVotre clé de corrélation — renvoyée telle quelle sur la ligne de résultat correspondante
amountLe montant de ligne sur lequel porte le calcul
productId / taxProfileIdLe produit détermine la recherche du profil fiscal ; un taxProfileId explicite prend le dessus et court-circuite cette recherche
taxBase'Exclusive of Tax' (par défaut), 'Inclusive of Tax' ou 'Tax Exempt'
TaxResponsesuccess / errorMessageLe contrat de résultat — vérifiez success avant de lire les résultats
nexusEstablished / totalTaxAmount / totalEmbeddedTaxAmount / linesLes résultats au niveau de l’en-tête, plus le détail par ligne
TaxResultLinelineKey / taxTypeId / taxRuleIdQuelle ligne, quelle taxe, quelle règle s’est appliquée
taxBase / taxRate / taxAmount / isTaxIncludedInPrice / isRecoverable / sequenceLe calcul résolu, exactement tel que la comptabilisation l’effectuerait
glAccountId / deductionGlAccountIdLe compte général sur lequel la taxe serait comptabilisée ; le compte général de déduction n’est renseigné que pour les taxes incluses (por-dentro)

L’ensemble dédié à la retenue à la source en est le reflet :

DTOMembreContenu
WHTRequestentityId / accountId / transactionDate / side / documentCurrencyCode / linesMême structure d’en-tête que pour la taxe ; documentCurrencyCode est facultatif et sert à la conversion des seuils en multidevise
WHTLineRequestlineKey / lineSubtotal / lineTaxAmount / ruleOverrideIdlineTaxAmount alimente le mode Base taxe comprise ; ruleOverrideId l’emporte sur la règle par défaut du compte
WHTResponsesuccess / errorMessage / totalWHTAmount / linestotalWHTAmount ne totalise que les lignes non exonérées
WHTResultLinelineKey / ruleId / rateId / rate / base / amount / isExempt / exemptReason / glAccountIdLa retenue de chaque ligne, y compris pourquoi une ligne est exonérée

Comment les comptes généraux sont résolus. Les comptes généraux de la taxe indirecte proviennent des enregistrements de correspondance comptable par type de taxe (refin__Tax_Type_GL_Mapping__c) : une correspondance propre à l’entité l’emporte sur une correspondance valable pour toute l’organisation, la recherche se faisant par type de taxe et par récupérabilité pour le côté de la requête ; les taxes incluses reçoivent en outre un compte général de déduction issu du côté « Déduction sur ventes » / « Purchase Deduction » de la correspondance. Les comptes généraux de retenue proviennent du champ Compte général du taux de retenue à la source correspondant, avec repli sur le compte Retenue à payer par défaut de l’entité. Autrement dit, la réponse vous indique non seulement combien, mais aussi où le montant serait comptabilisé — la même résolution que celle qu’effectue la comptabilisation.

Un appel minimal, à la manière de Meridian — l’entité américaine chiffre la ligne de loyer d’un locataire avant qu’aucune facture n’existe :

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);
}

Le pendant pour la retenue à la source suit le même rythme. L’entité indienne de Meridian estime la retenue TDS sur la facture d’un prestataire avant sa saisie : construisez un WHTRequest avec side = 'Purchase', un WHTLineRequest par ligne de facture portant lineSubtotal, appelez calculateWithholdingTax, puis lisez chaque WHTResultLine — rate, base, amount et isExempt avec son exemptReason lorsqu’un certificat ou un seuil exempte la ligne. Comme rien n’est persisté, l’équipe des comptes fournisseurs peut annoncer au fournisseur le montant net à payer, et les chiffres correspondront au paisa près au document comptabilisé.

refin.PDFGeneratorAPI — des documents depuis le code

generateOne(sourceRecordId, sourceObjectApiName) génère et joint le PDF d’un document comptabilisé — facture de vente, avoir, encaissement, décaissement ou bon de commande — et renvoie les ID de contenu ainsi que, lorsque le territoire active les liens publics, l’URL. Le pendant côté flux est présenté dans Intégration avec les flux Salesforce.

La signature :

global static PDFResult generateOne(Id sourceRecordId, String sourceObjectApiName)

Selon les termes mêmes de la classe, « les packages d’extension l’appellent depuis Apex lorsqu’ils ont besoin d’une génération de PDF programmatique en dehors d’un contexte de flux ». Le paramètre sourceObjectApiName doit désigner l’un des objets source pris en charge :

Nom d’API de l’objetDocumentRemarque
refin__Sales_Invoice__cFacture de venteLes avoirs passent aussi par ici — un avoir est un type de facture de vente (refin__Sales_Invoice_Type__c), vous passez donc le nom de l’objet facture
refin__Cash_Receipt__cEncaissement—
refin__Cash_Disbursement__cDécaissement—
refin__Purchase_Order__cBon de commande—

Le PDF produit est joint à l’enregistrement source sous forme de ContentVersion avec un ContentDocumentLink — un fichier Salesforce ordinaire que vous pouvez interroger, partager ou envoyer par e-mail. Le publicUrl du résultat n’est renseigné que « si l’entité a l’option Auto Public URL activée ; null sinon » — le code qui envoie des liens aux clients par e-mail doit donc tester la valeur null plutôt que de supposer qu’une URL existe. Et vous n’aurez peut-être même pas besoin de code : chaque entité dispose d’options de génération automatique par type de document (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) — generateOne sert aux cas qui sortent de ce cadre, comme une régénération à la demande ou l’ajout du PDF à un envoi personnalisé.

L’accès aux classes n’est pas accordé d’avance. Aucun des cinq ensembles d’autorisations du package ne répertorie les classes globales (PDFGeneratorAPI, GlobalTaxService, OCREmailHandler) dans son accès aux classes Apex. Le besoin d’un accès explicite aux classes pour vos appelants dépend de la manière dont le code s’exécute et de l’endroit où il s’exécute — vérifiez-le dans une sandbox avec un utilisateur réaliste avant la mise en production, et accordez l’accès dans votre propre ensemble d’autorisations si les appels sont bloqués.

Comptabiliser depuis Apex — le champ Statut est l’API

Il n’existe pas de méthode post() à appeler, et c’est voulu : le moteur de comptabilisation s’exécute dans la phase des déclencheurs de la mise à jour de l’objet source lui-même. Votre code Apex comptabilise un document de la même manière qu’un utilisateur — faites passer refin__Status__c de « Brouillon » à « Comptabilisé » par une mise à jour DML, et toute la mécanique s’exécute : moteurs de taxe et de retenue à la source, inscription du taux de change lors du passage au statut Comptabilisé, et chaque garde-fou (contrôle d’approbation, validation de la période, blocages sur une entité ou un compte général inactif). Une comptabilisation refusée se manifeste par une exception DML portant le message exact du garde-fou — traitez-la comme le contrat de l’API, pas comme du bruit.

Le modèle de création fiable se déroule en deux temps : insérer en Brouillon (en-tête et lignes), puis passer le statut à « Comptabilisé » dans un second appel. À l’insertion, un statut nul prend par défaut la valeur « Brouillon », les numéros de document sont attribués automatiquement à partir de la séquence de documents de l’entité (prédéfinir le champ Name revient à une numérotation manuelle, dont l’unicité est validée), et les valeurs par défaut de l’entité sont renseignées automatiquement — le Grand livre, la Période comptable et la Date d’échéance d’après la Condition de paiement du fournisseur. Notez que modifier l’Entité ou le Type de facture d’un document en Brouillon le renumérote : effectuez donc vos modifications avant de comptabiliser.

Il n’existe aucun contournement pour les intégrations. Les 52 déclencheurs du package — un par objet transactionnel — s’exécutent sur le DML de l’API exactement comme dans l’interface utilisateur, et aucun indicateur ne vous permet de les désactiver. La classe de base TriggerHandler comporte bien un contournement interne utilisé par les propres chemins de migration du moteur, mais il est public, et non global — invisible au-delà de la frontière de l’espace de noms, si bien que le code des abonnés et des extensions ne peut pas l’atteindre. Les enregistrements comptabilisés sont verrouillés, à l’exception de champs de workflow autorisés tels que refin__Approval_Status__c, et le statut Annulé est immuable. N’écrivez jamais vous-même de lignes refin__Accounting_Transaction__c ; les garde-fous existent précisément pour empêcher les écritures de grand livre forgées à la main.

Réagir aux événements comptables

Vos déclencheurs et vos flux déclenchés par un enregistrement sur les objets refin__ cohabitent sans problème avec ceux du package (l’ordre d’exécution entre les packages et les automatisations de l’organisation suit les règles standard de la plateforme — gardez les vôtres idempotents). Le modèle le plus rentable : réagir aux transitions de statut — un déclencheur sur la facture de vente qui détecte le passage Brouillon→Comptabilisé peut alimenter votre système en aval avec un document déjà entièrement validé et numéroté. Réagissez après le travail du package : lisez l’état final de l’enregistrement plutôt que d’essayer de vous interposer.

Les règles qui préservent la compatibilité avec les mises à niveau

À faireÀ éviter
Appeler les classes globales documentéesAccéder par réflexion aux classes public du package — elles sont invisibles d’un espace de noms à l’autre et changent sans préavis
Comptabiliser en mettant à jour refin__Status__c (les moteurs s’exécutent pour vous)Écrire vous-même des lignes refin__Accounting_Transaction__c — les garde-fous existent précisément pour empêcher les écritures de grand livre forgées à la main
Respecter les erreurs DML comme contrat de l’API (les messages exacts des garde-fous)Étouffer les échecs de comptabilisation — ce sont des vérités comptables, pas du bruit
Étendre les données via les ensembles de champs et vos propres champsDépendre du texte d’aide des champs du package ou des mécanismes internes de l’interface utilisateur
Exemple concret : un calculateur de location. Le LWC personnalisé de « devis de bail » de Meridian appelle GlobalTaxService.calculateTax avec des montants de lignes provisoires pour montrer à un locataire le loyer toutes taxes comprises avant qu’aucune facture n’existe — mêmes taux, mêmes règles, zéro enregistrement créé. À la signature du bail, l’intégration crée le véritable modèle récurrent et les chiffres correspondent au centime près, car il s’agit d’un seul et même moteur.

Erreurs courantes à éviter

ErreurPourquoi c’est un problèmeQue faire à la place
Persister les résultats de GlobalTaxService sous forme de vos propres enregistrements de taxeLe contrat est explicite : aucun DML n’est effectué et « L’appelant est responsable de la persistance et de la comptabilisation » — mais des lignes de taxe construites à la main s’écartent de ce que la comptabilisation calculeraUtilisez la réponse pour l’affichage et la logique de décision ; laissez la comptabilisation du document écrire les véritables enregistrements de taxe
Envelopper le DML de mise à jour du statut dans un try/catch fourre-tout qui journalise et continueUne comptabilisation refusée est une réponse comptable ; l’étouffer laisse des documents non comptabilisés sans que personne ne s’en aperçoiveInterceptez l’exception, transmettez le message exact à une personne ou à une file d’attente, et traitez chaque document non comptabilisé comme une tâche à traiter
Appeler generateOne pour des objets qu’elle ne prend pas en chargeL’ensemble pris en charge se limite exactement aux factures de vente, encaissements, décaissements et bons de commande (les avoirs passent par le type de facture de vente) — tout le reste échoueConsultez le tableau des objets pris en charge ci-dessus avant d’y associer un bouton
Réagir au passage Brouillon→Comptabilisé avant que le package ait terminéVotre logique lit un état à moitié comptabilisé — des numéros ou des lignes de grand livre pas encore définitifsRéagissez en after-update sur l’état final, ou de manière asynchrone (modèles queueable/cache de plateforme) une fois la transaction validée
Tester dans une sandbox sans licence activeLes opérations d’écriture sur les données comptables échouent avec le message de licence, et l’équipe débogue des « bugs » fantômesActivez la licence dans chaque sandbox et attribuez une licence à l’utilisateur qui exécute le code avant de tester le code d’intégration

Pour aller plus loin

  1. Packages d’extension — les contrats de fournisseur destinés aux ISV.
  2. Lightning Web Components — le pendant côté interface utilisateur.
  3. Présentation de l’API — la vue d’ensemble.
Documentation officielle de Salesforce Apex est le langage de la plateforme Salesforce — tout ce qui concerne sa syntaxe, ses limites et ses tests se trouve dans le Guide du développeur Apex. Les liens s’ouvrent dans un nouvel onglet.

Questions fréquentes

Oui — mettez à jour le statut dans votre transaction ; le moteur de comptabilisation s’exécute dans la phase des déclencheurs de cette même transaction. Prévoyez votre budget CPU en conséquence (les moteurs de taxe ne sont pas gratuits) et gérez le chemin d’exception DML : une comptabilisation refusée provoque la restauration de la transaction et renvoie le message du garde-fou.

Les leviers pris en charge relèvent de la configuration : comptes par défaut de l’entité, correspondances comptables des taxes, comptes généraux des types d’immobilisations, référentiels de reporting. Il n’existe délibérément aucun point d’extension de code à l’intérieur de la comptabilisation — le déterminisme est le produit. Une logique de comptes généraux personnalisée intervient avant la comptabilisation (choisissez les comptes sur les lignes) ou après (écritures comptables).

Lisez errorMessage — il emploie le même langage de validation que les documents (entité manquante, côté non valide, aucune ligne). C’est votre code appelant qui décide s’il s’agit d’un message destiné à l’utilisateur ou d’une correction de données.

Les méthodes de calcul de GlobalTaxService s’engagent contractuellement à ne pas le faire : leurs résultats sont « toujours renvoyés, sans jamais lever d’exception vers l’appelant » — les erreurs transitent par success=false accompagné de errorMessage. Une classe GlobalTaxServiceException existe dans la surface globale, mais votre programmation défensive repose sur l’indicateur de la réponse, pas sur des blocs catch.

PDFGeneratorAPI et EInvoiceTrailCreator sont accordées par les dix ensembles d’autorisations du package, car des actions de flux du package les appellent. GlobalTaxService, BankFeedProvider, OCREmailHandler et les types de contrat de facturation électronique ne sont accordés par aucun — accordez vous-même l’accès aux classes Apex à l’utilisateur appelant. Vérifiez dans votre organisation ; l’exigence varie selon la manière dont le code est appelé.

Pilotez-les plutôt depuis l’onglet Opérations de la console d’administration — c’est la surface de contrôle prise en charge, qui réunit l’état de planification, les résultats et les journaux d’audit en un seul endroit. Les classes de lot elles-mêmes ne font pas partie du contrat global.

Cet article vous a-t-il été utile ?