Stephen Smith's Blog

Musings on Machine Learning…

Starting to Program the Sage 300 ERP Views in .Net

with 27 comments


Introduction

Last time we used the Sage 300 ERP .Net Interface to open a session and create a database link to a Sage 300 ERP company. In this article we will start to investigate how to use the API to manipulate the Sage 300 ERP business logic. The individual business logic objects are known as Views (not to be confused with the Views in MVC or SQL Server Views). For a bit more background on the Views have a look at this article.

These business logic Views represent all the various objects in Sage 300 like G/L Accounts, A/R Customers or O/E Orders. There are also Views for doing processing operations like posting G/L batches or running I/C Day End. The nice thing about these Views is that they all share the same interface and this complete interface is accessible from the .Net API.  Although the API to each view is standard, sometimes you need to use several Views together to accomplish a task and there are about 5 protocols for how you use the Views together to accomplish something. But if you learn the API for one set of Views and learn the 5 protocols then you can do anything in any of the Sage 300 applications from any of the several hundred Views. Additionally you can utilize any Views created by third party ISV solutions.

Since the .Net interface is used by our VB UIs when they are running in the old 5.0A style web deployed, via the .Net Remoting option, you know that the .Net API is capable of performing any task that can be performed by a regular Sage 300 form.

As we proceed we’ll look into the various parts of the API in more detail, but for this article we’ll just look at how to get started and do some basic operations with data views.

Opening a View

To use a View, first we need to open it from the database link (DBLink). Doing this is quite simple:

ACCPAC.Advantage.View arCustView = mDBLinkCmpRW.OpenView("AR0024");

In this case we needed to add the “ACCPAC.Advantage” part to the definition of View, because there is a System.Windows.Forms.View and the compiler needs to know which one we mean. Unfortunately the word View is a bit over used in Computer Science which can lead to some confusion.

But what is this “AR0024” that we are opening? Where did that come from? In the Sage 300 world, all UIs and Views are uniquely identified by what is called a Roto ID which consists of two alphabetic characters followed by four decimal digits. Every Sage 300 SDK application whether written by Sage or an ISV must register a unique two letter prefix for their application with the DPP program. This then guarantees that two SDK modules won’t conflict with each other. Then the developer of the module (in this case A/R) assigns the numbers to all their Views and UIs. Sage’s convention is to start the Views at 0001 and to start the UIs at 1000.

So how do you know what to specify? There are several ways to figure this out.

  1. Use the Sage 300 ERP Application Object Model (AOM). Which is on our Web site here. From this site you can get a list of all Views for all the Sage applications along with any underlying database table structure. Using this site requires using Internet Explorer. You can’t use this for information on ISV applications.
  2. If you have the SDK then you can use the very helpful ViewDoc utility which is part of the SDK application (which you must activate inside Sage 300). A benefit of this is that you can get information on ISV applications that are installed on your system as well.
  3. Use macro recording. If you macro record a UI which uses the View you are after, then the macro recording will record the DBLink OpenView call with the roto view. Just note you need to change the syntax from VBA/COM to C#/.Net (which is fairly easy).
  4. The UI Info tool that is included with the core product can be used, but you need to first get the info on a UI that uses the View then drill down into the View by getting info on the data source.

After calling OpenView, your view object is ready to use, so let’s see some things we can do.

CRUD

CRUD stands for “Create, Read, Update and Delete”. Here we’ll look at reading and updating anyway.

When you open a View there is not data loaded. If we don’t know what record we want, one way to find out is to iterate through all the records or to just read in the first one. Calling GoTop will get the first record.

bool gotOne = arCustView.GoTop();

This function returns a bool to specify true if it returned a record and false if it didn’t. Most of the .Net API functions have simple return codes like this. These are usually the things you want to handle easily programmatically. If something else happens then this function will throw an exception, these could be things like a network connectivity errors or some bad SQL Server index corruption error. Today we’ll just handle the easy cases. In a future article we’ll look more at error handling and what to do when one of these methods throws an exception.

Now let’s iterate through all the records and print out the customer records (assuming the GoTop above was called first).

String custNum;
String custName;

while (gotOne)
{
    custNum = (String) arCustView.Fields.FieldByName("IDCUST").Value;
    custName = (String) arCustView.Fields.FieldByName("NAMECUST").Value;
    Console.WriteLine("Customer Number: " + custNum +
        " Customer Name: " + custName);
    gotOne = arCustView.GoNext();
}

