Tuesday, December 15, 2015

“WorkFlow Infrastructure Configuration Wizard” Wrong argument type for function.

I had an issue, where, when i try to open the “WorkFlow Infrastructure Configuration Wizard”. The Wizard does not come up instead I get the following error:

Error executing code: Wrong argument type for function.

(C)\Classes\WorkflowSetupWizard\new – line 75
(C)\Classes\WorkflowSetupWizard\construct – line 3
(C)\Classes\WorkflowSetupWizard\main – line 3

I compiled all these classes and there were no changes made to these classes. 

It turns out that the reason it fails is due to corrupted batch data records in the batch table from a previous run of the wizard. I deleted the three Workflow batch records from the Batch table in the AOT. And try to run the wizard again and that solved the error.

Friday, November 20, 2015

Create Intercompany Inbound load for outbound loads

The following code gives an idea on how to create inbound load for outbound load. I added this method in the WHSShipConfirm.shipConfirmAllLoads() method. This is not the full code, but tells how I take the outbound load, create an inbound load in the other company and then use the whsInventTransSumDim table to get the sales line and create lines for that.

public void InterCompanyLoadCreate(WHSLoadTable outboundLoadTable)
{
    WHSLoadTable        loadTableInterCompany;

   if (CustTable::find(OutBoundLoadTable.AccountNum).interCompanyTradingRelationActive())
    {
        changeCompany(CustTable::find(OutBoundLoadTable.AccountNum).interCompanyTradingPartnerCompanyID())
        {
        loadTableInterCompany.setLoadId();
        loadTableInterCompany.LoadDirection = WHSLoadDirection::Inbound;
        loadTableInterCompany.initFromLoadTemplateId(OutBoundLoadTable.LoadTemplateId);
        loadTableInterCompany.LoadPaysFreight = NoYes::No;
        loadTableInterCompany.InterCompanyCompanyId = OutBoundLoadTable.dataAreaId; //field created
        loadTableInterCompany.InterCompanyLoadID = OutBoundLoadTable.LoadId;//field created
        loadTableInterCompany.InterCompanyOrder = NoYes::Yes;//field created

        loadTableInterCompany.assignOriginInfo(AddRemove::Add);
        loadTableInterCompany.insert();
       }
    }

    if (loadTableInterCompany)
    {
        
    while select whsloadLine
                where whsloadLine.LoadId == OutBoundLoadTable.LoadId

    {
        select salesLine
            where salesLine.InventTransId == whsloadLine.InventTransId;

        select crossCompany WHSInventTransSumDim
            where WHSInventTransSumDim.InventTransId == salesLine.InterCompanyInventTransId;


        if (WHSInventTransSumDim)
        {
            changeCompany(loadTableInterCompany.dataAreaID)
                    this.parmLoadPlanningWorkbenchServerForm().createTmpLoadLinesPurchLines(false,WHSInventTransSumDim,counter);
            ++counter;
        }
    }
    changeCompany(loadTableInterCompany.dataAreaId)
    {
        this.parmLoadPlanningWorkbenchServerForm().addLoadLines(loadTableInterCompany);
    }
//add code to delete the created inbound load if no lines are created
   }
}

Thursday, November 5, 2015

Compile code at runtime in AX

The following will work if your code is saved in your table as a field and you are compiling the code at run time from the form that uses that field.

void clicked()
{
    XppCompiler xppCompiler = new XppCompiler();
    super();

    YourTable.write();
    if (!xppCompiler.compile(YourTable.displayExpressionAll()))
        error (xppCompiler.errorText());
    else
        info ("@SYS55301");

}

Wednesday, October 28, 2015

AX R3 - Access Denied: MCRInventSearchController

The following is taken from http://www.axdeveloperconnection.it/webapp/ (BY LANE | MONDAY, JULY 14, 2014 ( 1 YEAR 3 MONTHS ) before it went down. All credit goes to the original creator.

During our validation phase of the AX R3 upgrade project, we found some issues regarding security for our product configuration department staff.  This is likely due to developing highly customized roles for our users, to keep licensing costs low.  Even so, we expected most new menu items to go in to existing privileges for the majority of users.  In any case, you may run in to this error message:

If you trace this, you'll want to start at the EcoResProductTranslation table, update method.  In there, you'll see a static call to MCRInventSearch::updateFromProduct().

You'll need to give access to the highlighted class and method as a ServerMethod on either a privilege, duty, or role so that users can change the product names.  Given, that the Trade Item Search configuration key is enabled.

E.g.

Tuesday, October 27, 2015

Programmatically change project group for each project

//as long as we have the proj id and the group to change, we can use the inbuilt classes to change it for us.

private static void changeGroupPerReord(projtable _currentRecord, ProjgroupID _toProjGroupID)
{
    ProjGroupChange projGroupChange;
    ProjTable       projTable;
    Common          currentRecord = _currentRecord;

    if (!currentRecord)
    {
        throw error(strFmt("@SYS29104", classId2Name(classIdGet(projGroupChange))));
    }

    projGroupChange = new ProjGroupChange();

    projGroupChange.getLast();
    projGroupChange.parmProjGroupIdTo("");
    projGroupChange.parmProjInvoiceProjId("");
    projGroupChange.parmProjGroupIdTo(_toProjGroupID);
    projTable       = currentRecord;
    if (ProjWIPTable::exist(projTable.ProjId))
    {
        projGroupChange.parmProjWIPId(projTable.ProjId);
        projGroupChange.parmProjType(projTable.Type);
        projGroupChange.parmProjGroupIdFrom(projTable.ProjGroupId);
        projGroupChange.parmShowChild(false);
        projGroupChange.parmProjId("");
    }
    else
    {
        projGroupChange.parmProjId(projTable.ProjId);
        projGroupChange.parmProjType(projTable.Type);
        projGroupChange.parmProjGroupIdFrom(projTable.ProjGroupId);
        projGroupChange.parmShowChild(true);
        projGroupChange.parmProjWIPId("");
    }


    projGroupChange.run();

}

