Contract-Based Web Services API

Hi Everyone.

Today I want to speak with you about new Contract-Based Web Services API that you can use in Acumatica 5.3 version.
The main differences between previous Screen-Based API and new AP:

  1. Previously you had to use names of fields and actions right from Acumatica UI. And if we rename or move field your code will stop working. Now new API has an intermediate layer between Acumatica UI and the integration code, so can be sure that most of minor changes in Acumatica code will not break existing integration.
  2. New API is easier to use. You need less lines to complete the same task. Also it is more intuitive, so it will easier for you to write a code.
  3. The current API just isn’t well adapted to REST. But now you can feel very strong industry trend toward to REST.

So now you have intermediate layer that we call “Endpoint”, We will keep and evolve default (build-in) endpoint that can be used for most of common task, but if you need more complex or specific logic than you have 2 options:

  • You can configure new one in Acumatica user interface. (Fast and flexible, but requires support).
  • Ask us to add new logic to default endpoint. (Long, but much easier to use and support)
Contract-Based Web Services API

Endpoint contains screens, linked/detail contains, field and actions. All these items has names that will be expanded through web services. On the other side all these items connected with Acumatica user interface elements.

Contract-Based API Field Mapping

When everything required is mapped you can start to use web service.
In this example i will show you how to use Stock Items thought API. For my task it is enough to use default endpoint, so i will use it.

Contract-Based API Management

If you click on “View Endpoint Service” you will see web services definition with all possible methods. The main thing that you need at this point of time is URL of web service.

Contract-Based API Endpoint

You will use this url in Visual Studio (Or other environment) to import web service definition.

Contract-Based API Add Reference to Visual Studio

And the last preparation step – do not forget to enable cookies and increase request limit.

Contract-Based API Cookies and Timeout Configuration

Now we can start development.
Here you can find code example that will show you some basic scenarios of using contract-based web services. You can copy it to your project in Visual Studio and try to play around

Code Example:

The classic web services API is not going anywhere and will remain supported. But it is better to built all new projects on the new contract-based web services API – sometimes next year it will become mandatory for solution certification.
Existing code can be migrated gradually; you can mix both technologies in the same product (You can share session cookies to avoid needing multiple logins).

Have a nice integration!

