Author: tw

  • Reporting as finished items with scrap on route – multiple reporting as finished

    Situation:
    • Depending on the processing equipment we expect some percentage of the bulk to be waste e.g. 2% of 1000kg (i.e. when starting 980kg the system automatically reserves 1000kg of ingredients, 980kg are expected as good qty).
    • We have defined this as errorPct on the Workcenter, which gets copied to the route when created.
    • When processing we will start with ingredient to make 1000Kg but expect only 980Kg good qty the rest will be waste.
    • On the shop floor up to 5% might occur i.e. we might report 950 kg ( and 50 kg waste) as finished.
    • We will produce the 1000kg in two lots of 500kg and report them independently.

    What DAX does:

    1. Create a job for 980 kg
    2. Start the job for 980 kg (reserves 1000kg of ingredients)
    3. Two reporting as finished scenarios:
      1. Report it as fished in two lots of 450 kg (unexpected high scrap of 50 kg per lot)
        1. First lot: the system suggests 980 kg good qty, no error qty; we enter 450kg / 40kg (assuming the system already accounts for 10 kg error qty)
        2. Second lot: the system suggest 10 kg good qty and 0 kg error qty! (Problem #1) Expected would be either 490 kg or 530 kg (in our situation 490 kg, as we will take what comes out of the process and will not add more ingredient to make good for error qty).
        3. Although all ingredients have been used up the system still expects 80kg more product to be finished. (Problem #2) The remain status will stay at Material consumption until the job is ended. The actual remain status should be Ended.
      2. Report it as fished in two lots of 495 kg (unexpected low scrap of 5 kg per lot)
        1. First lot: the system suggests 980kg good qty, no error qty; we enter 495kg / -5kg (assuming the system already accounts for 10 kg error qty)
        2. Second lot: the system suggest 0kg good qty and 15 kg error qty! (Problem #1) Expected would be either 490 kg or 485kg. We again enter 495kg / -5kg.
        3. Even though the system realises that no more qty is to be expected as finished (i.e. remain qty = 0) the remain status will still stay at Material consumption until the job is ended. (Problem #2) 
    4. Other combinations combinations of good qty and error quantity can be applied. All result in very weird behavior.
    It is incomprehensible how anyone could use the system together with error quantities on the route. Luckily Dynamics Ax allows every customer to dive into the code an the culprits leading to Problem #1 are quickly found:
    The class ProdUpdReportFinished has a method called proposalQtyGood and proposalQtyError the returns proposed good and error quantities.  Check the code below.
    I will post the solution of Problem #2 it a future blog.
     
     
     
    static InventQty proposalQtyGood(ProdId _prodId)
    {
        ProdTable   prodTable              = ProdTable::find(_prodId);
        InventQty   routeReportedError     = prodTable.routeReportedError();
        InventQty   routeReportedTotal     = prodTable.routeReportedGood() + routeReportedError;
        InventQty   reportedFinishedGood   = prodTable.reportedFinishedGood();
        InventQty   reportedFinishedError  = prodTable.reportedFinishedError();
        InventQty   maxReportedError       = (reportedFinishedError < routeReportedError) ? routeReportedError : reportedFinishedError;
        InventQty   plannedOrder;
        ;
        if (prodTable.mandatoryRegister())
        {
            plannedOrder = InventTransSum::new2TransId().idRegistered(prodTable.InventTransId);
        }
        else
        {
    //bw start
    //Changed on 15 Mar 2007 by TW
    /* Description:
    Calculate the planned order qty relative to the qty started not relative to the qty on the route.
    */
            /* original code
            plannedOrder = routeReportedTotal;
            if (!plannedOrder)
            {
                plannedOrder= prodTable.QtyStUp;
                if (!plannedOrder)
                    plannedOrder= prodTable.QtySched;
            }
            plannedOrder = plannedOrder – reportedFinishedGood – maxReportedError;
            */
            plannedOrder = prodTable.QtyStUp – reportedFinishedGood – reportedFinishedError;
    //bw end
            if (plannedOrder < 0)
                plannedOrder = 0;
        }
        return plannedOrder;
    }
     
    static InventQty proposalQtyError(ProdId _prodId)
    {
        InventQty   qtyError;
        ProdTable _prodTable= ProdTable::find(_prodId);
        ;
        if (_prodTable.mandatoryRegister())
            return 0;
        else
        {
            qtyError = _prodTable.routeReportedError() – _prodTable.reportedFinishedError();
            if (qtyError < 0)
                qtyError = 0;
        }
    //bw start
    //Changed on 15 Mar 2007 by TW
    /* Description:
    Never suggest an error Quantity!
    The error Qty should only reflect finished product that has been compleated,
    but that has failed some sort of quality check. I.e. this value is expected to be zero.
    */
        qtyError = 0;
    //bw end
        return qtyError;
    }
     
     
  • Alternative items in production

    I do not quite understand why the alternative item functionality is only available for sales not for production. Consider the following scenario: Cream typically is bought in small volumes at around $3.00 per KG. A large order of a special product might allow us to buy larger volumes at say $2.00 per KG. The price of the standard cream should not be change for the costing of standard jobs because when the large job ceases, the small jobs need to pay for themselves.
    An easy solution would be the use of an alternative item with an independent price, but of course the the two items can be used interchangeably on the shopfloor.
    A small mod in one of the ProdUpd… classes (e.g. ProdUpdStartup.updateBOMConsumption()) activates alternative items for production. It functions by checking the availablility of the BOMItems when starting the job. Depending on the configuration it might be more appropriatly placed in another of ther ProdUpd… classes.
     
    void updateBOMConsumption()
    {
        […]
        Container                   c; //bw
        ItemId                      altItemId;    //bw
        InventDim                   altInventDim; //bw
        ;
        […]
        while(loop)
            {
                prodBOM = recListProdBOM.peek();
                if(!prodBOM.RecId)
                {
                    loop = recListProdBOM.next();
                    continue;
                }
                prodBOM.selectForUpdate(true);
    //bw start
    //Changed on 30 Apr 2007 by TW
    /* Description:
    Allow for alternative ingredients
    */
               //only if not yet consumed and no specific dimension has been assigned
               if(prodBOM.QtyInventCalc == prodBOM.RemainInventPhysical
                  && (!prodBom.InventDimId || prodBom.InventDimId == InventDim::inventDimIdBlank()))
               {
                    // use standard method to find an alternative item for the desired qty.
                    c = InventTable::findAlternativeItem(prodBom.ItemId,prodBom.InventDim(),prodBom.QtyInventCalc);
                    if (c)
                    {
                        altItemId = conpeek(c,1);
                        altInventDim = conpeek(c,2);
                        prodBom.ItemId = altItemId;
                        if (altInventDim)
                            prodBom.InventDimId = altInventDim.inventDimId;
                        else
                            prodBom.InventDimId = InventDim::inventDimIdBlank();
                    }
                }
    //bw end
                prodBOMCalc = ProdBOMCalc::newBOMCalcData(BOMCalcData,
                                                          prodBOM,
                                                          consumpProposal,
                                                          prodParmStartUp.BOMAutoConsump,
                                                          !prodBOM.ConstantReleased,
                                                          NoYes::No,
                                                          true);
               […]
    }
     
  • Allow changing of Inventory UnitId

    Dynamics Ax does not allow you to change the Inventory UnitId of an item if there is stock or if there are open transactions. We as a food business wanted to change the inventory unit of canola oil from 200L barrels to 1000L Pallecons (or even better to Ltrs, avoiding future changes of the Inventory Stock Item), but as nearly every one of our products use canola oil the will be no point in time without open transactions (production orders) and deleting and reentering all open orders is also a nuisance. Therefore I decided to make a mod which allows us to change to unitId with open transactions and existing stock. The modifications only need to be done in the update method of the InventTableModule table and are marked with //bw.
    IMPORTANT: The code here only updates InventTrans and InventSum. There are other tables with Quantities referring to the Inventory Unit such as InventJournalTrans which I do not update. This leads to inconsistencies which we can live with but you might not!
     
    void update(boolean _updatePriceDate = true)
    {
        InventTrans inventTrans;                //bw
        InventTableModule   old = this.orig();  //bw
        InventSum   inventSum;                  //bw
        boolean updPrice;
        FormRun         formRun;
        FormObjectSet   formObjSet;
        int     i;
        if (this.orig().Price     != this.Price     ||
            this.orig().Markup    != this.Markup    ||
            this.orig().PriceUnit != this.PriceUnit)
        {
            updPrice        = true;
            if (_updatePriceDate)
                this.PriceDate  = systemdateget();
        }
        ttsbegin;
    //bw start
    //Changed on 27 Apr 2007 by TW
    /* Description:
    allow update of units: modify stock level and inventJournals
    Prerequisite: a unit conversion must exist between the old and the new UnitId.
    The unit conversion will be used to adjust the quantities.
    */
        if (old.UnitId != this.UnitId && this.ModuleType==ModuleInventPurchSales::Invent) //only for inventory type
        {
            if(UnitConvert::canConvert(old.UnitId,this.UnitId,this.ItemId)//assured by validateField
                && UnitConvert::valueConvert(1,old.UnitId,this.UnitId,this.ItemId)!=1) //only bother if not one-to-one
            {
                //adjust inventTrans (this updates all transaction history of the item and might take a while)
                while select forupdate inventTrans where inventTrans.ItemId == this.ItemId
                {
                    inventTrans.Qty = UnitConvert::valueConvert(inventTrans.Qty,old.UnitId,this.UnitId,this.ItemId);
                    inventTrans.QtySettled = 
                        UnitConvert::valueConvert(inventTrans.QtySettled,old.UnitId,this.UnitId,this.ItemId);
                    inventTrans.update();
                }
                //adjust inventSum
                select forupdate firstonly inventSum where inventSum.ItemId == this.ItemId;
                if (inventSum)
                {
                    inventSum.AvailOrdered 
                        = UnitConvert::valueConvert(inventSum.AvailOrdered,old.UnitId,this.UnitId,this.ItemId);
                    inventSum.AvailPhysical 
                        = UnitConvert::valueConvert(inventSum.AvailPhysical,old.UnitId,this.UnitId,this.ItemId);
                    inventSum.OnOrder 
                        = UnitConvert::valueConvert(inventSum.OnOrder,old.UnitId,this.UnitId,this.ItemId);
                    inventSum.Ordered 
                        = UnitConvert::valueConvert(inventSum.Ordered,old.UnitId,this.UnitId,this.ItemId);
                    inventSum.ReservOrdered 
                        = UnitConvert::valueConvert(inventSum.ReservOrdered,old.UnitId,this.UnitId,this.ItemId);
                    inventSum.ReservPhysical 
                        = UnitConvert::valueConvert(inventSum.ReservPhysical,old.UnitId,this.UnitId,this.ItemId);
                    inventSum.Received 
                        = UnitConvert::valueConvert(inventSum.Received,old.UnitId,this.UnitId,this.ItemId);
                    inventSum.PostedQty 
                        = UnitConvert::valueConvert(inventSum.PostedQty,old.UnitId,this.UnitId,this.ItemId);
                    inventSum.Deducted 
                        = UnitConvert::valueConvert(inventSum.Deducted,old.UnitId,this.UnitId,this.ItemId);
                    inventSum.QuotationIssue 
                        = UnitConvert::valueConvert(inventSum.QuotationIssue,old.UnitId,this.UnitId,this.ItemId);
                    inventSum.QuotationReceipt 
                        = UnitConvert::valueConvert(inventSum.QuotationReceipt,old.UnitId,this.UnitId,this.ItemId);
                    inventSum.Registered 
                        = UnitConvert::valueConvert(inventSum.Registered,old.UnitId,this.UnitId,this.ItemId);
                    inventSum.Picked 
                        = UnitConvert::valueConvert(inventSum.Picked,old.UnitId,this.UnitId,this.ItemId);
                    inventSum.Arrived 
                        = UnitConvert::valueConvert(inventSum.Arrived,old.UnitId,this.UnitId,this.ItemId);
                    inventSum.PhysicalInvent 
                        = UnitConvert::valueConvert(inventSum.PhysicalInvent,old.UnitId,this.UnitId,this.ItemId);
                    inventSum.update();
                }
            }
        }
    //bw end
        super();

        if (updPrice)
        {
            […]
        }

        ttscommit;
    }

  • Restore Sharepoint WSS 2 Database

    I was trying to upgrade to WSS 3 and something failed so I wanted to return to WSS 2 so I reinstalled WSS 2 and tried to attached my backuped databases. I received the “Database ‘Database_Name’ already exists. (Error code:1801)” error message which I could resolve by following KB828815. But then I received the a further error message which seemed to reflect a version confilct that I could not solve. So I decided to copy the database manually with the following steps:
    1. Create a New Site (including new content database e.g. STS_new_234)
    2. Use the SQL Server management studio to attach/restore the old content database e.g. STS_old_123)
    3. Get the new SiteId from the [STS_new_234].[dbo].[Sites] e.g. {61806ab3-c940-4867-8f5a-629c0036e6bd}
    4. Copy the data of all tables (exept Sites) from the STS_old_123 to STS_new_234 and rename the field SiteId to the new SiteId in the process e.g.
      INSERT INTO [STS_new_234].[dbo].[NavNodes]
      SELECT ‘61806ab3-c940-4867-8f5a-629c0036e6bd’
            ,[WebId]
            ,[Eid]
            ,[EidParent]
            ,[NumChildren]
            ,[RankChild]
            ,[ElementType]
            ,[Url]
            ,[DocId]
            ,[Name]
            ,[DateLastModified]
            ,[NodeMetainfo]
            ,[NonNavPage]
            ,[NavSequence]
            ,[ChildOfSequence]
        FROM [STS_old_123].[dbo].[NavNodes]

      Note: The most important tables: Docs, Links, NavNodes, UserData, UserInfo, WebCat
    5. Modify the [STS_new_234].[dbo].[Webs]:
      • First make the old root a sub-site by editing FullUrl and setting ParentWebId to the new root.
      • If all is now well try and exchange the new root with the old root (again by editing FullUrl and ParentWebId accordingly).

    NOTE: The whole issue probably could have been avoided by running PRESCAN on all the existing databases BEFORE starting to upgrade.