Hi all,
I’ve implemented a customization to the Physical Inventory Review screen.
In short, the customization is to accomodate the odd occasion where lot tracked items have multiple batches on a single location (technically shouldn’t happen, but in a retail environment it’s impracticle to have multiple locations, so we operate on 1 shelf location).
I’ve implemented the customization in such a way that the staff can count all of the lot numbers of a particular stock item into 1 ‘fake’ lot number.
I’ve then added a new custom action to the menu, which allows them to distribute the counted line item across multiple lots.

I’ve managed to get all this done & working correctly, EXCEPT that the screen doesn’t update the details section & totals section until such time that the screen is reloaded manually.
Am I perhaps missing some call in the code to refresh the screen sections?
PS. we are on Build 22.111.0020
Any assistance is highly appreciated.


Code:
using PX.Common;
using PX.Data;
using PX.Data.BQL;
using PX.Data.BQL.Fluent;
using System;
using System.Collections;
using System.Collections.Generic;
namespace PX.Objects.IN
{
// Acuminator disable once PX1016 ExtensionDoesNotDeclareIsActiveMethod extension should be constantly active
[PXProtectedAccess]
public abstract class INPIReview_Extension : PXGraphExtension<INPIReview>
{
public override void Initialize()
{
base.Initialize();
Base.actionsFolder.AddMenuAction(distributeAllCountedPILotsAction);
}
public
SelectFrom<INPIHeader>.
InnerJoin<INSite>.On<INPIHeader.FK.Site>.
Where<MatchUserFor<INSite>>.
View PIHeader;
public PXAction<INPIHeader> distributeAllCountedPILotsAction;
[PXButton(CommitChanges = true)]
[PXUIField(DisplayName = "Distribute PI Lots", MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select)]
protected virtual IEnumerable DistributeAllCountedPILotsAction(PXAdapter adapter)
{
INPIHeader header = PIHeader.Current;
if (header == null || !header.Status.IsIn(INPIHdrStatus.Counting, INPIHdrStatus.Entering))
{
return adapter.Get();
}
Base.Save.Press();
// Acuminator disable once PX1008 LongOperationDelegateSynchronousExecution [Justification]
PXLongOperation.StartOperation(this, () =>
{
using (PXTransactionScope ts = new PXTransactionScope())
{
INPIReview docGraph = PXGraph.CreateInstance<INPIReview>();
docGraph.PIHeader.Current = docGraph.PIHeader.Search<INPIHeader.pIID>(header.PIID);
docGraph.DisableCostCalculation = true;
DistributeAllCountedLots(docGraph);
docGraph.DisableCostCalculation = false;
RecalcDemandCost();
RecalcTotals();
docGraph.PIDetail.Cache.IsDirty = true;
docGraph.Save.Press();
ts.Complete();
}
});
return adapter.Get();
}
protected void DistributeAllCountedLots(INPIReview docGraph)
{
// Find all items that's been counted under our special lot number
var details =
SelectFrom<INPIDetail>.
InnerJoin<INLocation>.On<INLocation.siteID.IsEqual<INPIDetail.siteID>.
And<INLocation.locationID.IsEqual<INPIDetail.locationID>>>.
Where<INPIDetail.pIID.IsEqual<@P.AsString>.
And<SBINLocationExt.usrPILotNumber.IsNotNull>.
And<SBINLocationExt.usrPILotNumber.IsEqual<INPIDetail.lotSerialNbr>>>.
OrderBy<INPIDetail.lineNbr.Asc>.
View.Select(docGraph, docGraph.PIHeader.Current.PIID);
foreach (PXResult<INPIDetail> row in details)
{
INPIDetail detailLine = row;
DistributeCountedLot(docGraph, detailLine);
docGraph.PIDetail.Delete(detailLine);
}
}
protected void DistributeCountedLot(INPIReview docGraph, INPIDetail countedDetailLine)
{
// Find all real lots to which the counted amount should be distributed to
var realDetailLines =
SelectFrom<INPIDetail>.
LeftJoin<INItemSite>.On<INItemSite.inventoryID.IsEqual<INPIDetail.inventoryID>.
And<INItemSite.siteID.IsEqual<INPIDetail.siteID>>>.
LeftJoin<INItemCost>.On<INItemCost.inventoryID.IsEqual<INPIDetail.inventoryID>.
And<INItemCost.curyID.IsEqual<@P.AsString>>>.
LeftJoin<INItemLotSerial>.On<INItemLotSerial.inventoryID.IsEqual<INPIDetail.inventoryID>.
And<INItemLotSerial.lotSerialNbr.IsEqual<INPIDetail.lotSerialNbr>>>.
Where<INPIDetail.pIID.IsEqual<@P.AsString>.
And<INPIDetail.locationID.IsEqual<@P.AsInt>>.
And<INItemLotSerial.lotSerialNbr.IsNotNull>>.
OrderBy<INItemLotSerial.expireDate.Desc, INItemLotSerial.lotSerialNbr.Desc>.
View.Select(docGraph, docGraph.PIHeader.Current.BaseCuryID, docGraph.PIHeader.Current.PIID, countedDetailLine.LocationID);
foreach (PXResult<INPIDetail, INItemSite, INItemCost> row in realDetailLines)
{
INPIDetail realDetailLine = row;
INItemSite.PK.StoreResult(docGraph, row);
INItemCost.PK.StoreResult(docGraph, row);
decimal countedPhysicalQty = countedDetailLine.PhysicalQty.HasValue ? (decimal)countedDetailLine.PhysicalQty : 0;
decimal realPhysicalQty = realDetailLine.PhysicalQty.HasValue ? (decimal)realDetailLine.PhysicalQty : 0;
decimal realBookQty = realDetailLine.BookQty.HasValue ? (decimal)realDetailLine.BookQty : 0;
decimal? qtyToAdd = Math.Abs(realBookQty - realPhysicalQty);
if (countedPhysicalQty >= realBookQty && (realPhysicalQty < realBookQty || !realDetailLine.PhysicalQty.HasValue))
{
docGraph.PIDetail.Cache.SetValueExt<INPIDetail.physicalQty>(realDetailLine, qtyToAdd + realPhysicalQty);
docGraph.PIDetail.Cache.MarkUpdated(realDetailLine);
countedDetailLine.PhysicalQty -= qtyToAdd;
}
else if(countedPhysicalQty > 0 && (realPhysicalQty < realBookQty || !realDetailLine.PhysicalQty.HasValue))
{
if (Math.Abs(realBookQty - realPhysicalQty) > countedPhysicalQty)
{
qtyToAdd = countedPhysicalQty;
}
docGraph.PIDetail.Cache.SetValueExt<INPIDetail.physicalQty>(realDetailLine, qtyToAdd + realPhysicalQty);
docGraph.PIDetail.Cache.MarkUpdated(realDetailLine);
countedDetailLine.PhysicalQty -= qtyToAdd;
} else if(countedDetailLine.PhysicalQty == 0m || !countedDetailLine.PhysicalQty.HasValue)
{
docGraph.PIDetail.Cache.SetValueExt<INPIDetail.physicalQty>(realDetailLine, 0m);
docGraph.PIDetail.Cache.MarkUpdated(realDetailLine);
}
}
if (countedDetailLine.PhysicalQty > 0)
{
PXResult<INPIDetail, INItemSite, INItemCost> row = (PXResult<INPIDetail, INItemSite, INItemCost>)realDetailLines.LastOrDefault_();
INPIDetail realDetailLineOldest = row;
INItemSite.PK.StoreResult(docGraph, row);
INItemCost.PK.StoreResult(docGraph, row);
docGraph.PIDetail.Cache.SetValueExt<INPIDetail.physicalQty>(realDetailLineOldest, realDetailLineOldest.PhysicalQty + countedDetailLine.PhysicalQty);
docGraph.PIDetail.Cache.MarkUpdated(realDetailLineOldest);
}
}
[PXProtectedAccess]
protected abstract bool AreKeysFieldsEntered(INPIDetail detail);
[PXProtectedAccess]
protected abstract List<INPIEntry.ProjectedTranRec> RecalcDemandCost(bool adjustmentCreation = false, bool forseDebitLinesRecalculation = false);
[PXProtectedAccess]
protected abstract void RecalcTotals();
}
}