Condividi tramite


Aggiungere campi dati nell'integrazione fiscale usando l'estensione

In questo articolo viene illustrato come usare le estensioni X++ per aggiungere campi dati nell'integrazione fiscale. Questi campi possono essere estesi al modello di dati fiscali del servizio per le imposte e utilizzati per determinare i codici imposta. Per ulteriori informazioni, vedi Aggiungere campi dati nelle configurazioni imposte.

Modello dati

I dati nel modello di dati sono forniti dagli oggetti e implementati dalle classi.

Ecco un elenco degli oggetti principali:

  • AxClass/TaxIntegrationDocumentObject
  • AxClass/TaxIntegrationLineObject
  • AxClass/TaxIntegrationTaxLineObject

Nella figura seguente viene illustrato come questi oggetti sono correlati.

Relazione degli oggetti del modello di dati. ]

Un oggetto Documento può contenere molti oggetti Riga. Ogni oggetto contiene metadati per il servizio per le imposte.

  • TaxIntegrationDocumentObject contiene metadati originAddress, che contengono informazioni sull'indirizzo di origine, e metadati includingTax, che indicano se l'importo della riga include l'IVA.
  • TaxIntegrationLineObject contiene i metadati itemId, quantity e categoryId.

Nota

TaxIntegrationLineObject implementa anche gli oggetti Addebito.

Flusso di integrazione

I dati nel flusso vengono gestiti dalle attività.

Attività chiave

  • AxClass/TaxIntegrationCalculationActivityOnDocument
  • AxClass/TaxIntegrationCurrencyExchangeActivityOnDocument
  • AxClass/TaxIntegrationDataPersistenceActivityOnDocument
  • AxClass/TaxIntegrationDataRetrievalActivityOnDocument
  • AxClass/TaxIntegrationSettingRetrievalActivityOnDocument

Le attività vengono eseguite nell'ordine seguente:

  1. Recupero impostazione
  2. Recupero dati
  3. Servizio di calcolo
  4. Cambio valuta
  5. Persistenza dei dati

Ad esempio, estendi Recupero dati prima del Servizio di calcolo.

Attività di recupero dati

Le attività di recupero dati recuperano i dati dal database. Adattatori per transazioni diverse sono disponibili per recuperare i dati da tabelle di transazioni diverse:

  • AxClass/TaxIntegrationPurchTableDataRetrieval
  • AxClass/TaxIntegrationPurchParmTableDataRetrieval
  • AxClass/TaxIntegrationPurchREQTableDataRetrieval
  • AxClass/TaxIntegrationPurchRFQTableDataRetrieval
  • AxClass/TaxIntegrationVendInvoiceInfoTableDataRetrieval
  • AxClass/TaxIntegrationSalesTableDataRetrieval
  • AxClass/TaxIntegrationSalesParmDataRetrieval

In queste attività di recupero dati, i dati vengono copiati dal database in TaxIntegrationDocumentObject e TaxIntegrationLineObject. Poiché tutte queste attività estendono la stessa classe di modelli astratta, hanno metodi comuni.

Attività del servizio di calcolo

L'attività Servizio di calcolo è il collegamento tra il servizio per le imposte e l'integrazione fiscale. Questa attività è responsabile delle seguenti funzioni:

  1. Costruire la richiesta.
  2. Registrare la richiesta nel servizio per le imposte.
  3. Ottenere la risposta dal servizio per le imposte.
  4. Analizzare la risposta.

Un campo dati aggiunto alla richiesta verrà inviato insieme agli altri metadati.

Implementazione dell'estensione

In questa sezione vengono descritti i passaggi dettagliati che spiegano come implementare l'estensione. Ad esempio, vengono utilizzate le dimensioni finanziarie Centro di costo e Progetto.

Passaggio 1. Aggiungere la variabile dati nella classe oggetto

La classe oggetto contiene la variabile di dati e i metodi getter/setter per i dati. Aggiungi il campo dati a TaxIntegrationDocumentObject o a TaxIntegrationLineObject, a seconda del livello del campo. L'esempio seguente utilizza il livello di riga e il nome del file è TaxIntegrationLineObject_Extension.xpp.

Nota

Se il campo dati che stai aggiungendo è a livello di documento, cambia il nome del file in 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;
    }
}

Centro di costo e Progetto vengono aggiunti come variabili private. Crea metodi getter e setter per questi campi dati per manipolare i dati.

Passaggio 2. Recuperare i dati dal database

Specifica la transazione ed estendi le classi dell'adattatore appropriate per recuperare i dati. Ad esempio, se utilizzi una transazione Ordine fornitore, è necessario estendere TaxIntegrationPurchTableDataRetrieval e TaxIntegrationVendInvoiceInfoTableDataRetrieval.

Nota

TaxIntegrationPurchParmTableDataRetrieval è ereditato da TaxIntegrationPurchTableDataRetrieval. Non deve essere modificato a meno che la logica delle tabelle purchTable e purchParmTable è diversa.

Se il campo dati deve essere aggiunto per tutte le transazioni, estendi tutte le classi DataRetrieval.

Perché tutte le attività Recupero dati estendono la stessa classe modello, le strutture della classe, le variabili e i metodi sono simili.

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'esempio seguente mostra la struttura di base quando la tabella PurchTable viene utilizzata.

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

Quando il metodo CopyToDocument viene chiamato, il buffer this.purchTable è già esistente. Lo scopo di questo metodo è copiare tutti i dati richiesti da this.purchTable nell'oggetto document utilizzando il metodo setter creato nella classe DocumentObject.

