Ajouter des champs de données dans l’intégration fiscale à l’aide d’une extension
Cet article explique comment utiliser les extensions X++ pour ajouter des champs de données à l’intégration fiscale. Ces champs peuvent être étendus au modèle de données fiscales du service de taxe et être utilisés pour déterminer les codes taxe. Pour plus d’informations, consultez Ajouter des champs de données dans les configurations de taxe.
Modèle de données
Les données du modèle de données sont portées par des objets et implémentées par des classes.
Voici une liste des principaux objets :
- AxClass/TaxIntegrationDocumentObject
- AxClass/TaxIntegrationLineObject
- AxClass/TaxIntegrationTaxLineObject
L’illustration suivante montre les relations entre ces objets.
]
Un objet Document peut contenir plusieurs objets Ligne. Chaque objet contient des métadonnées pour le service de taxe.
-
TaxIntegrationDocumentObject
comporte les métadonnéesoriginAddress
, qui contiennent des informations sur l’adresse source, et les métadonnéesincludingTax
, qui indiquent si le montant de la ligne comprend la taxe de vente. -
TaxIntegrationLineObject
comporte les métadonnéesitemId
,quantity
etcategoryId
.
Note
TaxIntegrationLineObject
implémente également les objets Frais.
Flux d’intégration
Les données du flux sont manipulées par des activités.
Principales activités
- AxClass/TaxIntegrationCalculationActivityOnDocument
- AxClass/TaxIntegrationCurrencyExchangeActivityOnDocument
- AxClass/TaxIntegrationDataPersistenceActivityOnDocument
- AxClass/TaxIntegrationDataRetrievalActivityOnDocument
- AxClass/TaxIntegrationSettingRetrievalActivityOnDocument
Les activités sont exécutées dans l’ordre suivant :
- Récupération des paramètres
- Récupération des données
- Service de calcul
- Change de la devise
- Persistance des données
Par exemple, étendez Récupération des données avant Service de calcul.
Activités de récupération de données
Les activités de Récupération de données récupèrent les données de la base de données. Il existe des adaptateurs pour différentes transactions permettant de récupérer des données à partir de différentes tables de transaction :
- AxClass/TaxIntegrationPurchTableDataRetrieval
- AxClass/TaxIntegrationPurchParmTableDataRetrieval
- AxClass/TaxIntegrationPurchREQTableDataRetrieval
- AxClass/TaxIntegrationPurchRFQTableDataRetrieval
- AxClass/TaxIntegrationVendInvoiceInfoTableDataRetrieval
- AxClass/TaxIntegrationSalesTableDataRetrieval
- AxClass/TaxIntegrationSalesParmDataRetrieval
Dans ces activités de Récupération de données, les données sont copiées de la base de données vers TaxIntegrationDocumentObject
et TaxIntegrationLineObject
. Étant donné que toutes ces activités étendent la même classe de modèle abstrait, elles ont des méthodes communes.
Activités du service de calcul
L’activité Service de calcul est le lien entre le service de taxe et l’intégration fiscale. Cette activité est responsable des fonctions suivantes :
- Construire la requête.
- Valider la requête auprès du service de taxe.
- Obtenir la réponse de la part du service de taxe.
- Analyser la réponse.
Un champ de données que vous ajoutez à la requête sera validé avec d’autres métadonnées.
Implémentation de l’extension
Cette section fournit des étapes détaillées qui expliquent comment implémenter l’extension. Elle prend comme exemples les dimensions financières Centre de coûts et Projet.
Étape 1. Ajouter la variable de données dans la classe d’objets
La classe d’objets contient la variable de données et les méthodes getter/setter pour les données. Ajoutez le champ de données à TaxIntegrationDocumentObject
ou à TaxIntegrationLineObject
, en fonction du niveau du champ. L’exemple suivant utilise le niveau de ligne et le nom de fichier est TaxIntegrationLineObject_Extension.xpp
.
Note
Si le champ de données que vous ajoutez se trouve au niveau document, remplacez le nom du fichier par TaxIntegrationDocumentObject_Extension.xpp
.
[ExtensionOf(classStr(TaxIntegrationLineObject))]
final class TaxIntegrationLineObject_Extension
{
private OMOperatingUnitNumber costCenter;
private ProjId projectId;
/// <summary>
/// Gets a costCenter.
/// </summary>
/// <returns>The cost center.</returns>
public final OMOperatingUnitNumber getCostCenter()
{
return this.costCenter;
}
/// <summary>
/// Sets the cost center.
/// </summary>
/// <param name = "_value">The cost center.</param>
public final void setCostCenter(OMOperatingUnitNumber _value)
{
this.costCenter = _value;
}
/// <summary>
/// Gets a project ID.
/// </summary>
/// <returns>The project ID.</returns>
public final ProjId getProjectId()
{
return this.projectId;
}
/// <summary>
/// Sets the project ID.
/// </summary>
/// <param name = "_value">The project ID.</param>
public final void setProjectId(ProjId _value)
{
this.projectId = _value;
}
}
Centre de coûts et Projet sont ajoutés en tant que variables privées. Créez des méthodes getter et setter pour ces champs de données afin de manipuler les données.
Étape 2. Récupérer les données de la base de données
Spécifiez la transaction et étendez les classes d’adaptateur appropriées pour récupérer les données. Par exemple, si vous utilisez une transaction Commande fournisseur, vous devez étendre TaxIntegrationPurchTableDataRetrieval
et TaxIntegrationVendInvoiceInfoTableDataRetrieval
.
Note
TaxIntegrationPurchParmTableDataRetrieval
est hérité de TaxIntegrationPurchTableDataRetrieval
. Il ne doit pas être modifié à moins que la logique des tables purchTable
et purchParmTable
ne soit différente.
Si le champ de données doit être ajouté pour toutes les transactions, étendez toutes les classes DataRetrieval
.
Comme toutes les activités Récupération de données étendent la même classe de modèle, les structures de classe, les variables et les méthodes sont similaires.
protected TaxIntegrationDocumentObject document;
/// <summary>
/// Copies to the document.
/// </summary>
protected abstract void copyToDocument()
{
// It is recommended to implement as:
//
// this.copyToDocumentByDefault();
// this.copyToDocumentFromHeaderTable();
// this.copyAddressToDocument();
}
/// <summary>
/// Copies to the current line of the document.
/// </summary>
/// <param name = "_line">The current line of the document.</param>
protected abstract void copyToLine(TaxIntegrationLineObject _line)
{
// It is recommended to implement as:
//
// this.copyToLineByDefault(_line);
// this.copyToLineFromLineTable(_line);
// this.copyQuantityAndTransactionAmountToLine(_line);
// this.copyAddressToLine(_line);
// this.copyToLineFromHeaderTable(_line);
}
L’exemple suivant montre la structure de base quand la table PurchTable
est utilisée.
public class TaxIntegrationPurchTableDataRetrieval extends TaxIntegrationAbstractDataRetrievalTemplate
{
protected PurchTable purchTable;
protected PurchLine purchLine;
// Query builder methods
[Replaceable]
protected SysDaQueryObject getDocumentQueryObject()
[Replaceable]
protected SysDaQueryObject getLineQueryObject()
[Replaceable]
protected SysDaQueryObject getDocumentChargeQueryObject()
[Replaceable]
protected SysDaQueryObject getLineChargeQueryObject()
// Data retrieval methods
protected void copyToDocument()
protected void copyToDocumentFromHeaderTable()
protected void copyToLine(TaxIntegrationLineObject _line)
protected void copyToLineFromLineTable(TaxIntegrationLineObject _line)
...
}
Quand la méthode CopyToDocument
est appelée, le tampon this.purchTable
existe déjà. Le but de cette méthode est de copier toutes les données requises à partir de la table this.purchTable
vers l’objet document
en utilisant la méthode setter qui a été créée dans la classe DocumentObject
.
De même, un tampon this.purchLine
existe déjà dans la méthode CopyToLine
. Le but de cette méthode est de copier toutes les données requises à partir de la table this.purchLine
vers l’objet _line
en utilisant la méthode setter qui a été créée dans la classe LineObject
.
L’approche la plus simple consiste à étendre les méthodes CopyToDocument
et CopyToLine
. Cependant, nous vous recommandons d’essayer d’abord les méthodes copyToDocumentFromHeaderTable
et copyToLineFromLineTable
. Si elles ne fonctionnent pas comme vous le souhaitez, implémentez votre propre méthode et appelez-la dans CopyToDocument
et CopyToLine
. Il existe trois situations courantes dans lesquelles vous pouvez utiliser cette approche :
Le champ obligatoire se trouve dans
PurchTable
ouPurchLine
. Dans cette situation, vous pouvez étendrecopyToDocumentFromHeaderTable
etcopyToLineFromLineTable
. Voici l’exemple de code./// <summary> /// Copies to the current line of the document from. /// </summary> /// <param name = "_line">The current line of the document.</param> protected void copyToLineFromLineTable(TaxIntegrationLineObject _line) { next copyToLineFromLineTable(_line); // if we already added XXX in TaxIntegrationLineObject _line.setXXX(this.purchLine.XXX); }
Les données requises ne figurent pas dans la table par défaut de la transaction. Cependant, il existe des relations de jointure avec la table par défaut et le champ est requis sur la plupart des lignes. Dans cette situation, remplacez
getDocumentQueryObject
ougetLineObject
pour interroger la table par relation de jointure. Dans l’exemple suivant, le champ Livrer maintenant est intégré à la commande client au niveau de la ligne.public class TaxIntegrationSalesTableDataRetrieval { protected MCRSalesLineDropShipment mcrSalesLineDropShipment; /// <summary> /// Gets the query for the lines of the document. /// </summary> /// <returns>The query for the lines of the document</returns> [Replaceable] protected SysDaQueryObject getLineQueryObject() { return SysDaQueryObjectBuilder::from(this.salesLine) .where(this.salesLine, fieldStr(SalesLine, SalesId)).isEqualToLiteral(this.salesTable.SalesId) .outerJoin(this.mcrSalesLineDropShipment) .where(this.mcrSalesLineDropShipment, fieldStr(MCRSalesLineDropShipment, SalesLine)).isEqualTo(this.salesLine, fieldStr(SalesLine, RecId)) .toSysDaQueryObject(); } }
Dans cet exemple, un tampon
mcrSalesLineDropShipment
est déclaré et la requête est définie dansgetLineQueryObject
. La requête utilise la relationMCRSalesLineDropShipment.SalesLine == SalesLine.RecId
. Pendant que vous réalisez l’extension dans cette situation, vous pouvez remplacergetLineQueryObject
par votre propre objet de requête construit. Notez toutefois les points suivants :- Parce que la valeur de retour de la méthode
getLineQueryObject
estSysDaQueryObject
, vous devez construire cet objet à l’aide de l’approche SysDa. - Impossible de supprimer la table existante.
- Parce que la valeur de retour de la méthode
Les données requises sont liées à la table de transaction par une relation de jointure complexe, ou la relation n’est pas un à un (1 : 1) mais un à plusieurs (1 : N). Dans cette situation, les choses deviennent un peu compliquées. Cette situation s’applique à l’exemple des dimensions financières.
Dans ce cas, vous pouvez implémenter votre propre méthode pour récupérer les données. Voici l’exemple de code dans le fichier
TaxIntegrationPurchTableDataRetrieval_Extension.xpp
.[ExtensionOf(classStr(TaxIntegrationPurchTableDataRetrieval))] final class TaxIntegrationPurchTableDataRetrieval_Extension { private const str CostCenterKey = 'CostCenter'; private const str ProjectKey = 'Project'; /// <summary> /// Copies to the current line of the document from. /// </summary> /// <param name = "_line">The current line of the document.</param> protected void copyToLineFromLineTable(TaxIntegrationLineObject _line) { Map dimensionAttributeMap = this.getDimensionAttributeMapByDefaultDimension(this.purchline.DefaultDimension); if (dimensionAttributeMap.exists(CostCenterKey)) { _line.setCostCenter(dimensionAttributeMap.lookup(CostCenterKey)); } if (dimensionAttributeMap.exists(ProjectKey)) { _line.setProjectId(dimensionAttributeMap.lookup(ProjectKey)); } next copyToLineFromLineTable(_line); } private Map getDimensionAttributeMapByDefaultDimension(RefRecId _defaultDimension) { DimensionAttribute dimensionAttribute; DimensionAttributeValue dimensionAttributeValue; DimensionAttributeValueSetItem dimensionAttributeValueSetItem; Map ret = new Map(Types::String, Types::String); select Name, RecId from dimensionAttribute join dimensionAttributeValue where dimensionAttributeValue.dimensionAttribute == dimensionAttribute.RecId join DimensionAttributeValueSetItem where DimensionAttributeValueSetItem.DimensionAttributeValue == DimensionAttributeValue.RecId && DimensionAttributeValueSetItem.DimensionAttributeValueSet == _defaultDimension; while(dimensionAttribute.RecId) { ret.insert(dimensionAttribute.Name, dimensionAttributeValue.DisplayValue); next dimensionAttribute; } return ret; } }
Étape 3. Ajouter des données à la requête
Étendez la méthode copyToTaxableDocumentHeaderWrapperFromTaxIntegrationDocumentObject
ou copyToTaxableDocumentLineWrapperFromTaxIntegrationLineObjectByLine
pour ajouter des données à la requête. Voici l’exemple de code dans le fichier TaxIntegrationCalculationActivityOnDocument_CalculationService_Extension.xpp
.
[ExtensionOf(classStr(TaxIntegrationCalculationActivityOnDocument_CalculationService))]
final static class TaxIntegrationCalculationActivityOnDocument_CalculationService_Extension
{
// Define the field name in the request
private const str IOCostCenter = 'Cost Center';
private const str IOProject = 'Project';
// private const str IOEnumExample = 'Enum Example';
/// <summary>
/// Copies to <c>TaxableDocumentLineWrapper</c> from <c>TaxIntegrationLineObject</c> by line.
/// </summary>
/// <param name = "_destination"><c>TaxableDocumentLineWrapper</c>.</param>
/// <param name = "_source"><c>TaxIntegrationLineObject</c>.</param>
protected static void copyToTaxableDocumentLineWrapperFromTaxIntegrationLineObjectByLine(Microsoft.Dynamics.TaxCalculation.ApiContracts.TaxableDocumentLineWrapper _destination, TaxIntegrationLineObject _source)
{
next copyToTaxableDocumentLineWrapperFromTaxIntegrationLineObjectByLine(_destination, _source);
// Set the field we need to integrated for tax service
_destination.SetField(IOCostCenter, _source.getCostCenter());
_destination.SetField(IOProject, _source.getProjectId());
// If the field to be extended is an enum type, use enum2Symbol to convert an enum variable exampleEnum of ExampleEnumType to a string
// _destination.SetField(IOEnumExample, enum2Symbol(enumNum(ExampleEnumType), _source.getExampleEnum()));
}
}
Dans ce code, _destination
est l’objet wrapper utilisé pour générer la requête, et _source
est l’objet TaxIntegrationLineObject
.
Note
Définissez le nom du champ utilisé dans la requête sous la forme private const str. La chaîne doit être exactement la même que le nom du nœud (par l’étiquette) ajouté dans l’article, Ajouter des champs de données dans les configurations fiscales.
Définissez le champ dans la méthode copyToTaxableDocumentLineWrapperFromTaxIntegrationLineObjectByLine en utilisant la méthode SetField. Le type de données du deuxième paramètre doit être string. Si le type de données n’est pas string, convertissez-le en chaîne. Si le type de données est type d’énumération X++, nous vous recommandons d’utiliser la méthode enum2Symbol pour convertir la valeur enum en une chaîne. La valeur enum ajoutée dans la configuration des taxes doit être exactement la même que le nom de l’énumération. Voici une liste des différences entre la valeur enum, l’étiquette et le nom.
- Le nom de enum est un nom symbolique dans le code. enum2Symbol() peut convertir la valeur enum en son nom.
- La valeur de l’énumération est de type entier.
- L’étiquette de l’énumération peut être différente selon les langues préférées. enum2Str() peut convertir la valeur enum en son étiquette.
Dépendance au modèle
Pour créer le projet avec succès, ajoutez les modèles de référence suivants aux dépendances de modèle :
- ApplicationPlatform
- ApplicationSuite
- Moteur de taxe
- Dimensions, si la dimension financière est utilisée
- Autres modèles nécessaires référencés dans le code
Validation
Après avoir terminé les étapes précédentes, vous pouvez valider vos modifications.
- Dans Finances, accédez à Comptabilité fournisseur et ajouter &debug=vs%2CconfirmExit& à l’URL. Par exemple,
https://usnconeboxax1aos.cloud.onebox.dynamics.com/?cmp=DEMF&mi=PurchTableListPage&debug=vs%2CconfirmExit&
. Le symbole & final est essentiel. - Ouvrez la page Commande fournisseur, puis sélectionnez Nouveau pour créer une commande fournisseur.
- Définissez la valeur du champ personnalisé, puis sélectionnez Taxe de vente. Un fichier de dépannage avec préfixe, TaxServiceTroubleshootingLog est téléchargé automatiquement. Ce fichier contient les informations sur les transactions envoyées au service de calcul des taxes.
- Vérifiez si le champ personnalisé ajouté est présent dans la section Entrée de calcul du service fiscal JSON et si sa valeur est correcte. Si la valeur n’est pas correcte, revérifiez les étapes de ce document.
Exemple de profil :
===Tax service calculation input JSON:===
{
"TaxableDocument": {
"Header": [
{
"Lines": [
{
"Line Type": "Normal",
"Item Code": "",
"Item Type": "Item",
"Quantity": 0.0,
"Amount": 1000.0,
"Currency": "EUR",
"Transaction Date": "2022-1-26T00:00:00",
...
/// The new fields added at line level
"Cost Center": "003",
"Project": "Proj-123"
}
],
"Amount include tax": true,
"Business Process": "Journal",
"Currency": "",
"Vendor Account": "DE-001",
"Vendor Invoice Account": "DE-001",
...
// The new fields added at header level, no new fields in this example
...
}
]
},
}
...
Annexe
Cette annexe présente l’exemple de code complet pour l’intégration des dimensions financières, Centre de coûts et Projet au niveau de la ligne.
TaxIntegrationLineObject_Extension.xpp
[ExtensionOf(classStr(TaxIntegrationLineObject))]
final class TaxIntegrationLineObject_Extension
{
private OMOperatingUnitNumber costCenter;
private ProjId projectId;
/// <summary>
/// Gets a costCenter.
/// </summary>
/// <returns>The cost center.</returns>
public final OMOperatingUnitNumber getCostCenter()
{
return this.costCenter;
}
/// <summary>
/// Sets the cost center.
/// </summary>
/// <param name = "_value">The cost center.</param>
public final void setCostCenter(OMOperatingUnitNumber _value)
{
this.costCenter = _value;
}
/// <summary>
/// Gets a project ID.
/// </summary>
/// <returns>The project ID.</returns>
public final ProjId getProjectId()
{
return this.projectId;
}
/// <summary>
/// Sets the project ID.
/// </summary>
/// <param name = "_value">The project ID.</param>
public final void setProjectId(ProjId _value)
{
this.projectId = _value;
}
}
TaxIntegrationPurchTableDataRetrieval_Extension.xpp
[ExtensionOf(classStr(TaxIntegrationPurchTableDataRetrieval))]
final class TaxIntegrationPurchTableDataRetrieval_Extension
{
private const str CostCenterKey = 'CostCenter';
private const str ProjectKey = 'Project';
/// <summary>
/// Copies to the current line of the document from.
/// </summary>
/// <param name = "_line">The current line of the document.</param>
protected void copyToLineFromLineTable(TaxIntegrationLineObject _line)
{
Map dimensionAttributeMap = this.getDimensionAttributeMapByDefaultDimension(this.purchline.DefaultDimension);
if (dimensionAttributeMap.exists(CostCenterKey))
{
_line.setCostCenter(dimensionAttributeMap.lookup(CostCenterKey));
}
if (dimensionAttributeMap.exists(ProjectKey))
{
_line.setProjectId(dimensionAttributeMap.lookup(ProjectKey));
}
next copyToLineFromLineTable(_line);
}
private Map getDimensionAttributeMapByDefaultDimension(RefRecId _defaultDimension)
{
DimensionAttribute dimensionAttribute;
DimensionAttributeValue dimensionAttributeValue;
DimensionAttributeValueSetItem dimensionAttributeValueSetItem;
Map ret = new Map(Types::String, Types::String);
select Name, RecId from dimensionAttribute
join dimensionAttributeValue
where dimensionAttributeValue.dimensionAttribute == dimensionAttribute.RecId
join DimensionAttributeValueSetItem
where DimensionAttributeValueSetItem.DimensionAttributeValue == DimensionAttributeValue.RecId
&& DimensionAttributeValueSetItem.DimensionAttributeValueSet == _defaultDimension;
while(dimensionAttribute.RecId)
{
ret.insert(dimensionAttribute.Name, dimensionAttributeValue.DisplayValue);
next dimensionAttribute;
}
return ret;
}
}
TaxIntegrationCalculationActivityOnDocument_CalculationService_Extension.xpp
[ExtensionOf(classStr(TaxIntegrationCalculationActivityOnDocument_CalculationService))]
final static class TaxIntegrationCalculationActivityOnDocument_CalculationService_Extension
{
// Define the field name in the request
private const str IOCostCenter = 'Cost Center';
private const str IOProject = 'Project';
/// <summary>
/// Copies to <c>TaxableDocumentLineWrapper</c> from <c>TaxIntegrationLineObject</c> by line.
/// </summary>
/// <param name = "_destination"><c>TaxableDocumentLineWrapper</c>.</param>
/// <param name = "_source"><c>TaxIntegrationLineObject</c>.</param>
protected static void copyToTaxableDocumentLineWrapperFromTaxIntegrationLineObjectByLine(Microsoft.Dynamics.TaxCalculation.ApiContracts.TaxableDocumentLineWrapper _destination, TaxIntegrationLineObject _source)
{
next copyToTaxableDocumentLineWrapperFromTaxIntegrationLineObjectByLine(_destination, _source);
// Set the field we need to integrated for tax service
_destination.SetField(IOCostCenter, _source.getCostCenter());
_destination.SetField(IOProject, _source.getProjectId());
}
}