Open a CSV through a dialog box and read it

 private static void openCSVAndReadIt(Args args)
{
    #File
    IO  iO;
    Dialog dialog;
    DialogField     dialogFilename;
    ProjId projId;
    ProjGroupId fromProjGrpId;
    ProjGroupId toProjGrpId;
    FilenameOpen        filename;
    Container           record;
    boolean first = true;
    ;
    dialog = new Dialog("ProjGrp Change");

    dialogFilename = dialog.addField(extendedTypeStr(FilenameOpen));
    dialog.filenameLookupFilter(["@SYS100852","*.csv"]);

    dialog.caption("ProjGrp Change");
    dialogFilename.value(filename);
    if(!dialog.run())
        return;
    filename = dialogFilename.value();

    iO = new CommaTextIo(filename,#IO_Read);
    if (! iO || iO.status() != IO_Status::Ok)
    {
        throw error("@SYS19358");
    }
    while (iO.status() == IO_Status::Ok)
    {
        record = iO.read();// To read file
        if (record)
        {
            if (first)  //To skip header
            {
                first = false;
            }
            else
            {

                //do your logic here, in my case I will be using it change project groups as mentioned in the next blog post

                projId = conpeek(record, 1);//To peek record
                toProjGrpId = conpeek(record, 4);
                ProjGrpChangeCSV::changeGroupPerReord(ProjTable::find(projId),toProjGrpId);
            }
        }
    }
}

Monday, October 19, 2015

Another instance of CIL generation is already in progress

If AX crashes while doing a full CIL, a record is left behind that does not allow CIL again and gives the Another instance of CIL generation is already in progress.

You can delete the record from SysLastValueTable. It has UserID = '-AutoSem' and ElementName = 'Cil Generation'.

Friday, September 18, 2015

Ship Transfer Order automatically when picked

Recently, I had a requirement to ship transfer automatically when picked, but almost everything on the internet talked about automating the whole process.

This is the code I wrote, this work brilliantly with Tasklet and as long as this is added just after the MOB_postPickOrder.process() after the
case #PickWMSPickRouteOrderPrefix : // WMS Pick Order, picking Transfer Order will automatically ship it. Just pass wmsOrderTrans.inventTransRefId as a parameter to the following function.

static void PostInventTransferOrderShip(Args _args)
{
    inventTransferParmUpdate    inventTransferParmUpdate;
    ParmId                      parmID;
    InventTransferTable         inventTransferTable;
    InventTransferParmTable     inventTransferParmTable;
    InventTransferMultiShip     inventTransferMultiShip;

    select inventTransferTable
        where inventTransferTable.TransferId == "WHATEVER TRANSFER ORDER";

    parmId = RunBaseMultiParm::getSysParmId();
    inventTransferParmTable.clear();

    inventTransferParmUpdate.ParmId = parmID;
    inventTransferParmUpdate.insert();
    inventTransferParmTable.initValue();
    inventTransferParmTable.ParmId = parmId;
    inventTransferParmTable.TransferId = inventTransferTable.TransferId;
    inventTransferParmTable.ShipUpdateQty = InventTransferShipUpdateQty::All;
    inventTransferParmTable.EditLines = NoYes::Yes;
    inventTransferParmTable.AutoReceiveQty = NoYes::No;
    inventTransferParmTable.UpdateType = InventTransferUpdateType::Shipment;
    inventTransferParmTable.insert();
    //Transfer Order created above should have status as shipped
    inventTransferMultiShip = InventTransferMultiShip::construct();
    inventTransferMultiShip.runUpdate(inventTransferParmTable);
}

Monday, March 30, 2015

AX- Flush statistics for specific tables to force SQL server to regenerate execution plan

On a newly upgraded CU7 environment, I found that it was taking several minutes to find an exact match when searched through the filter about the grid.



But, if I search it in the table like this, it was instant.



Turns out, SQL server was using a bad execution plan. To fix this, the following code flushes the statistics for specific table which forces SQL server to regenerate the execution plan.


static void gpupdateStats(Args _args)
{
    setPrefix("Updating");
    WinAPIServer::updateStatsTable(tableNum(CustTable));
    WinAPIServer::updateStatsTable(tableNum(DirPartyTable));
    WinAPIServer::updateStatsTable(tableNum(LogisticsPostalAddress));
    WinAPIServer::updateStatsTable(tableNum(LogisticsElectronicAddress));
    WinAPIServer::updateStatsTable(tableNum(DirPartyLocation));
    WinAPIServer::updateStatsTable(tableNum(LogisticsLocation));
    WinAPIServer::updateStatsTable(tableNum(LogisticsAddressCountryRegion));
}
The following method is added to the WinAPIServer class:

 public static server void sqlExecStatement(str _sqlStatement)
{
    SqlStatementExecutePermission   permission;
    Connection                      connection;
    Statement                       statement;
    ;

    connection  = new Connection();
    statement   = connection.createStatement();

    connection.ttsbegin();

    permission = new SqlStatementExecutePermission(_sqlStatement);
    permission.assert();

    //BP deviation documented
    statement.executeUpdate(_sqlStatement);

    CodeAccessPermission::revertAssert();

    connection.ttscommit();
}
public static server void updateStatsTable(tableid _tableId)
{
    str         sqlStatement = "update statistics %1";
    DictTable   dictTable;
    ;

    dictTable = new DictTable(_tableId);

    sqlStatement = strfmt(sqlStatement,dictTable.name(DBBackEnd::Sql));
    setprefix(strfmt("Running sql statement : %1",sqlStatement));
    startLengthyOperation();
    WinAPIServer::sqlExecStatement(sqlStatement);
    info("Done");
    endLengthyOperation();

}

Bonus tip: to see all the uncommitted transaction on a table in sql do a query
set transaction isolation level read uncommitted 
SELECT *
  FROM INVENTTRANSFERPARMLINE
  where INVENTTRANSFERPARMLINE.parmID = 'xxx'

Thursday, March 19, 2015

AX 2012 - Send reports as an email without using outlook





I was working for a client and could not find a way to send purchase order confirmation as an email using smtp and not with outlook.

It turns out, AX sends batch mails as smtp, but single mails are sent using outlook, this is decided in SRSReportRunMailer->initMailer.

To send it as email for all invoices, all I needed to do was copy the code as in batch and paste it for non batch. Just make sure it is what they need, as it will effect all sales and purchase documents.