Allo stesso modo, un buffer this.purchLine esiste già nel metodo CopyToLine. Lo scopo di questo metodo è copiare tutti i dati richiesti da this.purchLine nell'oggetto _line utilizzando il metodo setter creato nella classe LineObject.

L'approccio più semplice è estendere i metodi CopyToDocument e CopyToLine. Tuttavia, ti consigliamo di provare prima i metodi copyToDocumentFromHeaderTable e copyToLineFromLineTable. Se non funzionano come richiesto, implementa il tuo metodo e chiamalo in CopyToDocument e CopyToLine. Esistono tre situazioni comuni in cui potresti utilizzare questo approccio:

  • Il campo obbligatorio è in PurchTable o PurchLine. In questa situazione, puoi estendere copyToDocumentFromHeaderTable e copyToLineFromLineTable. Ecco il codice di esempio.

    /// <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);
    }
    
  • I dati richiesti non sono nella tabella predefinita della transazione. Tuttavia, esistono alcune relazioni di join con la tabella predefinita e il campo è obbligatorio nella maggior parte delle righe. In questa situazione, sostituisci getDocumentQueryObject o getLineObject per eseguire query sulla tabella in base alla relazione di join. Nell'esempio seguente, il campo Consegna ora è integrato con l'ordine cliente a livello di riga.

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

    In questo esempio, un buffer mcrSalesLineDropShipment viene dichiarato e la query viene definita in getLineQueryObject. La query utilizza la relazione MCRSalesLineDropShipment.SalesLine == SalesLine.RecId. In questa situazione, puoi sostituire getLineQueryObject con il tuo oggetto query costruito. Considera però i seguenti punti:

    • Perché il valore restituito del metodo getLineQueryObject è SysDaQueryObject, è necessario costruire questo oggetto utilizzando l'approccio SysDa.
    • Non è possibile rimuovere la tabella esistente.
  • I dati richiesti sono correlati alla tabella delle transazioni da una complicata relazione di join, oppure la relazione non è uno a uno (1:1) ma uno a molti (1:N). In questa situazione, le cose diventano un po' più complicate. Questa situazione si applica all'esempio delle dimensioni finanziarie.

    In questa situazione, puoi implementare il tuo metodo per recuperare i dati. Ecco il codice di esempio nel file 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;
        }
    }
    

Passaggio 3. Aggiungere dati alla richiesta

Estendi il metodo copyToTaxableDocumentHeaderWrapperFromTaxIntegrationDocumentObject o copyToTaxableDocumentLineWrapperFromTaxIntegrationLineObjectByLine per aggiungere dati alla richiesta. Ecco il codice di esempio nel file 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()));
    }
}

In questo codice, _destination è l'oggetto wrapper utilizzato per generare la richiesta e _source è l'oggetto TaxIntegrationLineObject.

Nota

Definisci il nome campo utilizzato nella richiesta come private const str. La stringa deve essere esattamente la stessa del nome del nodo (non l'etichetta) aggiunta nell'articolo Aggiungere campi dati nelle configurazioni imposte.

Imposta il campo nel metodo copyToTaxableDocumentLineWrapperFromTaxIntegrationLineObjectByLine utilizzando il metodo SetField. Il tipo di dati del secondo parametro deve essere string. Se il tipo di dati non è string, convertilo in stringa. Se il tipo di dati è tipo enum X++, ti consigliamo di utilizzare il metodo enum2Symbol per convertire il valore enum in una stringa. Il valore enum aggiunto nella configurazione fiscale deve essere esattamente lo stesso del nome dell'enumerazione. Di seguito è riportato un elenco delle differenze tra valore enum, etichetta e nome.

  • Il nome di enum è un nome simbolico nel codice. enum2Symbol() può convertire il valore enum nel relativo nome.
  • Il valore dell'enumerazione è intero.
  • L'etichetta dell'enumerazione può essere diversa nelle lingue preferite. enum2Str() può convertire il valore enum nella relativa etichetta.

Dipendenza dal modello

Per creare correttamente il progetto, aggiungi i seguenti modelli di riferimento alle dipendenze del modello:

  • ApplicationPlatform
  • ApplicationSuite
  • Motore imposte
  • Dimensioni, se viene utilizzata la dimensione finanziaria
  • Altri modelli necessari a cui si fa riferimento nel codice

Convalida

Dopo aver completato i passaggi precedenti, puoi convalidare le modifiche.

  1. In Finance, vai a Contabilità fornitori e aggiungi &debug=vs%2CconfirmExit& all'URL. Ad esempio, https://usnconeboxax1aos.cloud.onebox.dynamics.com/?cmp=DEMF&mi=PurchTableListPage&debug=vs%2CconfirmExit&. Il carattere & finale è essenziale.
  2. Apri la pagina Ordine fornitore e seleziona Nuovo per creare un ordine fornitore.
  3. Imposta il valore per il campo personalizzato, quindi seleziona IVA. Un file di risoluzione dei problemi con il prefisso TaxServiceTroubleshootingLog viene scaricato automaticamente. Questo file contiene le informazioni sulla transazione registrate nel servizio di calcolo delle imposte.
  4. Verifica se il campo personalizzato aggiunto è presente nella sezione JSON input per servizio di calcolo delle imposte e se il relativo valore è corretto. Se il valore non è corretto, ricontrollare i passaggi in questo documento.

Esempio di file:

===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
        ...
      }
    ]
  },
}
...

Appendice

Questa appendice mostra il codice di esempio completo per l'integrazione delle dimensioni finanziarie Centro di costo e Progetto a livello di riga.

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