If we got a record then get the customer number and customer name and write them to the console. Inside each View there is a collection of Fields. These usually include the database table fields along with some calculated fields. We’ll look at these in much more detail in a future article. For now this is how you get the various fields from the customer record. How do you know the strings “IDCUST” and “NAMECUST”? You find these the same way you find the Roto ID. The four methods mentioned above will also give you all the fields for each View. We had to cast the result to “String” because the field value is an object. The reason for this is that each field has a type like number, string, date or time and depending on the type will affect the object type. In this case we know these are both strings, so we can just tell the compiler that by setting the cast. If we got this wrong we’ll get an exception when we run. Again the four methods above will give you all the field types as well as some more useful information.

OK so that reads all the records, but what if we know which record we want and just want to read it? This is done as follows:

arCustView.Fields.FieldByName("IDCUST").SetValue("1200", false);
arCustView.Read(false);
custNum = (String) arCustView.Fields.FieldByName("IDCUST").Value;
custName = (String) arCustView.Fields.FieldByName("NAMECUST").Value;
Console.WriteLine("After Read, Customer Number = " + custNum +
     " Customer Name: " + custName);

Here we see how to set the key field for the customer record with the SetValue method of the field. The second parameter is verify which we’ll talk about another time, but for now this is fine set to false. This just determines if we should verify this field right away or wait for later.

Then we call Read to read the record. The parameter lock is set to false, which is nearly always the case (if you set it to true then you will get an exception and an error about needing to be in a transaction which we’ll talk about another time).

Then there is the code to get the field value and print them to the console. A bit of bad programming here with no error checking. Note that this will only work if there is a customer 1200 like in sample data.

Suppose now that we’ve read this record we want to update it? Well that turns out to be quite easy:

arCustView.Fields.FieldByName("NAMECUST").SetValue("Ronald MacDonald", false);
arCustView.Update();

Here we set the field “NAMECUST” to a new value and then call the Update method which has no paramters. You can then run Sage 300 and run the A/R customer’s screen, bring up customer 1200 and see that his name is in fact changed.

Summary

This was a quick introduction to the basics of how to access and use the Business Logic Views in Sage 300 ERP. All the API elements described apply to all the Views in Sage 300 ERP, so learning to manipulate on object goes a long way to proficiently manipulating all objects.

I’ve updated the sample application here as mentioned in this article.

Written by smist08

October 20, 2013 at 12:27 am

27 Responses

