Partilhar via


Adicionar campos de dados na integração fiscal utilizando extensão

Isto artigo explica como utilizar as extensões X++ para adicionar campos de dados na integração fiscal. Estes campos podem ser expandidos ao modelo de dados fiscais do serviço de impostos e utilizados para determinar códigos fiscais. Para mais informações, consulte Adicionar campos de dados em configurações de imposto.

Modelo de dados

Os dados no modelo de dados são transportados por objetos e implementados por classes.

Aqui está uma lista dos objetos principais:

  • AxClass/TaxIntegrationDocumentObject
  • AxClass/TaxIntegrationLinhaObjeto
  • AxClass/TaxIntegrationTaxLineObject

A ilustração seguinte mostra como estes objetos estão relacionados.

Relação entre objectos do modelo de dados.]

Um objeto Document pode conter muitos objectos Line . Cada objeto contém metadados para o serviço de impostos.

  • TaxIntegrationDocumentObject tem os metadados originAddress , que contêm informações sobre o endereço de origem, e os metadados includingTax , que indicam se o montante da linha inclui imposto sobre vendas.
  • TaxIntegrationLineObject tem itemId, quantity e categoryId metadados.

Nota

TaxIntegrationLineObject também implementa os objectos Charge .

Fluxo de integração

Os dados no fluxo são manipulados por atividades.

Atividades principais

  • AxClass/TaxIntegrationCálculoActivityOnDocument
  • AxClass/TaxIntegrationCurrencyExchangeActivityOnDocument
  • AxClass/TaxIntegrationDataPersistenceActivityOnDocument
  • AxClass/TaxIntegrationDataRetrievalActivityOnDocument
  • AxClass/TaxIntegrationSettingRetrievalActivityOnDocument

As atividades são executadas pela seguinte ordem:

  1. Obtenção de definição
  2. Obtenção de dados
  3. Serviço de cálculo
  4. Câmbio de moedas
  5. Persistência de dados

Por exemplo, alargar Data Retrieval antes de Calculation Service.

Atividades de obtenção de dados

Recuperação de dados actividades recuperam dados da base de dados. Estão disponíveis adaptadores para diferentes transações de modo a obter dados de diferentes tabelas de transações:

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

Nestas actividades Data Retrieval , os dados são copiados da base de dados para TaxIntegrationDocumentObject e TaxIntegrationLineObject. Sendo que todas estas atividades expandem a mesma classe de modelos abstratos, têm métodos comuns.

Atividades do Serviço de cálculo

A atividade Calculation Service é a ligação entre o serviço fiscal e a integração fiscal. Esta atividade é responsável pelas seguintes funções:

  1. Construir o pedido.
  2. Publicar o pedido para o serviço de impostos.
  3. Obter a resposta do serviço de impostos.
  4. Analisar a resposta.

Um campo de dados que adicionar ao pedido será publicado juntamente com outros metadados.

Implementação de extensão

Esta secção fornece passos detalhados que explicam como implementar a extensão. Utiliza as dimensões financeiras Centro de custos e Projeto como exemplos.

Passo 1. Adicionar a variável de dados na classe de objetos

A classe de objeto contém a variável de dados e os métodos getter/setter para os dados. Adicionar o campo de dados a TaxIntegrationDocumentObject ou TaxIntegrationLineObject, consoante o nível do campo. O exemplo seguinte utiliza o nível de linha e o nome do ficheiro é TaxIntegrationLineObject_Extension.xpp.

Nota

Se o campo de dados que está a adicionar estiver ao nível do documento, altere o nome do ficheiro para 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;
    }
}

O centro de custos e o projeto são adicionados como variáveis privadas. Crie métodos getter e setter para estes campos de dados para manipular os dados.

Passo 2. Obter dados da base de dados

Especifique a transação e expanda as classes de adaptador apropriadas para obter os dados. Por exemplo, se for utilizada uma transação Ordem de compra , é necessário alargar TaxIntegrationPurchTableDataRetrieval e TaxIntegrationVendInvoiceInfoTableDataRetrieval.

Nota

TaxIntegrationPurchParmTableDataRetrieval é herdado de TaxIntegrationPurchTableDataRetrieval. Não deve ser alterado, exceto se a lógica das tabelas purchTable e purchParmTable for diferente.

Se o campo de dados tiver de ser adicionado a todas as transacções, estenda todas as classes DataRetrieval .

Como todas as actividades do Data Retrieval estendem a mesma classe de modelo, as estruturas de classe, as variáveis e os métodos são semelhantes.

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

O exemplo seguinte mostra a estrutura básica quando é utilizada a tabela PurchTable .

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 o método CopyToDocument é chamado, a memória intermédia this.purchTable já existe. O objetivo deste método é copiar todos os dados necessários de this.purchTable para o objeto document utilizando o método setter que foi criado na classe DocumentObject .

Do mesmo modo, já existe um buffer this.purchLine no método CopyToLine . O objetivo deste método é copiar todos os dados necessários de this.purchLine para o objeto _line utilizando o método setter que foi criado na classe LineObject .

