Wednesday, 4 June 2014

FIFO Automate “Edit Dimensions” to assign Batchnumbers (FIFO) in ax 2009

Automate “Edit Dimensions” to 


assign Batchnumbers (FIFO)

Situation:
  • All finished products and sub-assemblies are batch controlled, but the assignment of the batches is not always known in advance and in other cases mixed batches are used which is also not handled automatically. More technical: Blank receipts are not allowed, but blank issuing is.
  • For our purposes FIFO is good enough for assigning the batches to transactions after the fact.
Solution:
  • The following methods identify existing batches and transactions without batchnumber assign.
  • The positiv and negative quantities are matched and if nescessary some transactions are split.
  • (The following static methods are part of the BWBatches class)
//bw start
//Changed on 30 May 2007 by TW
/* Description:
This method with fix the Batchnumber assignment of the item identified by the parameter
In the method the is only the identification of the positive and negative quantities.
In the end the auxillary method fixBatchAssignmentItemMatch is used to match positive and negative quantities.
And the auxillary method fixBatchAssignmentItemEdit is used to edit the transactions.
*/
public static void fixBatchAssignmentItem(ItemId _itemId)
{
    InventBatch inventBatch;
    InventOnhand inventOnhand;
    InventDim   inventDim;
    InventTable item = InventTable::find(_itemId);
    Qty         remaining;
    array       positives = new array(Types::Container); //positives container: inventDimId, qty, prodDate
    Int         positivesI = 0;
    array       negatives = new array(Types::Container); //negatives container: inventDimId, qty, prodDate
    Int         negativesI = 0;
    array       matches; //matches container: pos inventDimId, neg inventDimId, qty, pos prodDate
    Int         i;
    boolean     hasInventDimIdBlank = false;
    ;
    setprefix(item.ItemId);
    //———————-find stock levels for all batch numbers——————————–
    //—————add non-zero stock levels to postive or negative lists————————
    while select inventBatch order by prodDate asc where inventBatch.itemId == item.ItemId
    {
        inventOnhand = InventOnhand::newInventBatch(inventBatch);
        remaining = inventOnhand.physicalInvent();
        if (remaining)
        {
            inventDim = null;
            inventDim.inventBatchId = inventBatch.inventBatchId;
            inventDim = InventDim::findOrCreate(inventDim);
            if (remaining<0) //add negative stock levels to negatives array
            {
                negativesI++;
                negatives.value(negativesI,[inventDim.inventDimId,remaining, inventBatch.prodDate]);
            }
            else if (remaining >0) //add positive stock levels to positives array
            {
                positivesI++;
                positives.value(positivesI,[inventDim.inventDimId,remaining, inventBatch.prodDate]);
            }
        }
    }
    //add empty dimension last
 
   inventDim = InventDim::findOrCreateBlank(false);
    inventOnhand = InventOnhand::newItemDim(item.ItemId,inventDim,InventDimParm::activeDimFlag(item.DimGroupId));
    remaining = inventOnhand.physicalInvent();
    if (remaining<0) //add negative stock levels to negatives array
    {
        hasInventDimIdBlank=true;
        negativesI++;
        negatives.value(negativesI,[inventDim.inventDimId,remaining,1\1\1900]);
    }
    else if (remaining >0) //add positive stock levels to positives array
    {
        hasInventDimIdBlank=true;
        positivesI++;
        positives.value(positivesI,[inventDim.inventDimId,remaining,1\1\1900]);
    }
    //———————-match positives and negatives——————————–
    //———————————————————————————–
    if (negativesI || hasInventDimIdBlank)//only negatives und unassigned batches are a problem
    {
        matches = BWBatches::fixBatchAssignmentItemMatch(positives,negatives,item);
        //———————-edit dimensions of transactions——————————
        //———————————————————————————–
        BWBatches::fixBatchAssignmentItemEdit(matches,item);
    }
}
//bw end