24 Replies to “Contract-Based Web Services API”

  1. I want to limit record load in Entity[] items = client.GetList(new StockItem(), false). Could you explain how to load this with 20 record from acumatica. Thank

  2. HI Vannak,
    As i know there is no function to limit number of records in CB API. However there are some workaround options:
    – Use filter to limit number of records
    – Use GI to create an inquiry that shows only top 10 records and use in with API
    – use Screen Based API.

    Hope it helps.

  3. For the contract-based web-services API, what would you suggest in-terms of helper / stub classes for PHP.

    The Screen based web-services API has Acumatica PHP helper files:

    1) acuwsdl2php.php
    2) AcumticaGate.php
    3) CustomerCreate.php
    4) CustomerSelect.php
    5) CustomerSelectAll.php
    6) LeadCreate.php

    This is particularly needed with the screen based web-services API as there are multiple WSDLs, one for each screen. Would I be right in assuming that with the contract based API generating stub-classes using:

    1) http://pear.php.net/reference/SOAP-0.9.4/SOAP/SOAP_WSDL.html#methodgenerateProxyCode
    2) https://www.wsdltophp.com/

    Would be more feasible? Or do you have new PHP helper for the contract based web-services

  4. Hello. Thank you for your blog. Would you mind doing a blog that explains how to setup your endpoints (I am trying to automate some Vendor reads and adds) for Acumatica 5.0?

    Thank you.

  5. I used the code on the I200-Screen-Based-Web-Services-6.0.pdf guide to export every customer , Inventory Item , Prices , … in the system and store them locally to be used in a local system

    this works with small amount of data but not big amount of data like 5 years stored customer data it takes very long time then gives an exception with the message :

    The operation has timed out ,

    And stack Trace :

    at System.Web.Services.Protocols.WebClientProtocol.GetWebResponse(WebRequest request) at System.Web.Services.Protocols.HttpWebClientProtocol.GetWebResponse(WebRequest request) at System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke(String methodName, Object[] parameters) at ERPPOS.STOREPOS.Screen.AR303000Export(Command[] commands, Filter[] filters, Int32 topCount, Boolean includeHeaders, Boolean breakOnError) in e:2BPOSERPPOSERPPOSWeb ReferencesSTOREPOSReference.cs:line 1619 at POS.Customer.ExportAllCustomers(Screen context) in e:2BPOSERPPOSERPPOSPOSIntegrationCustomer.cs:line 445 at POSVer1.ConfigurationSync.SyncCustomersConfiguration() in e:2BPOSERPPOSERPPOSPOSUtilitySyncUtilityConfigurationSync.cs:line 42

    then i have specified Timeout to a big number ,

    it takes very long time then gives an exception with the message :

    Client found response content type of '', but expected 'text/xml'. The request failed with an empty response. And the stack Trace :

    at System.Web.Services.Protocols.SoapHttpClientProtocol.ReadResponse(SoapClientMessage message, WebResponse response, Stream responseStream, Boolean asyncCall) at System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke(String methodName, Object[] parameters) at ERPPOS.STOREPOS.Screen.AR303000Export(Command[] commands, Filter[] filters, Int32 topCount, Boolean includeHeaders, Boolean breakOnError) in e:2BPOSERPPOSERPPOSWeb ReferencesSTOREPOSReference.cs:line 1619 at POS.Customer.ExportAllCustomers(Screen context) in e:2BPOSERPPOSERPPOSPOSIntegrationCustomer.cs:line 445 at POSVer1.ConfigurationSync.SyncCustomersConfiguration() in e:2BPOSERPPOSERPPOSPOSUtilitySyncUtilityConfigurationSync.cs:line 42

    Please Advice .

  6. Hi Abanoub,
    As in many other systems, when it comes to huge amount of data, you need to use different approach and limit number of records you can export.
    Please see here and example of how you can select data in batches
    https://stackoverflow.com/questions/46353867/exporting-records-from-acumatica-via-soap-contract-based-api

    With ReturnBehavior set to OnlySystem or OnlySpecified you can also define what parts of an entity should be exported. After you export IDs and keys in batches, record-specific data can be further requested with the Get method.

    Hope it helps!

  7. Hi Sergey,
    I got an error such as Value cannot be null : Parameter name:model.
    How to fix it?.

    Thanks

  8. Hi Pebrindanow. Sorry for late reply. Could you please give more details on where you get this error?

    1. Sergey, I’m getting the same error:

      System.ArgumentNullException: Value cannot be null.
      Parameter name: model
      at Microsoft.Data.Edm.EdmUtil.CheckArgumentNull[T](T value, String parameterName)
      at Microsoft.Data.Edm.ExtensionMethods.EntityContainers(IEdmModel model)
      at PX.Api.ContractBased.Edm.Helpers.GetEntityType(IEdmModel model, String objectName) in F:\Bld\AC-FULL2018R107-JOB1\sources\NetTools\PX.Api.ContractBased\Edm\Helpers.cs:line 0
      at PX.Api.ContractBased.Soap.EntityReaderBase.ReadEntity(String expectedEntityType) in F:\Bld\AC-FULL2018R107-JOB1\sources\NetTools\PX.Api.ContractBased\Soap\EntityReaderBase.cs:line 77
      at PX.Api.ContractBased.Soap.EntityReaderBase.ReadEntity(XmlReader reader, String localname, String expectedNamespace, String expectedEntityType) in F:\Bld\AC-FULL2018R107-JOB1\sources\NetTools\PX.Api.ContractBased\Soap\EntityReaderBase.cs:line 53
      at PX.Api.ContractBased.Soap.SoapMessageTransformerBase.BindParameters(XmlReader requestReader, MethodInfo methodInfo, Tuple`2[] parameterInfos) in F:\Bld\AC-FULL2018R107-JOB1\sources\NetTools\PX.Api.ContractBased\Soap\SoapMessageTransformerBase.cs:line 104
      at PX.Api.ContractBased.Soap.WebApiSoapController.Post(ISoapSystemContract systemContract, XmlReader requestReader, String serviceNamespace, String internalNamespace, MethodInfo method, Func`1 serviceFactory, IEdmModel edmModel) in F:\Bld\AC-FULL2018R107-JOB1\sources\NetTools\PX.Api.ContractBased\Soap\WebApiSoapController.cs:line 142
      at PX.Api.ContractBased.Soap.WebApiSoapController.d__6.MoveNext() in F:\Bld\AC-FULL2018R107-JOB1\sources\NetTools\PX.Api.ContractBased\Soap\WebApiSoapController.cs:line 102

  9. Now I have a different issue, the “linked entities” are getting loaded when calling GetList, the service did not generate the bool argument which I assumes loaded linked

    entites: client.GetList(new StockItem(), false);

    Mine is just client.GetList(new StockItem());

    Any way to force linked entities to load?

      1. Hello Sergey,

        I hope you’re doing well. I’m using contract-based SOAP APIs to try to import about 25,000 Journal Entry lines from a banking system into a single Acumatica GL batch. If I add all the records at once, my request times out after a few hours. Is there a faster/better way to do this? I looked into multi-threading to import the data into several smaller GL-batches and that works, but it still takes over an hour to run. Also, the customer does not accept that multi-threading approach; they want all their daily data in a single GL batch. Also, 25,000 records does not seem like a lot to me, so I wonder if Acumatica’s APIs were not built for this volume of lines in a single transaction. All I’m doing in my code is building the entity info by reading a text file and then calling the put method to create the GL batch using that entity with 25,000 records.

        I look forward to your response.

        Best regards,

        -Oscar Bello

  10. Hello Sergey,

    Your linked helped a lot. I’m able to upload records much faster this way. Having said that, When I try to change the header data for the GL Batch, Acumatica does not update the CuryID field properly. The rest of the header fields such as DateEntered, Hold, BranchID, and Description all allow me to updated them with no issues, so the issue is isolated to the CuryID field only. Is this a bug or by design?

    For the import in question, we need to create multiple GL Batches (one per currency), so we need to be able update the CuryID as needed (I hardcoded it to “CAD” for the example below). Please provide some input as to why CuryID cannot be updated properly.

    Best regards,

    -Oscar Bello

    See code below:

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using PX.Data;
    using PX.Common;
    using PX.Objects.GL;
    using PX.Objects.IN;
    using PX.Objects.CM;
    using PX.Export;

    namespace CCCUBankFileIntegrationProject
    {
    public class ImportTest : PXGraph
    {
    public const string SessionKey = “MyFileImportSessionKey”;

    public PXFilter Filter;
    public PXSelect<Details, Where<Details.batchNbr, Equal>> Transactions;
    public PXSelect NewFilePanel;

    public PXSave Save;
    public PXCancel Cancel;

    public PXAction CleanUp;
    [PXUIField(DisplayName = “Clean Up”, MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select, Visible = true)]
    [PXButton()]
    public virtual IEnumerable cleanUp(PXAdapter adapter)
    {
    foreach (Details tran in Transactions.Select())
    {
    Transactions.Delete(tran);
    }
    this.Actions.PressSave();

    return adapter.Get();
    }

    public PXAction UploadFileBatch;
    [PXUIField(DisplayName = “Upload File as Batches”, MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select, Visible = true)]
    [PXButton(CommitChanges = true)]
    public virtual IEnumerable uploadFileBatch(PXAdapter adapter)
    {
    if (this.NewFilePanel.AskExt() == WebDialogResult.OK)
    {
    PX.SM.FileInfo info = PXContext.SessionTyped().FileInfo[SessionKey] as PX.SM.FileInfo;
    System.Web.HttpContext.Current.Session.Remove(SessionKey);

    Random rnd = new Random();
    PXLongOperation.StartOperation(this.UID, delegate () { ImportBatch(info, rnd.Next(1, 1000) * 10000); });
    for (int i = 1; i < (Filter.Current.Threads ?? 1); i++)
    {
    PXLongOperation.StartOperation(Guid.NewGuid(), delegate () { ImportBatch(info, rnd.Next(1, 1000) * 10000); });
    }
    }

    return adapter.Get();
    }

    public PXAction UploadFileStandalone;
    [PXUIField(DisplayName = “Upload File as Standalone”, MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select, Visible = true)]
    [PXButton(CommitChanges = true)]

    public virtual IEnumerable uploadFileStandalone(PXAdapter adapter)
    {
    if (this.NewFilePanel.AskExt() == WebDialogResult.OK)
    {
    PX.SM.FileInfo info = PXContext.SessionTyped().FileInfo[SessionKey] as PX.SM.FileInfo;
    System.Web.HttpContext.Current.Session.Remove(SessionKey);

    Random rnd = new Random();
    PXLongOperation.StartOperation(this.UID, delegate () { ImportTran(info, rnd.Next(1, 1000) * 10000); });
    for (int i = 1; i < (Filter.Current.Threads ?? 1); i++)
    {
    PXLongOperation.StartOperation(Guid.NewGuid(), delegate () { ImportTran(info, rnd.Next(1, 1000) * 10000); });
    }
    }
    return adapter.Get();
    }

    private static void ImportTran(PX.SM.FileInfo file, Int32 range = 1)
    {
    ImportTest graph = CreateInstance();
    Byte[] bytes = file.BinData;

    using (PX.Data.XLSXReader reader = new XLSXReader(bytes))
    {
    //Initialising Reader
    reader.Reset();
    //Creating a dictionary to find column index by name
    Dictionary indexes = reader.IndexKeyPairs.ToDictionary(p => p.Value, p => p.Key);

    Int32 counter = range;
    DateTime start = DateTime.Now;
    while (reader.MoveNext())
    {
    Details row = graph.Transactions.Cache.CreateInstance() as Details;
    row.BatchNbr = “0”;
    row.LineNbr = counter;
    row = graph.Transactions.Insert(row);

    graph.Transactions.Cache.SetValueExt(row, “LedgerID”, “ACTUAL”);
    graph.Transactions.Cache.SetValueExt(row, “AccountID”, reader.GetValue(indexes[“Account”]));
    graph.Transactions.Cache.SetValueExt(row, “SubID”, reader.GetValue(indexes[“Subaccount”]).Replace(“-“, “”));
    row.UOM = String.IsNullOrWhiteSpace(reader.GetValue(indexes[“UOM”])) ? null : reader.GetValue(indexes[“UOM”]);
    row.TranDesc = reader.GetValue(indexes[“Description”]);
    row.Qty = 1.0m;
    row.CreditAmt = Decimal.Parse(reader.GetValue(indexes[“CreditAmount”]));
    row.DebitAmt = Decimal.Parse(reader.GetValue(indexes[“DebitAmount”]));

    row = graph.Transactions.Update(row);
    counter++;

    if ((counter % 1000) == 0)
    {
    graph.Actions.PressSave();
    graph.Clear();
    }
    }

    graph.Actions.PressSave();
    }
    }

    private static void ImportBatch(PX.SM.FileInfo file, Int32 range = 1)
    {
    JournalEntry graph = CreateInstance();

    Byte[] bytes = file.BinData;

    using (PX.Data.XLSXReader reader = new XLSXReader(bytes))
    {
    //Initialising Reader
    reader.Reset();
    //Creating a dictionary to find column index by name
    Dictionary indexes = reader.IndexKeyPairs.ToDictionary(p => p.Value, p => p.Key);

    //My code to put the batch on-hold and set header info like currency *****************************

    //Batch docHeader = graph.BatchModule.Cache.CreateInstance() as Batch;
    Batch docHeader = new Batch();

    docHeader.CuryID = “CAD”;
    docHeader.DateEntered = DateTime.Now.AddDays(-2);
    docHeader.Hold = true;
    docHeader.BranchID = 20; // “PRODRETAIL”;
    docHeader.Description = “Header Desc entered by me”;

    graph.BatchModule.Insert(docHeader);
    graph.Actions.PressSave();

    //END OF My code to put the batch on-hold and set header info like currency **********************

    Int32 counter = 0;
    DateTime start = DateTime.Now;
    while (reader.MoveNext())
    {
    GLTran row = graph.GLTranModuleBatNbr.Cache.CreateInstance() as GLTran;
    row = graph.GLTranModuleBatNbr.Insert(row);

    graph.GLTranModuleBatNbr.Cache.SetValueExt(row, “AccountID”, reader.GetValue(indexes[“Account”]));
    graph.GLTranModuleBatNbr.Cache.SetValueExt(row, “SubID”, reader.GetValue(indexes[“Subaccount”]).Replace(“-“, “”));
    row.Qty = 1.0m;
    row.CuryDebitAmt = Decimal.Parse(reader.GetValue(indexes[“DebitAmount”]));
    row.CuryCreditAmt = Decimal.Parse(reader.GetValue(indexes[“CreditAmount”]));

    row = graph.GLTranModuleBatNbr.Update(row);

    //Try to save each line separately and if errors are caught, add a GLTran with default data
    try
    {
    graph.Actions.PressSave();
    }

    catch (Exception e )
    {
    //DELETE the incorrect entry
    row = graph.GLTranModuleBatNbr.Delete(row);

    //Creating a substitute entry for the failed document line
    GLTran rowCorrected = graph.GLTranModuleBatNbr.Cache.CreateInstance() as GLTran;
    rowCorrected = graph.GLTranModuleBatNbr.Insert(row);

    graph.GLTranModuleBatNbr.Cache.SetValueExt(rowCorrected, “AccountID”, “81000”);
    graph.GLTranModuleBatNbr.Cache.SetValueExt(rowCorrected, “SubID”, “CON000”);
    row.Qty = 1.0m;
    row.CuryDebitAmt = Decimal.Parse(reader.GetValue(indexes[“DebitAmount”]));
    row.CuryCreditAmt = Decimal.Parse(reader.GetValue(indexes[“CreditAmount”]));
    graph.Actions.PressSave();
    }

    }

    graph.Actions.PressSave();

    }
    }
    }

    public class BatchConstant : Constant
    {
    public BatchConstant()
    : base(“0”)
    { }
    }

    public partial class Filter : PX.Data.IBqlTable
    {
    #region Threads
    public abstract class threads : PX.Data.IBqlField { }
    [PXInt(MinValue = 1, MaxValue = 4)]
    [PXUIField(DisplayName = “Threads “)]
    public virtual Int32? Threads { get; set; }
    #endregion
    }
    public partial class Details : PX.Data.IBqlTable
    {
    #region BatchNbr
    public abstract class batchNbr : PX.Data.IBqlField { }
    [PXDBString(15, IsUnicode = true, IsKey = true)]
    [PXUIField(DisplayName = “Batch Number”, Visibility = PXUIVisibility.Visible, Visible = false)]
    public virtual String BatchNbr { get; set; }
    #endregion
    #region LineNbr
    public abstract class lineNbr : PX.Data.IBqlField { }
    [PXDBInt(IsKey = true)]
    [PXDefault()]
    [PXUIField(DisplayName = “Line Nbr.”)]
    public virtual Int32? LineNbr { get; set; }
    #endregion
    #region LedgerID
    public abstract class ledgerID : PX.Data.IBqlField { }
    [PXDBInt()]
    [PXDefault()]
    [PXSelector(typeof(Search4<Ledger.ledgerID,
    //Where<Ledger.balanceType, NotEqual>,
    Aggregate<GroupBy>>),
    SubstituteKey = typeof(Ledger.ledgerCD))]
    [PXUIField(DisplayName = “Ledger ID”)]
    public virtual Int32? LedgerID { get; set; }
    #endregion
    #region AccountID
    public abstract class accountID : PX.Data.IBqlField { }
    [Account()]
    [PXDefault]
    public virtual Int32? AccountID { get; set; }
    #endregion
    #region SubID
    public abstract class subID : PX.Data.IBqlField { }
    [SubAccount(typeof(Details.accountID))]
    [PXDefault]
    public virtual Int32? SubID { get; set; }
    #endregion
    #region InventoryID
    public abstract class inventoryID : PX.Data.IBqlField { }
    [Inventory(Enabled = false, Visible = false)]
    public virtual Int32? InventoryID { get; set; }
    #endregion
    #region UOM
    public abstract class uOM : PX.Data.IBqlField { }
    [INUnit(typeof(Details.inventoryID))]
    public virtual String UOM { get; set; }
    #endregion
    #region Qty
    public abstract class qty : PX.Data.IBqlField { }
    [PXDBQuantity()]
    [PXDefault(TypeCode.Decimal, “0.0”)]
    [PXUIField(DisplayName = “Quantity”, Visibility = PXUIVisibility.Visible)]
    public virtual Decimal? Qty { get; set; }
    #endregion
    #region DebitAmt
    public abstract class debitAmt : PX.Data.IBqlField { }
    [PXDBBaseCury(typeof(Details.ledgerID))]
    [PXDefault(TypeCode.Decimal, “0.0”)]
    [PXUIField(DisplayName = “Debit Amt”)]
    public virtual Decimal? DebitAmt { get; set; }
    #endregion
    #region CreditAmt
    public abstract class creditAmt : PX.Data.IBqlField { }
    [PXDBBaseCury(typeof(Details.ledgerID))]
    [PXDefault(TypeCode.Decimal, “0.0”)]
    [PXUIField(DisplayName = “Credit Amt”)]
    public virtual Decimal? CreditAmt { get; set; }
    #endregion
    #region TranDesc
    public abstract class tranDesc : PX.Data.IBqlField { }
    [PXDBString(256, IsUnicode = true)]
    [PXUIField(DisplayName = “Transaction Description”, Visibility = PXUIVisibility.Visible)]
    public virtual String TranDesc { get; set; }
    #endregion
    #region TranDate
    public abstract class tranDate : PX.Data.IBqlField { }
    [PXDBDate()]
    [PXDefault(typeof(AccessInfo.businessDate))]
    [PXUIField(DisplayName = “Transaction Date”, Visibility = PXUIVisibility.Visible, Enabled = false)]
    public virtual DateTime? TranDate { get; set; }
    #endregion

    }

    }

    1. Hi Oscar,
      When you work with DACs thought the code you need to to emulate user behavior when he uses with User Interface. For example when you choose Date, Acumatica updates PostingPeriod. This is beacuse Date has commit changes flag. Same you need to do in the code:
      Batch docHeader = new Batch();
      docHeader.BranchID = 20; // “PRODRETAIL”;
      docHeader.Description = “Header Desc entered by me”;
      //Insert Record
      docHeader = graph.BatchModule.Insert(docHeader);
      //Update CUrrency
      docHeader.CuryID = “CAD”;
      docHeader = graph.BatchModule.Update(docHeader);
      //Update date
      docHeader.DateEntered = DateTime.Now.AddDays(-2);
      docHeader = graph.BatchModule.Update(docHeader);

      I Recommend you to follow same sequence as you do in the UI.

      1. Hello Sergey,

        That worked beautifully. Now all I have left is a simple question for sake of clarity. Can you clarify why we need to run the update method for some fields and not others? Because even the code you provided in your post “https://asiablog.acumatica.com/2016/03/simple-data-import-test.html” for adding GLTran lines works well only adding the update method once for all fields and not after every single field. When you say “For example when you choose Date, Acumatica updates PostingPeriod. This is beacuse Date has commit changes flag. ” My assumption is that the update method is needed for fields with the commit changes flag = true, correct? I’m just trying to understand when to apply and when not to apply the update method now. Thanks again for all your help; you’re a great resource.

        Best regards,

        -Oscar Bello

    2. Hi Oscar,
      As I explained, when you work thought code you need to emulate user behavior. So when user change the field you need to trigger business logic. However it is not for all fields because not all UI fields trigger business logic by themselves. Only fields where CommitChanges=true.
      So fields that generates callbacks from browser to server need to be updated in the separate update request in the code.

      1. Hi Sergey,

        One more thing. I have modified the program and made it work for the customer, but now I’m being asked to update some custom fields as well. I have tried the code below and it works only on the GLTran fields, but it does not update the GLTranExt custom fields and it does not generate any error messages. What am i missing? See code below:

        //Here I create instance of GLTran
        GLTran row = graph.GLTranModuleBatNbr.Cache.CreateInstance() as GLTran;

        //here I get a handle to graph extension GLTranExt to be able to update the added fields.
        var rowExt = row.GetExtension();

        row = graph.GLTranModuleBatNbr.Insert(row);

        graph.GLTranModuleBatNbr.Cache.SetValueExt(row, “AccountID”, JE.Account);
        graph.GLTranModuleBatNbr.Cache.SetValueExt(row, “SubID”, JE.Subaccount);
        row.TranDesc = “my line description”;
        row.Qty = 1.0m;
        row.CuryDebitAmt = (JE.DebitAmount);
        row.CuryCreditAmt = (JE.CreditAmount);
        rowExt.UsrTellerID = “Test teller”;
        rowExt.UsrTransactionTime = “Test Transaction Time”;
        rowExt.UsrTransactionType = “Test Transaction Type”;
        rowExt.UsrTranSequence = “Test Transaction Sequence”;
        row = graph.GLTranModuleBatNbr.Update(row);
        graph.Actions.PressSave();

        Best regards,

        -Oscar Bello

        1. I guess you need to change sequence of 2 lines:
          row = graph.GLTranModuleBatNbr.Insert(row);
          var rowExt = row.GetExtension();
          As when you insert row it may have a different key.

          1. Hello Sergey,

            You were right, that was the problem. Custom fields are now being updated successfully. I will keep the sequence in mind for any future development. Again, you’re a great source of knowledge and your help is greatly appreciated.

            Best regards,

            Oscar Bello

Leave a Reply

Please rate*

Your email address will not be published. Required fields are marked *