共用方式為


使用擴充在稅務整合中新增資料欄位

本文介紹如何使用 X++ 擴充功能在稅務整合中新增資料欄位。 這些欄位可以擴充到稅務服務的稅務資料模型,並用於確定稅碼。 有關詳細信息,請參閱 在稅務配置中新增資料欄位

資料模型

資料模型中的資料由物件承載,由類別實現。

以下是主要物件的列表:

  • AxClass/TaxIntegration文檔對象
  • AxClass/TaxIntegration對象
  • AxClass/TaxIntegrationTaxLine對象

下圖顯示這些物件之間的關係。

資料模型物件關係。 ]

一個 Document 物件可以包含許多 Line 物件。 每個物件都包含稅務服務的中繼資料。

  • TaxIntegrationDocumentObjectoriginAddress 元數據,其中包含有關來源地址的信息,以及 includingTax 元數據,該元數據指示行金額是否包含銷售稅。
  • TaxIntegrationLineObject 具有 itemIdquantitycategoryId 元資料。

附註

TaxIntegrationLineObject 也實現了 Charge 物件。

整合流程

流程中的資料由活動操控。

重要活動

  • AxClass/TaxIntegration計算ActivityOnDocument
  • AxClass/TaxIntegrationCurrencyExchangeActivityOnDocument
  • AxClass/TaxIntegrationDataPersistenceActivityOnDocument
  • AxClass/TaxIntegration資料擷取ActivityOnDocument
  • AxClass/TaxIntegrationSettingRetrievalActivityOnDocument

活動按以下順序執行:

  1. 設定檢索
  2. 資料檢索
  3. 計算服務
  4. 貨幣兌換
  5. 資料永續性

例如,在 計算服務 之前擴展 資料擷取

資料檢索活動

資料檢索 活動從資料庫檢索資料。 不同交易的配接器可用於從不同交易資料表中擷取資料:

  • AxClass/TaxIntegrationPurchTable資料檢索
  • AxClass/TaxIntegrationPurchParmTable資料檢索
  • AxClass/TaxIntegrationPurchREQTable資料檢索
  • AxClass/TaxIntegrationPurchRFQTable資料檢索
  • AxClass/TaxIntegrationVendInvoiceInfoTable資料檢索
  • AxClass/TaxIntegrationSalesTable資料檢索
  • AxClass/TaxIntegrationSalesParm資料檢索

在這些 資料擷取 活動中,資料從資料庫複製到 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 緩衝區已經存在。 此方法的目的是使用在 this.purchTable 類別中建立的 setter 方法將所有必需的資料從 document 複製到 DocumentObject 物件。

同樣, this.purchLine 緩衝區已存在於 CopyToLine 方法中。 此方法的目的是使用在 this.purchLine 類別中建立的 setter 方法將所有必需的資料從 _line 複製到 LineObject 物件。

最直接的方法是擴展 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 方法中設定欄位。 第二個參數的資料型別應為 string。 如果資料類型不是 string,則將其轉換為字串。 如果資料類型為 X++ 列舉類型,建議您使用 enum2Symbol 方法轉換列舉值轉換為字串。 稅務配置中新增的列舉值應與列舉名稱完全相同。 以下是列舉值、標籤和名稱之間的差異清單。

  • 列舉的名稱是程式碼中的符號名稱。 enum2Symbol() 可以將列舉值轉換為其名稱。
  • 該列舉值為整數。
  • 列舉的標籤可能因偏好的語言而異。 enum2Str() 可以將列舉值轉換為其標籤。

模型相依性

要成功構建項目,請將以下參考模型添加到模型相依性中:

  • ApplicationPlatform
  • ApplicationSuite
  • 稅務引擎
  • 維度,如果使用財務維度
  • 程式碼中引用的其他必要模型

驗證

完成前面的步驟後,您可以驗證您的更改。

  1. 在財務中,請前往 應付帳款 並新增 &debug=vs%2CconfirmExit& 到網址。 例如, 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());
    }
}