Subscribe to comments with RSS.

  1. […] Introduction Last time we used the Sage 300 ERP .Net Interface to open a session and create a database link to a Sage 300 ERP company. In this article we will start to investigate how to use the AP…  […]

  2. very good steve VLS

    victor labonte-smith

    October 28, 2013 at 12:43 pm

  3. Hi,
    Is there any way to ready optional fields values using sage erp300 .net api

    Phaneendra

    October 29, 2013 at 1:21 pm

    • Yes. Optional fields are always implemented as a detail view to the thing they are optional fields for. So AR0400 (ARCUSO) is a detail view to AR0024 (ARCUS). I’m just starting to talk about handling header/detail views, but will have more in future articles.

      smist08

      October 29, 2013 at 7:04 pm

      • Hi,
        I have created optional field at PetAnimal and has values like (Cat – Dog), now I need to read this particular Custom Field Value. How can i perform this action.

        Regards
        Phaneendra

        Phaneendra

        October 31, 2013 at 5:57 am

  4. Thank you for these posts. They are a gem. It helped me solve a problem I was having as posted on TekTips (http://www.tek-tips.com/viewthread.cfm?qid=1727196)

    Rodney

    February 21, 2014 at 2:18 am

  5. hi,
    i need a help
    compose views on record macro sage 300 61A using Accpac.Comapi not Accpac:Advantage

    because the code below i get following error : this view is incompatible

    try
    {

    string companyID = “TTTDAT”;
    string userID = “ADMIN”;
    string password = “ADMIN”;
    session.Init(“”, “AS”, “1001”, “61A”);
    session.Open(userID, password, companyID, DateTime.Today, 0, “”);

    AccpacCOMAPI.AccpacDBLink dblink = session.OpenDBLink(AccpacCOMAPI.tagDBLinkTypeEnum.DBLINK_COMPANY, AccpacCOMAPI.tagDBLinkFlagsEnum.DBLINK_FLG_READWRITE);

    dblink.OpenView(“IC0640”, out ICSHE1header);
    dblink.OpenView(“IC0630”, out ICSHE1detail1);
    dblink.OpenView(“IC0645”, out ICSHE1detail2);
    dblink.OpenView(“IC0635”, out ICSHE1detail3);
    dblink.OpenView(“IC0632”, out ICSHE1detail4);
    dblink.OpenView(“IC0636”, out ICSHE1detail5);
    // dblink.OpenView(“”, out viewnull);

    AccpacCOMAPI.AccpacView[] view6 = new AccpacView[2] { ICSHE1detail1, ICSHE1detail2 };
    Object obj = (Object)view6;
    ICSHE1header.Compose(ref obj);

    AccpacCOMAPI.AccpacView[] view1 = new AccpacView[4] { ICSHE1header, ICSHE1detail3, ICSHE1detail4, ICSHE1detail5 };
    obj = (Object)view1;
    ICSHE1detail1.Compose(ref obj);

    //AccpacCOMAPI.AccpacView[] view1 = new AccpacView[4] { ICSHE1header, ICSHE1detail3, ICSHE1detail4, ICSHE1detail5 };
    ///obj = (Object)view1;
    // ICSHE1detail1.Compose(ref obj);

    AccpacCOMAPI.AccpacView[] view2 = new AccpacView[1] { ICSHE1header };
    obj = (Object)view2;
    ICSHE1detail2.Compose(ref obj);

    AccpacCOMAPI.AccpacView[] view3 = new AccpacView[1] { ICSHE1detail1 };
    obj = (Object)view3;
    ICSHE1detail3.Compose(ref obj);

    AccpacCOMAPI.AccpacView[] view4 = new AccpacView[1] { ICSHE1detail1 };
    obj = (Object)view4;
    ICSHE1detail4.Compose(ref obj);

    AccpacCOMAPI.AccpacView[] view5 = new AccpacView[1] { ICSHE1detail1 };
    obj = (Object)view5;
    ICSHE1detail5.Compose(ref obj);

    ICSHE1header.Order = 3;
    ICSHE1header.FilterSelect(“DELETED=0″, true, 3, 0);
    ICSHE1header.Order = 3;
    ICSHE1header.Order = 0;
    Object od = (Object)”3”;
    ICSHE1headerFields.get_FieldByName(“SEQUENCENO”).PutWithoutVerification(ref od);

    ICSHE1header.Init();
    temp = ICSHE1header.Exists;
    ICSHE1detail1.RecordClear();
    ICSHE1detail1.Order = 3;
    Object pro = (Object)”1″;
    //
    ICSHE1headerFields.get_FieldByName(“PROCESSCMD”).PutWithoutVerification(ref pro);
    Object desc1 = (Object)desc;
    ICSHE1headerFields.get_FieldByName(“HDRDESC”).PutWithoutVerification(ref desc1);

    ICSHE1header.Process();

    Object cust1 = (Object)cust;
    ICSHE1headerFields.get_FieldByName(“HDRDESC”).set_Value(ref cust1);
    ICSHE1headerFields.get_FieldByName(“PROCESSCMD”).PutWithoutVerification(ref pro);
    Object refere1 = (Object)refere;
    ICSHE1headerFields.get_FieldByName(“REFERENCE”).PutWithoutVerification(ref refere1);

    ICSHE1header.Process();
    Object pric = (Object)”STD”;
    ICSHE1headerFields.get_FieldByName(“PRICELIST”).set_Value(ref pric);

    temp = ICSHE1detail1.Exists;
    ICSHE1detail1.RecordClear();
    temp = ICSHE1detail1.Exists;
    ICSHE1detail1.RecordCreate(0);

    for (int i = 0; i 0)
    {
    int num1 = 0;
    int num2 = checked(session.Errors.Count – 1);
    int index = num1;
    while (index <= num2)
    {
    MessageBox.Show("The system could not post the document see eror detail:"+"\n"+
    session.Errors.Item(index).ToString(), "MD Interface", MessageBoxButtons.OK, MessageBoxIcon.Error);
    //this.lblFeedback.Text = session.Errors.Item(index).ToString(); //session.Errors.Item[index].Message.ToString();
    vException = session.Errors.Item(index).ToString();
    checked { ++index; }
    }
    }
    // MessageBox.Show(session.Errors.Item(index).ToString();, "MD Interface", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }

    }

    Manuel cuambe

    May 6, 2014 at 1:22 pm

    • The best way to get the correct compositions is to macro record the UI (I/C Shipments in this case). The compositions must match exactly including position. If you miss one, you can put in null, but better to get them all. For instance the header composes to IC0630, IC0390, IC0645 and you are missing IC0390. Similarly you are missing other compositions on the details.

      smist08

      May 7, 2014 at 4:22 am

      • thanks,
        it ws usefulll now its post to acccpac

        Manuel

        May 8, 2014 at 2:54 pm

  6. […] Introduction to the Sage 300 ERP .Net API Starting to Program the Sage 300 ERP Views in .Net Composing Views in the Sage 300 ERP .Net API Using the Sage 300 ERP View Protocols with .Net Using […]

  7. This is great information. Thanks for posting it. When you bring back a view, I wondered if there is a way that you can drill down into the child views as well. For example, if you bring back an Order (OE0520 view) can you iterate down through the detail views (OE0500 child views)

    john

    May 20, 2015 at 2:40 pm

  8. Hello,

    I’m trying to fetch the last update date from a customer group but I am getting an error on this line

    Object obj = customerGroupView.Fields.FieldByName(“AUDTDATE”).Value;

    How do I fetch that timestamp from the record?

    Egidio Caprino

    March 1, 2016 at 12:36 pm

    • The Audit Stamp fields aren’t directly exposed by the Views, however in this case you can use the DATELASTMN field to get the date last updated. If you happen to be using the Java or .Net interfaces there is a GetETag method which (although you aren’t supposed to look into it) contains the audit stamp. Unfortunately I don’t think this is exposed in the COM API.

      smist08

      March 1, 2016 at 11:37 pm

  9. Thank you for your reply.

    Is it possible to insert a new record? I tried to set all the fields value and the to call the view.RecordCreate(ACCPAC.Advantage.ViewRecordCreate.Insert) method but it does nothing.

    Egidio Caprino

    March 10, 2016 at 5:08 pm

    • Stupid me: it’s the .Insert() one. But I am getting an unclear “HRESULT E_FAIL” error…

      Egidio Caprino

      March 10, 2016 at 5:23 pm

      • Check the session’s errors collection. Chances are there is a text description in there on what is wrong (probably some sort of validation problem).

        smist08

        March 10, 2016 at 5:40 pm

      • It worked, thank you very much!

        Currently I am saving the errors in a temp file in this way

        Console.WriteLine(sageSession.Errors.GenerateErrorFile());

        There is a way to fetch the error messages directly?

        Egidio Caprino

        March 11, 2016 at 4:50 pm

      • Have a look at this article on errors: https://smist08.wordpress.com/2013/12/06/error-reporting-in-sage-300-erp/. Also if you record an empty macro it will have ad default error handler, these should show you how to access the errors programmatically.

        smist08

        March 12, 2016 at 2:23 am

  10. Thank you smist08! Do you have an example of setting a value in a field of type Date?

    Egidio Caprino

    March 18, 2016 at 10:06 am

  11. Hi Stephen,
    I am trying to create a new Purchase Order using C# and here is my code:
    View header = sage.dbLink.OpenView(“PO0620”);
    View detail1 = sage.dbLink.OpenView(“PO0630”);
    View detail2 = sage.dbLink.OpenView(“PO0610”);
    View detail3 = sage.dbLink.OpenView(“PO0632”);
    View detail4 = sage.dbLink.OpenView(“PO0619”);
    View detail5 = sage.dbLink.OpenView(“PO0623”);
    View detail6 = sage.dbLink.OpenView(“PO0633”);
    //compose views
    header.Compose(new View[] { detail2, detail1, detail3, detail4, detail5 });
    detail1.Compose(new View[] { header, detail2, detail4, null, null, detail6 });
    detail2.Compose(new View[] { header, detail1 });
    detail3.Compose(new View[] { header, detail4 });
    detail4.Compose(new View[] { header, detail2, detail1, detail3 });
    detail5.Compose(new View[] { header });
    detail6.Compose(new View[] { detail1 });

    header.RecordCreate(ViewRecordCreate.DelayKey);
    detail4.RecordCreate(ViewRecordCreate.DelayKey);
    header.RecordClear();
    header.RecordGenerate(false);

    //set default values
    header.Fields.FieldByName(“PORHSEQ”).SetValue(“1”, false); //seq
    header.Fields.FieldByName(“PROCESSCMD”).SetValue(“1”, false); //Process OIP Command
    detail4.Fields.FieldByName(“FUNCTION”).SetValue(“8”, true); //function

    //set header
    header.Fields.FieldByName(“VDCODE”).SetValue(po.vendorCode, true); //vendor code
    header.Fields.FieldByName(“DESCRIPTIO”).SetValue(po.description, false); //description
    header.Fields.FieldByName(“REFERENCE”).SetValue(po.description, false); //REFERENCE
    header.Fields.FieldByName(“STCODE”).SetValue(po.shipTo, true); //ship to location
    header.Fields.FieldByName(“BTCODE”).SetValue(po.billTo, true); //bill to location
    header.Fields.FieldByName(“VIACODE”).SetValue(po.shipVia, true); //Ship via
    foreach (var item in po.items)
    {
    //set line items
    detail1.RecordCreate(ViewRecordCreate.DelayKey);
    detail1.Fields.FieldByName(“PORLREV”).SetValue(“-1”, true); //line number
    detail1.Fields.FieldByName(“ITEMNO”).SetValue(item.item, true); //Item A1-105/0
    detail1.Fields.FieldByName(“OQORDERED”).SetValue(item.quantityOrdered, true); //Qty ordered
    detail1.Insert();
    }
    header.Insert();
    return true;

    I am getting an error on header.insert(), saying “Tax Reporting Total Amount for authority COUNTY of 19.01 does not match line sum of 0.07.”

    Can you please let me know what I am doing wrong?

    Rohith

    July 12, 2016 at 3:08 am

  12. Great article. Shame there aren’t more like this on the web. All I’m trying to do so far is create a customer in ARCUS in C#. I’ve recorded it in the UI. It

    Lee Savidge

    June 11, 2018 at 3:00 pm

  13. Great article. Shame there aren’t more like this on the web. All I’m trying to do so far is create a customer in ARCUS in C#. I’ve recorded it in the UI. Code below:

    // Open the A/R Customer View.
    ACCPAC.Advantage.View arCustomerHeader = mDBLinkCmpRW.OpenView(“AR0024”);
    ACCPAC.Advantage.View arCustomerDetail = mDBLinkCmpRW.OpenView(“AR0400”);

    // Get the XML from the post
    //CustomersT bjCustomer = ReadXml(SageARAccountPost);
    TestARCust bjTestCustomer = ReadXml(SageARAccountPost);

    arCustomerHeader.Compose(new ACCPAC.Advantage.View[] { arCustomerDetail });
    arCustomerDetail.Compose(new ACCPAC.Advantage.View[] { arCustomerHeader });

    arCustomerHeader.Fields.FieldByName(“NAMECUST”).SetValue(“Test company LS 4”, false);
    arCustomerHeader.Fields.FieldByName(“IDGRP”).SetValue(“WHL”, false);
    /*
    Not sure what to do next. A recordCreate or a RecordGenerate. Then how do I call the Process() function?
    arCustomerHeader.
    arCustomerHeader.
    */
    return “Customer Added”;

    Lee Savidge

    June 11, 2018 at 3:03 pm

  14. As has happened so many times. I post a comment after trying for ages and losing the will to live and while waiting for a reply, I get it working. Not sure if the code is correct, but it is adding a customer to ARCUS

    Lee Savidge

    June 11, 2018 at 3:45 pm

  15. Hi, Great article, one question, how do I filter a view and loop through the resulting records, for example if I want to extract all optional fields for an “X” customer, there could be one or more records on the optional fields view AR0400 for my X customer, so far if I filter the view by customer X as you show in this article and then doing a while using GoTop – it loops through all records for all customers on the AR0400 view. Thanks

    luysquez

    June 25, 2018 at 3:24 am

  16. Thank you so much for the information. I have had great success utilizing the View IDs. However I am having a major problem with any data that has Type of “BCD*10.4. For exampole, ROTO ID IC0290, Field Name: QTYONHAND. I have built a form which asks for an item number. Once the item number is submitted, it queries the db and finds this view. It then brings back the QTYONHAND value. However all it brings back are zeros (0.0000). If I try to bring a value that is Type “String”, like CNTLACCT, it brings back the correct value no problem. For that I am using String. For the BCD Type, in C#, I am using the value type Decimal. I have tried string but get met with an exception error. Any idea on how to handle these BCD value Types? Thank you!

    MasterAtreu

    September 10, 2018 at 6:55 pm

  17. Thank you very much your information was very helpful

    Murid Mehr

    April 18, 2023 at 9:27 am


Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.