//bw start
//Changed on 22 May 2007 by TW
/* Description:
match positive and negative stocklevels of an item
_positives, _negatives: array of containers with three elements [inventDimId, qty, prodDate]
return value: array of containers with 4 elements [pos inventDimId, neg inventDimId, qty, pos prodDate]
*/
client server private static array fixBatchAssignmentItemMatch(array _positives, array _negatives, InventTable _item)
{
    int         i,j;
    Container   posC;
    Qty         posQty;
    Container   negC;
    Qty         negQty;
    Date        batchDate,batchDate2;
    array       matches = new array(Types::Container); //container elements [pos inventDimId, neg inventDimId, qty, pos prodDate]
    int         matchesI;
    ;
    for (i=1; i<=_positives.lastIndex(); i++)//run through all negative stock levels    {
        posC = _positives.value(i); //_positives container elements [inventDimId, qty, prodDate]        posQty = conpeek(posC,2);
        batchDate = conpeek(posC,3);
        if (posQty)
        {
            for (j=1; j<=_negatives.lastIndex(); j++)//try to match negative stock levels with positiv stock levels            {
                negC = _negatives.value(j); //_negatives container elements [inventDimId, qty, prodDate]                negQty = conpeek(negC,2);
                batchDate2 = conpeek(negC,3);
                if (negQty)
                {
                    if (posQty >= -negQty) //more that enough available -> use all needed (-negQty)                    {
                        matchesI++;
                        matches.value(matchesI,[conpeek(posC,1),conpeek(negC,1),-negQty, batchDate]);
                        negQty -= negQty; //reduce needed Qty (=0)                        posQty += negQty; //reduce available Qty                    }
                    else //if (posQty < -negQty) //not enough available -> use all available (posQty)                    {
                        matchesI++;
                        matches.value(matchesI,[conpeek(posC,1),conpeek(negC,1),posQty, batchDate]);
                        negQty += posQty; //reduce needed Qty                        posQty -= posQty; //reduce available Qty (=0)                    }
                    _negatives.value(j,[conpeek(negC,1),negQty,batchDate2]); //update needed value                }
            }
            _positives.value(i,[conpeek(posC,1),posQty,batchDate]); //update available value        }
    }
    //finally check if any negatives remain unmatched    for (j=1; j<=_negatives.lastIndex(); j++)
    {
        negC = _negatives.value(j);
        negQty = conpeek(negC,2);
        if (negQty)
            error(strfmt("Can not completely resolve stocklevels of %1. Please fix manually.",_item.ItemId),"",SysInfoAction_TableField::newBuffer(_item));
    }
    return matches;
}
//bw end

