There are cases where a purchase order is created with 100 quantity and the product receipt is done only for 90 qty. The invoicing is also done and while checking the open order list, this purchase order will be listed. There were many requests from the operations and clients to hide these purchase orders from the open order list.
Even if we tweak our BI to hide these orders, this will show up in the inventory transactions of dynamics AX/ D365 as open order. There is an option in AX to delete the remaining qty. The real pain starts where the purchase order has hundreds of line items (You will have to hire someone to remove these items). I have made a utility to tackle this situation. Sharing with you all the code and the screenshots.
This will consider the purchase orders that are linked to projects and will release the budget allocated.
There is a button created in the purchase order tab inside the purchase order form.

Button properties are shown below.

Write the below code in the click even of that button
void clicked()
{
PurchTable_ds.reread();
PurchTable_ds.refresh();
super();
PurchLine_ds.research(true);
PurchLine_ds.refresh();
}
The MenuItem is pointing to a class. So you need to create a class VV_PurchShortClose and write the below set of codes. Why I created a class because of the re-usability of the code.
Method 1 inside the Class
class VV_PurchShortClose
{
Description description;
SysOperationProgress vvProgress;
PurchTable purchTable;
#DEFINE.CurrentVersion(3)
#LOCALMACRO.CurrentList
description
}
Method 2 inside the Class
public container pack()
{
return [#CurrentVersion, #CurrentList];
}
Method 3 inside the Class
public void shortClosePO(PurchTable _purchTable)
{
PurchLine purchLine;
if (Box::okCancel("Purchase order '" + _purchTable.PurchId + "' lines remain physical will be set to 0. Do you wish to continue?", DialogButton::Cancel) == DialogButton::Ok)
{
try
{
info(strFmt("Process started %1 %2.", systemDateGet(), time2str(timeNow(), TimeSeparator::Colon, TimeFormat::AMPM)));
ttsBegin;
vvProgress= new SysOperationProgress();
vvProgress.setAnimation(#AviUpdate);
vvProgress.setCaption("Short close PO '" + _purchTable.PurchId + "'");
vvProgress.setText("Short close PO process started..");
while select forUpdate purchLine
where purchLine.PurchId == _purchTable.PurchId
&& purchLine.RemainPurchPhysical !=0
&& purchLine.RemainInventPhysical !=0
{
vvProgress.setText(strFmt("Processing line %1.", PurchLine.LineNumber));
purchLine.reread();
this.updateVendorPackingSlipTrans(purchLine);
purchLine.RemainPurchPhysical = 0;
purchLine.RemainInventPhysical = 0;
purchLine.update();
}
ttsCommit;
info(strFmt("Process ended %1 %2.", systemDateGet(), time2str(timeNow(), TimeSeparator::Colon, TimeFormat::AMPM)));
}
catch (Exception::UpdateConflict)
{
retry;
}
catch (Exception::UpdateConflictNotRecovered)
{
retry;
}
catch
{
ttsAbort;
}
}
}
Method 3 inside the Class
This method is called if the purchase order is linked to a project and if any budget is allocated for project.
public void shortCloseProjectPO(PurchTable _purchTable)
{
PurchLine purchLine;
if (Box::okCancel("Purchase order '" + _purchTable.PurchId + "' lines remain physical will be set to 0 and Project budget would be updated. Do you wish to continue?", DialogButton::Cancel) == DialogButton::Ok)
{
try
{
info(strFmt("Process started %1 %2.", systemDateGet(), time2str(timeNow(), TimeSeparator::Colon, TimeFormat::AMPM)));
ttsBegin;
vvProgress= new SysOperationProgress();
vvProgress.setAnimation(#AviUpdate);
vvProgress.setCaption("Short close PO '" + _purchTable.PurchId + "'");
vvProgress.setText("Short close PO process started..");
while select forUpdate purchLine
where purchLine.PurchId == _purchTable.PurchId
&& (purchLine.RemainPurchPhysical !=0
|| purchLine.RemainInventPhysical !=0)
{
vvProgress.setText(strFmt("Processing line %1.", PurchLine.LineNumber));
PurchLine.reread();
this.updateCommittedCost(PurchLine);
this.updateVendorPackingSlipTrans(purchLine);
PurchLine.RemainPurchPhysical = 0;
PurchLine.RemainInventPhysical = 0;
PurchLine.update();
}
ttsCommit;
info(strFmt("Process ended %1 %2.", systemDateGet(), time2str(timeNow(), TimeSeparator::Colon, TimeFormat::AMPM)));
}
catch (Exception::UpdateConflict)
{
retry;
}
catch (Exception::UpdateConflictNotRecovered)
{
retry;
}
catch
{
ttsAbort;
}
}
}
Method 4 inside the Class
public boolean unpack(container packedClass)
{
Integer version = RunBase::getVersion(packedClass);
switch (version)
{
case #CurrentVersion:
[version, #CurrentList] = packedClass;
break;
default:
return false;
}
return true;
}
Method 5 inside the Class
This is to release the committed cost
public void updateCommittedCost(PurchLine _purchLine)
{
CostControlTransCommittedCost committedCost,committedCostInsert;
while select forUpdate committedCost
where committedCost.EmplItemId == _purchLine.ItemId
&& committedCost.SourceDocumentLine == _purchLine.SourceDocumentLine
&& committedCost.Qty == _purchLine.RemainPurchPhysical
if(committedCost)
{
this.updateForecastSales(committedCost);
committedCostInsert.ProjId = committedCost.ProjId;
committedCostInsert.CategoryId = committedCost.CategoryId;
committedCostInsert.CommittedDate = committedCost.CommittedDate;
committedCostInsert.AmountMst = committedCost.AmountMst*-1;
committedCostInsert.EmplItemId = committedCostInsert.EmplItemId;
committedCostInsert.CurrencyCode = committedCost.CurrencyCode;
committedCostInsert.ProjType = committedCost.ProjType;
committedCostInsert.ProjTransType = committedCost.ProjTransType;
committedCostInsert.RefId = committedCost.RefId;
committedCostInsert.CommittedCostOrig = committedCost.CommittedCostOrig;
committedCostInsert.Qty = committedCost.Qty *-1 ;
committedCostInsert.ActivityNumber = committedCost.ActivityNumber;
committedCostInsert.Amount = committedCost.Amount*-1;
committedCostInsert.ProjTransId = committedCost.ProjTransId;
committedCostInsert.Open = NoYes::No;
committedCostInsert.Reverse = NoYes::No;
committedCostInsert.VendAccount = committedCost.VendAccount;
committedCostInsert.InventTransId = committedCost.InventTransId;
committedCostInsert.LedgerDimension = committedCost.LedgerDimension;
committedCostInsert.DefaultDimension = committedCost.DefaultDimension;
committedCostInsert.SourceDocumentLine = committedCost.SourceDocumentLine;
committedCostInsert.Worker = committedCost.Worker;
committedCostInsert.insert();
committedCost.Open = 0;
committedCost.update();
}
}
Method 6 inside the Class
public void updateForecastSales(CostControlTransCommittedCost transCommit)
{
ForecastSales forecastSales;
select forUpdate forecastSales
where forecastSales.ProjId == transCommit.ProjId
&& forecastSales.ModelId == "REMBGT"
&& forecastSales.ProjCategoryId == transCommit.CategoryId;
if(forecastSales)
{
forecastSales.CostPrice += transCommit.Amount;
forecastSales.update();
}
}
Method 7 inside the Class
public void updateVendorPackingSlipTrans(PurchLine _purchLine)
{
VendPackingSlipTrans vendPackingslipTrans;
select forUpdate vendPackingslipTrans
where vendPackingslipTrans.PurchaseLineLineNumber == _purchLine.LineNumber
&& vendPackingslipTrans.ItemId == _purchLine.ItemId
&& vendPackingslipTrans.InventDimId == _purchLine.InventDimId
&& vendPackingslipTrans.Remain != 0;
if(vendPackingslipTrans)
{
vendPackingslipTrans.Remain = 0;
vendPackingslipTrans.update();
}
}
Method 8 inside the Class
public void validatePO(PurchTable _purchTable)
{
PurchLine purchLine;
boolean ret = true;
select firstOnly purchLine
where purchLine.PurchId == _purchTable.PurchId
&& purchLine.RemainInventPhysical != 0
&& purchLine.RemainPurchPhysical != 0;
if(!purchLine)
{
ret = false;
}
if (ret)
{
this.shortClosePO(_purchTable);
}
ret = true;
select * from purchTable
where purchTable.PurchId == _purchTable.PurchId
&& purchTable.ProjId != '';
if(!purchTable)
{
ret = false;
}
if(ret)
{
this.shortCloseProjectPO(_purchTable);
}
if (!purchLine && !purchTable)
{
warning('No lines exist to short close PO.');
ret = false;
}
}
Method 9 inside the Class
public server static void main(Args _args)
{
VV_PurchShortClose shortclosePO;
PurchTable purchTable;
FormObjectSet purchTable_ds;
;
if (_args.dataset() == tableNum(PurchTable))
{
purchTable = _args.record();
if (purchTable)
{
shortclosePO = new VV_PurchShortClose();
shortclosePO.validatePO(purchTable);
purchTable_ds = purchTable.dataSource();
purchTable_ds.reread();
purchTable_ds.refresh();
}
else
{
warning("Active record not found.");
}
}
else
{
warning("Wrong parameters.");
}
}
You can skip the methods that are used to release the committed cost and budget, if you are not using the concept of project purchase order.
Once you select the purchase order and click on that button, you will be prompted with the below message.

Once you click OK, the above class will be triggered and the purchase order will be marked as either invoiced/closed based on the status.
Comments