A abordagem mais simples consiste em alargar os métodos CopyToDocument e CopyToLine . No entanto, recomendamos que experimente primeiro os métodos copyToDocumentFromHeaderTable e copyToLineFromLineTable . Se não funcionarem como pretende, implemente o seu próprio método e chame-o em CopyToDocument e CopyToLine. Há três situações comuns em que poderá utilizar esta abordagem:

  • O campo obrigatório está em PurchTable ou PurchLine. Nesta situação, é possível alargar copyToDocumentFromHeaderTable e copyToLineFromLineTable. Aqui está o código de amostra.

    /// <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);
    }
    
  • Os dados necessários não se encontram na tabela predefinida da transação. No entanto, existem algumas relações de associação com a tabela predefinida, e o campo é obrigatório na maioria das linhas. Nesta situação, substitua getDocumentQueryObject ou getLineObject para consultar a tabela por relação de união. No exemplo a seguir, o campo Deliver Now é integrado ao pedido de venda no nível da linha.

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

    Neste exemplo, é declarada uma memória intermédia mcrSalesLineDropShipment e a consulta é definida em getLineQueryObject. A consulta utiliza a relação MCRSalesLineDropShipment.SalesLine == SalesLine.RecId. Enquanto estiver a estender esta situação, pode substituir getLineQueryObject pelo seu próprio objeto de consulta construído. No entanto, tenha em atenção os seguintes pontos:

    • Uma vez que o valor de retorno do método getLineQueryObject é SysDaQueryObject, é necessário construir isto objeto utilizando a abordagem SysDa.
    • Não é possível remover a tabela existente.
  • Os dados necessários estão relacionados com a tabela de transações através de uma complicada relação de associação, ou a relação não é de um para um (1:1) mas de um para muitos (1:N). Nesta situação, as coisas tornam-se um pouco complicadas. Esta situação aplica-se ao exemplo das dimensões financeiras.

    Nesta situação, pode implementar o seu próprio método para obter os dados. Eis o código de exemplo no ficheiro 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;
        }
    }
    

Passo 3. Adicionar dados ao pedido

Alargar o método copyToTaxableDocumentHeaderWrapperFromTaxIntegrationDocumentObject ou copyToTaxableDocumentLineWrapperFromTaxIntegrationLineObjectByLine para adicionar dados ao pedido. Eis o código de exemplo no ficheiro 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()));
    }
}

Neste código, _destination é o objeto envolvente que é utilizado para gerar o pedido e _source é o objeto TaxIntegrationLineObject .

Nota

Definir o nome do campo que é utilizado no pedido como private const str. A cadeia de caracteres deve ser exatamente igual ao nome do nó (não o etiqueta) adicionado no artigo Adicionar campos de dados nas configurações de impostos.

Definir o campo no método copyToTaxableDocumentLineWrapperFromTaxIntegrationLineObjectByLine utilizando o método SetField . O tipo de dados do segundo parâmetro deve ser string. Se o tipo de dados não for string, converta-o para string. Se o tipo de dados for X++ enum type, recomenda-se a utilização do método enum2Symbol para converter o valor do enum numa cadeia de caracteres. O valor do enum acrescentado na configuração fiscal deve ser exatamente igual ao nome do enum. Segue-se uma lista das diferenças entre enum value, label e name.

  • O nome do enum é um nome simbólico no código. enum2Symbol() pode converter o valor do enum no seu nome.
  • O valor da enumeração é um número inteiro.
  • A etiqueta da enumeração pode ser diferente nos idiomas preferidos. enum2Str() pode converter o valor do enum na sua etiqueta.

Dependência do modelo

Para compilar com sucesso o projeto, adicione os seguintes modelos de referência às dependências do modelo:

  • ApplicationPlatform
  • ApplicationSuite
  • Motor fiscal
  • Dimensões, se for utilizada uma dimensão financeira
  • Outros modelos necessários referenciados no código

Validação

Após completar os passos anteriores, pode validar as suas alterações.

  1. Em Finanças, vá a Contas a pagar e adicione &debug=vs%2CconfirmExit& ao URL. Por exemplo, https://usnconeboxax1aos.cloud.onebox.dynamics.com/?cmp=DEMF&mi=PurchTableListPage&debug=vs%2CconfirmExit&. O final & é essencial.
  2. Abra a página Purchase order e seleccione New para criar uma ordem de compra.
  3. Defina o valor para o campo personalizado e, em seguida, seleccione Sales tax. Um ficheiro de resolução de problemas com o prefixo TaxServiceTroubleshootingLog é transferido automaticamente. Este ficheiro contém informações sobre a transação publicadas no Serviço de cálculo de impostos.
  4. Verifique se o campo personalizado adicionado está presente na secção JSON Tax service calculation input e se o seu valor está correto. Se o valor não estiver correto, verifique novamente os passos neste documento.

Exemplo de ficheiro:

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

Anexo

Isto apêndice mostra o código de amostra completo para a integração das dimensões financeiras, Centro de custos e Projeto ao nível da linha.

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