Question

Details section not updating visuall until screen is refreshed

  • 21 September 2022
  • 6 replies
  • 357 views

Userlevel 4
Badge

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.

Screen before & After clicking the Distribute PI Lots button

 

Screen after manually reloading it

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();
}
}

 


6 replies

Userlevel 6
Badge +4

Right before your action ends, try refreshing the view like this:

Base.PIDetail.View.Clear();
Base.PIDetail.View.RequestRefresh();

 

This assumes the view you want to refresh in your base graph is PIDetail.  Also, keep in mind that you want it in your current graph instance, i.e. NOT docGraph.

Userlevel 4
Badge

Hi @Brian Stevens ,

Thank you very much for the super quick response!

I tried to refresh both the detail & header, and neither seemed to have made a change. Am I perhaps putting it in the wrong place?

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();
}
});

Base.PIDetail.View.Clear();
Base.PIDetail.View.RequestRefresh();
Base.PIHeader.View.Clear();
Base.PIHeader.View.RequestRefresh();

return adapter.Get();
}

 

Userlevel 6
Badge +4

Nope.  That’s what I was thinking.  Try removing the PXLongOperation.  It complains that it is running synchronously anyway per your Acuminator disable, so I’m pretty sure it’s pointless in this application.

Userlevel 4
Badge

Nope.  That’s what I was thinking.  Try removing the PXLongOperation.  It complains that it is running synchronously anyway per your Acuminator disable, so I’m pretty sure it’s pointless in this application.

Hey @Brian Stevens ,

Thank you very much for your assistance thus far! It is really appreciated.

I gave that a shot, and it did remove the deleted line (your see somewhere in my original code snippet I delete the fake counted row), but the other lines of the screen that where updated didn’t update onn screen accordingly. Also, what happens now on page refresh is it says that your unsaved changes will be lost. I just clicked on OK without saving, and then the other lines and total updated. 

This is quite a strange one indeed 🤔

I’ve essentially copied what the core code does on the SetNotEnteredToZero action, so the thought was that it would behave in the same way. Below is the exerpt from the core code, could it be in the way I’m extending / implementing the extension? Reason I ask is, as you can see in core code, they’re able to call functions on the docGraph like, e.g. docgraph.RecalcDemandCost();, whereas when I try that, I’m faced with protected function warnings, rightly so. But shy from that, I can’t think of any other reasons why it’s not working as expected for me 🤔

protected virtual IEnumerable SetNotEnteredToZero(PXAdapter adapter)
{
INPIHeader header = PIHeader.Current;
if (header == null || header.Status.IsNotIn(INPIHdrStatus.Counting, INPIHdrStatus.Entering))
return adapter.Get();

Save.Press();

PXLongOperation.StartOperation(this, () =>
{
using (PXTransactionScope ts = new PXTransactionScope())
{
INPIReview docgraph = PXGraph.CreateInstance<INPIReview>();
docgraph.PIHeader.Current = docgraph.PIHeader.Search<INPIHeader.pIID>(header.PIID);

var details =
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>>>.
Where<INPIDetail.pIID.IsEqual<@P.AsString>>.
OrderBy<INPIDetail.lineNbr.Asc>.
View.Select(docgraph, header.BaseCuryID, header.PIID);

docgraph.DisableCostCalculation = true;
foreach (PXResult<INPIDetail, INItemSite, INItemCost> row in details)
{
INPIDetail detail = row;
if (detail.Status != INPIDetStatus.NotEntered || !docgraph.AreKeysFieldsEntered(detail))
continue;

INItemSite.PK.StoreResult(docgraph, row);
INItemCost.PK.StoreResult(docgraph, row);

docgraph.PIDetail.Cache.SetValueExt<INPIDetail.physicalQty>(detail, 0m);
docgraph.PIDetail.Cache.MarkUpdated(detail);
}
docgraph.DisableCostCalculation = false;

docgraph.RecalcDemandCost();
docgraph.RecalcTotals();
docgraph.PIDetail.Cache.IsDirty = true;
docgraph.Save.Press();

ts.Complete();
}
});

return adapter.Get();
}

 

Userlevel 6
Badge +4

I’ve done some work in Physical Inventory, and I understand your pain.  In Physical Inventory Review, I run an action to add all items with previous days’ transactions to the count.  I delete specific records when needed, lock more items/locations, insert PIDetail records, and update PIHeader.  No long operation.  At the end, I just close out the action with:

                }
                Base.Save.Press();

            }

            return adapter.Get();
        }

Seems to be enough or me, and you do the same exact return.

Perhaps, see if Base.Save.Press() at the end changes anything for you before the graph refreshes.  Otherwise, you could flat out force a reload by re-finding your PI in docgraph and then redirect to the graph.  Not “pretty” and the screen gets an odd flash from the reload, but I’ve used the approach in a pinch until I could figure it out.

throw new PXRedirectRequiredException(docgraph, false, "PI Review");

 

or maybe more cleanly, just Base.PIHeader.Current = ...

 

Unfortunately, I’m getting slammed at work right now.  Ping me again in a couple of days if you don’t have an answer, and I’ll try to dig deeper if I can get some time to look into it.

Userlevel 4
Badge

Awesome, thanks again! I will give these ideas a go tomorrow 👌🏻

Reply


About Acumatica ERP system
Acumatica Cloud ERP provides the best business management solution for transforming your company to thrive in the new digital economy. Built on a future-proof platform with open architecture for rapid integrations, scalability, and ease of use, Acumatica delivers unparalleled value to small and midmarket organizations. Connected Business. Delivered.
© 2008 — 2024  Acumatica, Inc. All rights reserved