//bw start
//Changed on 22 May 2007 by TW
/* Description:
edit dimensions of transactions
_matches: array of containers with 4 elements [pos inventDimId, neg inventDimId, qty, pos prodDate]
*/
private static void fixBatchAssignmentItemEdit(array _matches, InventTable _item)
{
    #OCCRetryCount
    int     i;
    Qty     remaining;
    Date    batchDate;
    InventDim   inventDimPos;
    InventDim   inventDimNeg;
    InventTrans inventTrans;
    ;
    try
    {
        ttsbegin;
        for (i=1;i<=_matches.lastIndex();i++)
        {
            //_matches container elements [pos inventDimId, neg inventDimId, qty, pos prodDate]            inventDimPos = InventDim::find(conpeek(_matches.value(i),1));
            inventDimNeg = InventDim::find(conpeek(_matches.value(i),2));
            remaining = conpeek(_matches.value(i),3);
            batchDate = conpeek(_matches.value(i),4);
            remaining = -remaining; //running from the negative perspective
            //fix negative (i.e. issues with unassigned or wrongly assign batch numbers)
            //there might be many transactions with the same BatchId
            if (inventDimNeg.inventBatchId <= inventDimPos.inventBatchId)
            {  
                //the issued batch (or blank batch) will be fullfilled by newer batch
                //run through through all transactions by order by date DESCENDING
 
               while select forupdate inventTrans order by DatePhysical desc
                                        where inventTrans.ItemId == _item.ItemId
                                            && inventTrans.inventDimId == inventDimNeg.inventDimId
                {
                    //execute changes:
 
                   remaining = BWBatches::auxBatchAssignment(inventTrans,remaining,inventDimPos,inventDimNeg);
                    if (!remaining)
                        break;
                }
            }
            else
            {
                //the issued batch will be fullfilled by older batch
                //run through through all transactions by order by date ASCENDING
 
               while select forupdate inventTrans order by DatePhysical asc
                                        where inventTrans.ItemId == _item.ItemId
                                            && inventTrans.inventDimId == inventDimNeg.inventDimId
                                            && inventTrans.DatePhysical >= batchDate
                {
                    //execute changes:                    remaining = BWBatches::auxBatchAssignment(inventTrans,remaining,inventDimPos,inventDimNeg);
                    if (!remaining)
                        break;
                }
            }
            if (remaining)
                error(strfmt("Can not completely resolve stocklevels of %1. Please fix manually.",_item.ItemId),"",SysInfoAction_TableField::newBuffer(_item));
        }
        ttscommit;
    }
    catch (Exception::Deadlock)
    {
        if (xSession::currentRetryCount() >= #RetryNum)
            throw Exception::UpdateConflictNotRecovered;
        else
            retry;
    }
}
//bw end

//bw start
//Changed on 30 May 2007 by TW
/* Description:
Auxiliary method that assign a new batchnumber to a transaction and splits the transaction if nescessary
*/
private static qty auxBatchAssignment(InventTrans _inventTrans, Qty _remaining, InventDim _inventDimPos, InventDim _inventDimNeg)
{
    InventTrans inventTrans = _inventTrans;
    Qty remaining = _remaining;
    InventDim inventDimPos=_inventDimPos;
    InventDim inventDimNeg=_inventDimNeg;
    InventTransSplit    inventTransSplit;
    ;
    if (remaining>0 || inventTrans.Qty>0)
    {
        error(strfmt("auxBatchAssignment assertion: remaining (%1) and inventTrans.Qty (%2) are expected to be less than zero.",remaining,inventTrans.Qty));
        return 0;
    }
   
    if (-inventTrans.Qty <= -remaining)
    {//The transactions qty is smaller than the required quantity
     // -> use all (i.e. change dimension for entire transaction)
        
inventTrans.inventDimId = inventDimPos.inventDimId; //assign the new batch number
        inventTrans.update();
        BWBatches::auxBatchAssignmentRef(inventTrans); //change associated sales and production lines        info(strfmt("%3: %2 %4 -> %2 %5",inventTrans.InventTransId, inventTrans.Qty, inventTrans.DatePhysical,
                        inventDimNeg.inventBatchId?inventDimNeg.inventBatchId:inventDimNeg.inventDimId,inventDimPos.inventBatchId?inventDimPos.inventBatchId:inventDimPos.inventDimId));
        remaining -= inventTrans.Qty;
    }
    else if (-inventTrans.Qty > -remaining)
    {//The transactions qty is larger than the required quantity
     // -> use only part of the transaction (i.e. it is nescessary to plit the transaction!)
        inventTransSplit = InventTransSplit::newInventTransSplit(inventTrans.RecId);
        warning(strfmt(strfmt("Spliting %1: %2 %5 -> %3 + %4 %5",inventTrans.DatePhysical, inventTrans.Qty, remaining, inventTrans.Qty – remaining, (inventDimNeg.inventBatchId?inventDimNeg.inventBatchId:inventDimNeg.inventDimId))));
        inventTransSplit.parmSplitQty(inventTrans.Qty – remaining); //inventTrans will contain the desired qty        try
        {
            inventTransSplit.run();
        }
        catch (Exception::Error)
        {
            error(strfmt("Can not split transaction. Please fix stocklevels of %1 manually.",inventTrans.ItemId),"",SysInfoAction_TableField::newBuffer(InventTable::find(inventTrans.ItemId)));
            return 0;
        }
        inventTrans = InventTrans::findRecId(inventTrans.RecId,true); //reload the record (for update)        inventTrans.inventDimId = inventDimPos.inventDimId; //assign the new batch number        inventTrans.update();
        BWBatches::auxBatchAssignmentRef(inventTrans); //change associated sales and production lines        info(strfmt("%3: %2 %4 -> %2 %5",inventTrans.InventTransId, inventTrans.Qty, inventTrans.DatePhysical,
                        inventDimNeg.inventBatchId?inventDimNeg.inventBatchId:inventDimNeg.inventDimId,inventDimPos.inventBatchId?inventDimPos.inventBatchId:inventDimPos.inventDimId));
        remaining -= inventTrans.Qty;
    }
    return remaining;
}
//bw end

