Friday, March 26, 2021

Changing a string field to match number sequence format

 Our customer is automating their invoice processing with a 3rd party program (AxTension). The invoices they receive from vendors often have only part of the purchase order number in them. Their current PO format is PO#######. Sometimes they just receive the numeric part, '1234567', sometimes the numeric part is missing the leading zeroes, '12345'. This kicks the PO numbers out as errors and the users would have to manually match them to the PO in the system.

I was asked to write logic that will "fix" the PO numbers that come in. First of all, if the PO number is blank, I just want to return (this avoids adding a po number for NON PO invoices):

if(_purchId == strMin())
{
    return _purchId;
}


Then I had to find the number sequence format for the PurchId extended data type.

NumberSequenceReference Ref;
NumberSequenceDataType dataType;
NumberSequenceTable    numSeq;
extendedTypeId                poTypeId;

//Find the EDT id for your extended data type (mine is PurchId)
poTypeId = extendedTypeNum(PurchId);

select firstOnly RecId from dataType
            where dataType.DatatypeId == poTypeId
            join numbersequenceid from Ref
            where Ref.numbersequencedatatype == datatype.recid|
            join Format from numSeq
            where numSeq.recid == Ref.numbersequenceid;

Once I have the numberSequenceTable record and the format field, I can use it to check the purchId that was passed into the system. I also did not want to show the user any errors so I store the length of the info log before and clear it after calling in to the numCheckFormat() method run a check on my format.

When you incorporate this into your class(es), you can have the user pass in the purchId and return the newPurchId you have fixed (public static PurchId validatePurchId(_purchId)). When you run it as a job, you can create the PurchId purchId variable and set it to the purchId you want to "fix."

int lines;
purchid purchid, newpurchid;

//Save current infolog spot
lines = infolog.line();   

// Check to see if purchid matches format
if(!NumberSeq::numCheckFormat(purchID,numSeq))
{  // if check fails, fix purchid
    //Cut any infolog messages that were created
    infolog.cut(lines ? lines : 1);
    //loop through the format
    newpurchid = purchid;

    for(i=1; i <= strLen(numseq.format); i++)
    {
        if(i > strLen(_purchId))
        { // if the purchid is longer than the format, we don't need to modify it (this only adds)
            break;
        }

        f = subStr(numSeq.format,i,1);
        p = substr(purchId,i,1);
        if (f != '#')
        { // if we aren't looking for a number in the format
            if(f != p)
            { // insert the character if it is in the format but not the purchid
                
if(strLen(purchId) < strLen(numSeq.Format))
                {
                        //insert character into string
                        purchId = strIns(purchId, f, i);
                }

                //fix issue where readsoft reads the letter O as a numeric 0 
                else if (p == '0')
               { //Overwrite character in the string
                        purchId = strPoke(purchId, f, i);
                }
            }  // END if f!=p     
        } // END if f!= '#'
    } // END for

    // After inserting the characters that exist in the format but not in the purchid, check the length
    // if it's not long enough, we want to insert 0s before the existing numbers in the purchid
    if(strLen(newpurchid) < strLen(numSeq.format))
    {
        zeroPos = strNFind(numSeq.Format,'#',strLen(numSeq.Format),-strLen(numSeq.Format));
        newPurchId = strIns(newpurchid,strRep('0',strLen(numSeq.format)-strLen(newpurchId)),zeroPos+1);
    }

You could also add logic that deletes unwanted characters from the purchid if it's not in the format, but we were assuming that wasn't going to happen. If it did happen, we would actually need user interaction to figure out if it matched with a PO in the system. 

If you want an easy way to test this, you can create a dialog that allows you to enter your PurchId and your Format and ensure that the logic above will modify the PurchId the way you intend.
I added this to the job to give me a dialog:

    Dialog                  dialog = new Dialog("Purchase order number");
    DialogField             dlgpurchid,dlgFormat;

    dlgpurchid = dialog.addField(extendedTypeStr(str60),"Num to validate");
    dlgFormat  = dialog.addField(extendedTypeStr(NumberSequenceFormat));
    dlgFormat.value("PO#######"); // Set my current known number sequence as a starting point    

    if(dialog.run())
    {
        purchid = dlgPurchId.value();
        // code to select the num seq record from the table can go here
        //After selecting the numsequencetable record, you would need to store the format from your dialog
        numSeq.format = dlgFormat.value();
        // code to fix the purchid can go here
    }

This next section specifically references where to put the code in AxTension to adjust the PurchId. It has to be done on the header and the lines (as you can pass invoice number per line with AxTension).

1. Add method to AXTip_ImportPurchInvoices that will receive the purchId, fix it, and return the new purchId

2. Add code in method addInvoiceLine() where it calls the setField() for #Field_Line_PurchId. Replace it with this:
this.setField(#Field_Line_PurchId, this.YOURMETHOD(getFieldorDefaultValue(this.getField(#Field_Line_PurchId, ''), this.getField(#Field_PurchId, ''))));

3. Add code in method handlePurchInvoice(). After the if(importRunId), add this line of code:
this.setfield(#Field_PurchId,this.YOURMETHOD(this.getField(#Field_PurchId)));

4. Add code in class AXTip_ImportInvoices_ReadSoftOnline. This extends a class that extends the earlier class where you put your new method to fix the purchId. Add code in the analyzeFile() method.
Set default Id with your new logic.
defaultPurchId = this.YOURMETHOD(this.getfield(#Field_PurchId, ''));

5. In the readsoftonline class in #4, add code in the setHeaderFields() method:
change the this.setField() for the #Field_PurchId to call your new method instead
this.setfield(#Field_PurchId, this.YOURMETHOD(this.marshalStr(documentData.get_PurchId(), identifierStr(PurchId))));

Hope this helps!

-Amber