How to read single record with BOI (C#)

SOLVED

Hello All,

Can anyone help me in getting a single record from Sage BOI script. I am using the following code but no data is returned.

int taskId = (int)oSs.InvokeMethod("nLookupTask", "AR_Customer_ui");
oSs.InvokeMethod("nSetProgram", taskId);

using (var arCustSvc = new DispatchObject(pvx.InvokeMethod("NewObject", "AR_Customer_svc", oSs.GetObject())))
{
    arCustSvc.InvokeMethod("nSetKeyValue", "ARDivisionNo$", "00");
    arCustSvc.InvokeMethod("nSetKeyValue", "CustomerNo$", "0000007");

    int returnValue = (int)arCustSvc.InvokeMethod("nFind");
    if (returnValue > 0)
    {
        int retVal = 0;

        string str1 = "", str2 = "";
        retVal = (int)arCustSvc.InvokeMethod("nGetRecord", str1, str2);
    }
}

here retVal is 1, but str1 and str2 is empty.

Is there anything i am doing wrong here?

  • 0

    I've finally found an npm module that allows me to use ActiveX from a Node.js instance.

    (See https://www.npmjs.com/package/winax, https://github.com/durs/node-activex )

    I'm using modules in Node.js, but this will similarly work on commonjs. Note, winax adds ActiveXObject on the global scope and not returning it on the WinAX object - hence the code for new ActiveXObject and not new WinAX.

    // import DotEnv from 'dotenv';
    // const dotenv = DotEnv.config();
    
    import Debug from 'debug';
    const debug = Debug('chums:lib:test');
    import WinAX from 'winax';
    
    
    
    async function glTest() {
        try {
            const {SAGE_USER, SAGE_PASSWORD, SAGE_PATH} = process.env;
    
            const oPVX = new ActiveXObject('ProvideX.script');
            oPVX.init(SAGE_PATH);
    
            const oSS = oPVX.NewObject('SY_Session');
            let retVal = 0;
            retVal = oSS.nSetUser(SAGE_USER, SAGE_PASSWORD);
            if (!retVal) {
                return Promise.reject(new Error('User not authenticated'));
            }
            debug('glTest() nSetUser', {retVal});
            retVal = oSS.nSetCompany('TST');
            debug('glTest() nSetCompany', {retVal});
            retVal = oSS.nSetDate('G/L', '20211201');
            debug('glTest() nSetDate', {retVal});
            if (!retVal) {
                return Promise.reject(new Error('User not authorized for module G/L'));
            }
    
            retVal = oSS.nSetProgram(oSS.nLookupTask('GL_Account_ui'));
            debug('glTest() nSetProgram', {retVal});
            if (!retVal) {
                return Promise.reject(new Error('User not authorized for GL_Account_ui'));
            }
            const glAccountSvc = oPVX.NewObject('GL_Account_svc', oSS);
    
            retVal = oSS.nSetProgram(oSS.nLookupTask('GL_Allocation_ui'));
            debug('glTest() nSetProgram', {retVal});
            if (!retVal) {
                return Promise.reject(new Error('User not authorized for GL_Allocation_ui'));
            }
            const glAllocationSvc = oPVX.NewObject('GL_Allocation_svc', oSS);
    
            let sAccountKey = '';
            let accountValue = new WinAX.Variant(0, 'pfloat');
    
            const FiscalYear = '2021';
            const FiscalPeriod = '12';
    
            const response = {};
            retVal = glAccountSvc.nSetIndex('KACCOUNT');
            debug('glTest() nSetIndex', {retVal});
            retVal = glAccountSvc.nFind('5214-02-00');
            debug('glTest() nFind', {retVal});
            [0,1,2,3,4].forEach(i => {
                retVal = glAccountSvc.nMoveNext();
                sAccountKey = glAccountSvc.sAccountKey;
                const row = {
                    sAccount: glAccountSvc.sAccount.valueOf(),
                    sAccountDesc: glAccountSvc.sAccountDesc.valueOf()
                };
                row.nGetAccountBalance = glAllocationSvc.nGetAccountBalance(sAccountKey, FiscalYear, FiscalPeriod, accountValue);
                row.sAccountValue = accountValue.valueOf();
    
                response[sAccountKey] = row;
                debug('forEach()', {i, retVal, sAccountKey});
            })
            glAllocationSvc.DropObject();
            glAccountSvc.DropObject();
            oSS.nCleanUp();
            oSS.DropObject();
            
            return response;
    
        } catch(err) {
            debug("glTest()", err.message);
            return Promise.reject(err);
        }
    }
    
    export const testGL = async (req, res) => {
        try {
            const result = await glTest();
            res.json({result});
        } catch(err) {
            debug("testGL()", err.message);
            res.status(500).json({error: err.message});
        }
    }
    

    Returned value: 

    {
        "result": {
            "8150000BB": {
                "sAccount": "5214-02-01",
                "sAccountDesc": "Cost of Retainer Sal:Sales-Wholesale",
                "nGetAccountBalance": 1,
                "sAccountValue": #####.##
            },
            "8150000BC": {
                "sAccount": "5214-02-02",
                "sAccountDesc": "Cost of Retainer Sal:Sales-Safety",
                "nGetAccountBalance": 1,
                "sAccountValue": #####.##
            },
            "8150000BD": {
                "sAccount": "5214-02-03",
                "sAccountDesc": "Cost of Retainer Sal:Sales-PL/OEM",
                "nGetAccountBalance": 1,
                "sAccountValue": #####.##
            },
            "8150000BE": {
                "sAccount": "5214-02-04",
                "sAccountDesc": "Cost of Retainer Sal:Sales-ASI",
                "nGetAccountBalance": 1,
                "sAccountValue": #####.##
            },
            "8150000BF": {
                "sAccount": "5214-02-05",
                "sAccountDesc": "Cost of Retainer Sal:Sales-Distrib",
                "nGetAccountBalance": 1,
                "sAccountValue": #####.##
            }
        }
    }

    Timings from the debugging console:

    [nodemon] starting `node index.js`
    [nodemon] forking
    [nodemon] child pid: 18564
      chums:index index() Server started on 8082; mode: production +0ms
    index() Server started on 8082; mode: production chums:*
      chums:index ::1 GET /test-gl  +3s
      chums:lib:test glTest() nSetUser { retVal: 1 } +0ms
      chums:lib:test glTest() nSetCompany { retVal: 1 } +9ms
      chums:lib:test glTest() nSetDate { retVal: 1 } +12ms
      chums:lib:test glTest() nSetProgram { retVal: 100006 } +109ms
      chums:lib:test glTest() nSetProgram { retVal: 100006 } +162ms
      chums:lib:test glTest() nSetIndex { retVal: 1 } +264ms
      chums:lib:test glTest() nFind { retVal: 1 } +3ms
      chums:lib:test forEach() { i: 0, retVal: 1, sAccountKey: 8150000BB } +11ms
      chums:lib:test forEach() { i: 1, retVal: 1, sAccountKey: 8150000BC } +35ms
      chums:lib:test forEach() { i: 2, retVal: 1, sAccountKey: 8150000BD } +13ms
      chums:lib:test forEach() { i: 3, retVal: 1, sAccountKey: 8150000BE } +8ms
      chums:lib:test forEach() { i: 4, retVal: 1, sAccountKey: 8150000BF } +8ms
    

    One thing to note that threw me for a bit of a loop: values from fields in the service object (and likely other objects) need to be accessed with .valueOf() to use their value in other objects.

    I ran this code from a client machine (windows 10). I haven't compared timings to my version in PHP running on the Sage100 server (Windows Server 2016) , but the response seems just as quick or quicker than on the Sage100 server.

  • FormerMember
    0 FormerMember in reply to David Speck

    Thanks David for the reminder of role permissions. Running as admin gives you super powers not everyone has.

  • FormerMember
    0 FormerMember in reply to Chums

    PHP is one of the fastest interpreters and the most popular language on the web. 

    I don't like or use Node.js, Java or .Net.

  • FormerMember
    0 FormerMember in reply to David Speck

    David,

    I removed the SetUser call and was able to list the G/L account data. This is totally anonymous access.

  • 0 in reply to FormerMember

    If you have unified authentication enabled, it uses the session's Windows account for access.

  • FormerMember
    0 FormerMember in reply to Kevin M

    Kevin,

    Sage 100 isn't even running. I have no Windows unified login set up. This is a simple ScriptBasic external BOI example.

    No one should get upset as any ProvideX programmer could do the same in BASIC.

    It's the BOI programmer's responsibility to maintain user security with their enhancements.

  • 0 in reply to FormerMember

    In a simplified VBScript using ProvideX.Script to create a new session using SY_Session, I have observed the following.

    Without SetUser but with SetProgram, trying to get the object handle fails because SetProgram fails.

    Without SetUser and without SetProgram, both NewObject and GetObject fail to return an object handle to GL_Account_Svc.

    So I don't know what is going on with your environment or code but it isn't normal.

  • FormerMember
    0 FormerMember in reply to David Speck

    David,

    This is the ScriptBasic external BOI program I'm using, Try to do the same externally in the language of your choice.

    IMPORT com.sbi
    
    oscript = COM::CREATE(:SET, "ProvideX.Script")
    COM::CBN(oscript, "Init", :CALL, "C:\\Sage\\Sage 100 Standard\\MAS90\\Home")
    osession = COM::CBN(oscript, "NewObject", :SET, "SY_Session")
    COM::CBN(osession, "nsetcompany", :CALL, "ABC")
    COM::CBN(osession, "nSetDate", :CALL, "G/L", "20211124")
    
    oacct = COM::CBN(oscript, "NewObject", :SET, "GL_Account_svc", osession)
    oalloc = COM::CBN(oscript, "NewObject", :SET, "GL_Allocation_svc", osession)
    
    COM::CBN(oacct, "nMoveFirst", :CALL)
    DO UNTIL COM::CBN(oacct, "nEOF", :GET)
      AccountKey = COM::CBN(oacct, "sAccountKey", :GET)
      Account = COM::CBN(oacct, "sAccount", :GET)
      AccountDesc = COM::CBN(oacct, "sAccountDesc", :GET)
      AccountValue = 0
      COM::CBN(oalloc, "nGetAccountBalance", :CALL, AccountKey, "2025", "05", AccountValue)
      PRINT Account & " - " & AccountDesc & STRING(40 - LEN(AccountDesc), " ") & FORMAT("%~$#,###,##0.00-~", AccountValue) & "\n"
      COM::CBN(oacct, "nMoveNext", :CALL)
    LOOP
    
    COM::CBN(oalloc, "DropObject", :CALL)
    COM::CBN(oacct, "DropObject", :CALL)
    COM::CBN(osession, "nCleanup", :CALL)
    COM::CBN(osession, "DropObject", :CALL)
    COM::RELEASE(oscript)

  • 0 in reply to FormerMember

    Found the difference.  Although my code also included SetModule.  The code that made the difference is the fact that you are using the ProvideX.Script object's NewObject whereas I was using the SY_Session object's NewObject method.  

    Personally, I always use SY_Session's method because it usually does a good job of reporting errors in LastErrorMsg for the reason a method failed, you don't get this when using ProvideX.Script.  

    Certain methods may appear to work when using the ProvideX.Script method but I would imagine it will produce unexpected behavior when a class is expecting certain variables and/or properties to be set that are normally handled when using SetUser, SetModule, and SetProgram.  I know I have seen a few cases first hand where if the module and program are not set the created class produces all kinds of errors when calling methods because it expects certain things to be set.

  • FormerMember
    0 FormerMember in reply to David Speck

    David,

    I wouldn't use this method for anything other than table data access. It's a non-bloated BOI alternative to ODBC.

    The ScriptBasic example took 0.8273508 of a second to return 208 accounts and their balances.