使用扩展在税务集成中添加数据字段

本文说明如何使用 X++ 扩展在税务集成中添加数据字段。 这些字段可以扩展到税务服务的税务数据模型,并用于确定税码。 有关详细信息,请参阅在税务配置中添加数据字段

数据模型

数据模型中的数据由对象承载并由类实现。

下面是主要对象的列表:

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

下图显示了这些对象之间的关系。

数据模型对象关系。

单据对象可以包含许多对象。 每个对象都包含税务服务的元数据。

  • TaxIntegrationDocumentObject 具有 originAddress 元数据(包含有关源地址的信息)和 includingTax 元数据(指示行金额是否包含销售税)。
  • TaxIntegrationLineObject 具有 itemIdquantitycategoryId 元数据。

注释

TaxIntegrationLineObject 还实施费用对象。

集成流

流中的数据由活动处理。

关键活动

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

活动按以下顺序运行:

  1. 设置检索
  2. 数据检索
  3. 计算服务
  4. 币种汇兑
  5. 数据持久性

例如,在计算服务之前扩展数据检索

数据检索活动

数据检索活动从数据库中检索数据。 不同事务的适配器可用于从不同的事务表中检索数据:

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

在这些数据检索活动中,将数据从数据库复制到 TaxIntegrationDocumentObjectTaxIntegrationLineObject。 因为所有这些活动扩展相同的抽象模板类,所以它们具有通用方法。

计算服务活动

计算服务活动是税务服务和税务集成之间的链接。 此活动负责以下功能:

  1. 构造请求。
  2. 将请求过帐到税务服务。
  3. 从税务服务获得响应。
  4. 分析响应。

添加到请求的数据字段将与其他元数据一起过帐。

扩展实施

本部分提供说明如何实施扩展的详细步骤。 它使用成本中心项目财务维度作为示例。

步骤 1. 在对象类中添加数据变量

对象类包含数据变量和数据的 getter/setter 方法。 将数据字段添加到 TaxIntegrationDocumentObjectTaxIntegrationLineObject,具体取决于字段的级别。 以下示例使用行级别,文件名是 TaxIntegrationLineObject_Extension.xpp

注释

如果要添加的数据字段位于文档级别,将文件名更改为 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;
    }
}

成本中心项目将添加为私有变量。 为这些数据字段创建 getter 和 setter 方法以处理数据。

步骤 2. 从数据库检索数据

指定事务,并扩展适当的适配器类以检索数据。 例如,如果您使用采购订单事务,必须扩展 TaxIntegrationPurchTableDataRetrievalTaxIntegrationVendInvoiceInfoTableDataRetrieval

注释

TaxIntegrationPurchParmTableDataRetrieval 继承自 TaxIntegrationPurchTableDataRetrieval。 它不应该更改,除非 purchTablepurchParmTable 表的逻辑不同。

如果应该为所有事务添加数据字段,扩展所有 DataRetrieval 类。

因为所有数据检索活动扩展相同的模板类,类结构、变量和方法类似。

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

以下示例显示使用 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)
    ...
}

当调用 CopyToDocument 方法时,this.purchTable 缓冲已存在。 此方法的目的是使用在 DocumentObject 类中创建的 setter 方法将所有所需数据从 this.purchTable 复制到 document 对象。

同样,this.purchLine 缓冲已存在于 CopyToLine 方法中。 此方法的目的是使用在 LineObject 类中创建的 setter 方法将所有所需数据从 this.purchLine 复制到 _line 对象。

最直接的方法是扩展 CopyToDocumentCopyToLine 方法。 但是,我们建议您首先尝试 copyToDocumentFromHeaderTablecopyToLineFromLineTable 方法。 如果它们不按您的要求工作,请实施自己的方法,并在 CopyToDocumentCopyToLine 中调用它。 在以下三种常见情况下,您可能会使用此方法:

  • 必填字段在 PurchTablePurchLine 中。 在这种情况下,您可以扩展 copyToDocumentFromHeaderTablecopyToLineFromLineTable。 下面是示例代码。

    /// <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);
    }
    
  • 所需数据不在事务的默认表中。 但是,默认表存在一些联接关系,并且大多数行上都需要该字段。 在这种情况下,替换 getDocumentQueryObjectgetLineObject 以通过联接关系查询表。 在以下示例中,现在交货字段在行级别与销售订单集成。

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

    在此示例中,声明 mcrSalesLineDropShipment 缓冲,并在 getLineQueryObject 中定义查询。 查询使用关系 MCRSalesLineDropShipment.SalesLine == SalesLine.RecId。 当在这种情况下扩展时,可以将 getLineQueryObject 替换为您自己的构造查询对象。 但是,请注意以下几点:

    • 因为 getLineQueryObject 方法的返回值是 SysDaQueryObject,因此您必须使用 SysDa 方法构造此对象。
    • 无法删除现有表。
  • 所需数据通过复杂的联接关系与事务表相关,或者该关系不是一对一 (1:1),而是一对多 (1:N)。 在这种情况下,事情变得有些复杂。 这种情况适用于财务维度的示例。

    在这种情况下,您可以实施自己的方法来检索数据。 下面是 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;
        }
    }
    

步骤 3. 将数据添加到请求

扩展 copyToTaxableDocumentHeaderWrapperFromTaxIntegrationDocumentObjectcopyToTaxableDocumentLineWrapperFromTaxIntegrationLineObjectByLine 以将数据添加到请求。 下面是 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()));
    }
}

在此代码中,_destination 是用于生成请求的包装器对象,_sourceTaxIntegrationLineObject 对象。

注释

定义在请求中用作 private const str 的字段名称。 此字符串应与在税务配置中添加数据字段一文中添加的节点名称(而不是标签)完全相同。

使用 SetField 方法设置 copyToTaxableDocumentLineWrapperFromTaxIntegrationLineObjectByLine 方法中的字段。 第二个参数的数据类型应为字符串。 如果数据类型不是字符串,请将其转换为字符串。 如果数据类型为 X++ 枚举类型,我们建议您使用 enum2Symbol 方法将枚举值转换为字符串。 税务配置中添加的枚举值应与枚举名称完全相同。 以下是枚举值、标签和名称之间的差异列表。

  • 枚举的名称是代码中的一个符号名称。 enum2Symbol() 可将枚举值转换为它的名称。
  • 枚举的值是整数。
  • 枚举的标签可能因首选语言而异。 enum2Str() 可将枚举值转换为它的标签。

模型依赖项

要成功构建项目,请将以下引用模型添加到模型依赖项中:

  • ApplicationPlatform
  • ApplicationSuite
  • 税引擎
  • 维度(如果使用财务维度)
  • 代码中引用的其他必要模型

验证

完成前面的步骤后,您可以验证您的更改。

  1. 在 Finance 中,转到应付帐款,将 &debug=vs%2CconfirmExit& 添加到 URL。 例如,https://usnconeboxax1aos.cloud.onebox.dynamics.com/?cmp=DEMF&mi=PurchTableListPage&debug=vs%2CconfirmExit&。 最后的 & 是必不可少的。
  2. 打开采购订单页面,选择新建创建采购订单。
  3. 设置自定义字段的值,然后选择销售税。 将自动下载带有前缀 TaxServiceTroubleshootingLog 的故障排除文件。 此文件包含过帐到税款计算服务的交易信息。
  4. 检查添加的自定义字段是否存在于税务服务计算输入 JSON 部分,以及值是否正确。 如果值不正确,请仔细检查此文档中的步骤。

文件示例:

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

附录

本附录显示了用于在行级别集成财务维度成本中心项目的完整示例代码。

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