//bw start
//Changed on 30 May 2007 by TW
/* Description:
The auxilary method replicated the inventTrans changes to sales lines and production lines.
*/
public static void auxBatchAssignmentRef(InventTrans _inventTrans)
{
    SalesLine salesline;
    ProdBom   prodBOM
    ;
    select forupdate firstonly salesline where salesline.InventTransId == _inventTrans.InventTransId;
    if (salesline)
    {
        if (salesline.QtyOrdered == _inventTrans.Qty && salesline.InventDimId != _inventTrans.inventDimId)
        {
            salesline.InventDimId = _inventTrans.inventDimId;
            salesline.update();
        }
    }
    else
    {
        select forupdate firstonly prodBOM where prodBOM.InventTransId == _inventTrans.InventTransId;
        if (prodBOM)
        {
            if (prodBOM.QtyInventCalc == _inventTrans.Qty && prodBOM.InventDimId != _inventTrans.inventDimId)
            {
                prodBOM.InventDimId = _inventTrans.inventDimId;
                prodBOM.update();
            }
        }
    }
}
//bw end

4 comments:

  1. I like your post there is a lot of information about software testing companies, which I would like to learn, thank you for the great guide.

    ReplyDelete

Labels

#veryusefulcode (1) AIF (8) AOT Maps (1) Args (1) Ax 2009 Reports (2) AX 2012 navigation (1) Ax 2012 Interview Questions (1) AX 7 (2) AX Architecture (1) Ax Backup (1) AX Workflow (2) AX2012 (1) AX2012 R2 (1) Ax2012R3 (1) AX2012R3 Dynamics Connector Step by Step Installation and Configuration (1) AX2012R3 EP Step by Step Installation and Configuration EP R3 (1) AX2012R3 HelpServer Step by Step Installation and Configuration (1) AX2012R3 Rapid Start Connector Step by Step Installation and Configuration (1) AX2012R3 Report Server and Analysis Server Step by Step Installation and Configuration (1) AX7 (1) Best practices (1) Blocking user to enter (1) Collection Classes (1) Container (1) D365FO (3) Data Migration Frame Work ax 2012R3 (1) Deleting duplicate Record from Table – Ax2012 (1) Delivery due date notification workflow in Ax 2012 (1) Development Steps EP (1) Dimensions (1) DIXF (1) DMF in Ax 2012 R3 (1) Dynamics Ax 2012 Interview Questions (1) DYNAMICS AX 2012 INTERVIEW QUESTIONS PART 2 (1) DYNAMICS AX 7 (1) EDT relation Migration Tool (1) EP AX 2012 (1) Ep Lookup (1) Error (1) Event Handler (1) F5 (1) File Handling (4) Filter on AX2012 Listpage (1) filtering (2) financial dimensions in AX 2012 (3) form (1) images (1) Installation and Configration (4) Installation and Configuration (11) Installation of Management Reporter 2012 for AX 2012 (1) Interaction class in ax 2012 (1) Interview Question (1) Interview Questions For Ax 2012 (1) Invent DIm (1) Jobs (2) license (1) List page and form menuitem enable code (1) Methods (1) microsoft Dynamics AX 365FO (1) Number Sequence Generation – AX 2012 (5) Number Sequence2012 (1) OLTP-OLAP (1) Passing Args (1) Passing form caller and menu item caller in ax 2012 (1) Passing Multiple Records Args (1) Posting in Ax 2012 (1) POSTING PURCHASE ORDER (1) Query (1) Query Filter Form (2) Query Ranges in X++ (1) Question and Answer (1) Report (1) Reports Controller class (1) RLS in ax 2009 (1) SALES ORDER IMPORT/EXPORT FRAMEWORK BY DMF (1) Security (1) security roles (1) Security Sysqueryrangeutil (1) Sharepoint 2016 (1) SQL SERVER (1) SSRS (2) SSRS Reports Controller class (2) Table collections & Virtual company (1) Time (1) TIPS AND TRICKS (1) Web service AIF (3) Web Services on IIS (AIF) Step by Step Installation and Configuration (1) workflow ax2012 (1) Workflow installation (1) Workflow Method (3) X++ (1)