I've got a couple custom summary fields that I'm using to calculate totals. It seems like the lines are totaling them correctly, but given that Acumatica is not recalculating the SUM each time, but rather altering it, when I copy I end up with a total that's double.
I tried this code (which is hijacked from a different post of someone having almost exactly the same issue) but I don't think it's even running. I'm guessing I have a big gap in my understanding of what should be happening here.
public delegate void CopyOrderProcDel(SOOrder order, CopyParamFilter copyFilter);
[PXOverride]
public void CopyOrderProc(SOOrder order, CopyParamFilter copyFilter, CopyOrderProcDel del)
{
Base.RowSelecting.AddHandler<SOOrder>((sender, e) =>
{
if (e.Row == null) return;
SOOrderExt orderExt = sender.GetExtension<SOOrderExt>(e.Row);
orderExt.UsrSpeedyTotalCost = 0m;
orderExt.UsrSpeedyTotalExt2 = 0m;
});
del(order, copyFilter);
For what it's worth, I've created a menu with an action button that does fix the summary fields, but that's a very clunky solution for the users.
Thanks in advance.
If you only want to reset your custom fields. Do it this way.
public delegate void CopyOrderProcDelegate(SOOrder sourceOrder, CopyParamFilter copyFilter);
[PXOverride]
public void CopyOrderProc(SOOrder sourceOrder, CopyParamFilter copyFilter, CopyOrderProcDelegate baseMethod)
{
baseMethod(sourceOrder,copyFilter);
SOOrderExt orderExt = Base.Document.Current.GetExtension<SOOrderExt>();
orderExt.UsrSpeedyTotalCost = 0m;
orderExt.UsrSpeedyTotalExt2 = 0m;
}
Related
Good day
I have a new field inside the CROpportunity Extenstion called usrGrossProfit.
During CROpportunity's RowSelected it works out the values as needed. The problem I am having is that the users are using the create Quote button on the form and because of this never saves using the save button, The system does it for them. I have found that because of this the usrGrossProfit value is not saved.
Is there a way to force a save/Persist inside the RowSelected function?
protected void CROpportunity_RowSelected(PXCache cache, PXRowSelectedEventArgs e)
{
try
{
var row = (CROpportunity)e.Row;
if (row == null) return;
CROpportunityExt SOE = PXCache<CROpportunity>.GetExtension<CROpportunityExt>(row);
int total = 0;
decimal TotalSales = 0;
decimal TotalCost = 0;
foreach (CROpportunityProducts item in this.Base.Products.Select())
{
total++;
CROpportunityProductsExt2 itemExt = PXCache<CROpportunityProducts>.GetExtension<CROpportunityProductsExt2>(item);
TotalCost += (decimal)itemExt.UsrCostPrice.Value * item.Qty.Value;
TotalSales += (decimal)itemExt.UsrSellingprice * item.Qty.Value;
}
SOE.UsrGrossProfit = TotalSales - TotalCost;
// I added this just to try and see if it helps
cache.SetValueExt<CROpportunityExt.usrGrossProfit>(row, (decimal)(TotalSales - TotalCost));
// we are not allowed to press the save button in the event Handler
//this.Base.Save.Press();
}
catch (Exception ex)
{
PXTrace.WriteError(ex);
}
}
I have also tried to override the CreateQuote Function but this doesn't work
public delegate IEnumerable CreateQuoteDelegate(PXAdapter adapter);
[PXOverride]
public IEnumerable CreateQuote(PXAdapter adapter, CreateQuoteDelegate baseMethod)
{
this.Base.Persist();
return baseMethod(adapter);
}
I have also made a business event to open and save the Opportunity also with no luck.
No, you shouldn't save on row selected even if it was allowed. This is because row selected event gets fired several times and you don't want to be saving each time.
If you want to save on your CreateQuote override, try this:
Base.Save.PressButton(adapter)
Perhaps a better option, might be to force the user so that it's the user himself who saves. For example, you could check the state and throw an error in your override instead of saving.
if (Opportunity.Current != null && Opportunity.Cache.GetStatus(Opportunity.Current) == PXEntryStatus.Inserted)
{
throw new PXException("Please save before proceeding");
}
While it is easy in thought to just limit records to INSite.branchID = AccessInfo.branchID, the need is a bit more complex. I thought that I found a simple solution when looking at the DAC for INTran to find:
#region SiteID
public abstract class siteID : PX.Data.BQL.BqlInt.Field<siteID> { }
protected Int32? _SiteID;
[IN.SiteAvail(typeof(INTran.inventoryID), typeof(INTran.subItemID))]
[PXDefault(typeof(INRegister.siteID))]
[PXForeignReference(typeof(FK.Site))]
[InterBranchRestrictor(typeof(Where<SameOrganizationBranch<INSite.branchID, Current<INRegister.branchID>>>))]
public virtual Int32? SiteID
{
get
{
return this._SiteID;
}
set
{
this._SiteID = value;
}
}
#endregion
which has an intriguing attribute for InterBranchRestrictor. After a little digging, I found this attribute actually is used rather widely in Acumatica, but it appears to be limited to only Report Graphs and enabling the feature for Inter-Branch Transactions. Easy enough, I enabled the feature and tried an Inventory Issue again. No luck. I still could select a site id for a different branch.
So far, I only have limited control by creating a graph extension on INIssueEntry to set and validate the site ID. But what I really want is to limit the selector to only site id's of the current branch.
protected void _(Events.FieldDefaulting<INTran.siteID> e)
{
PXResultset<INSite> Results = PXSelect<INSite, Where<INSite.branchID, Equal<Current<AccessInfo.branchID>>>>.Select(Base);
if (Results.Count == 1)
{
foreach (PXResult<INSite> result in Results)
{
INSite site = result;
e.NewValue = site.SiteID;
e.Cancel = true;
}
}
}
protected void _(Events.FieldVerifying<INTran.siteID> e)
{
int? siteID = (int?)e.NewValue;
INTran row = (INTran)e.Row;
INSite site = PXSelect<INSite, Where<INSite.siteID, Equal<Required<INSite.siteID>>,
And<INSite.branchID, Equal<Current<AccessInfo.branchID>>>>>.Select(Base, siteID);
if(siteID != null && site?.SiteID == null)
{
PXUIFieldAttribute.SetError<INTran.siteID>(e.Cache, row, "Invalid Warehouse for Branch");
}
}
I really want to leverage what it seems like
[InterBranchRestrictor(typeof(Where<SameOrganizationBranch<INSite.branchID, Current<INRegister.branchID>>>))]
does, but clearly I just don't understand what it does. Alternatively, if I could add a where clause to
[IN.SiteAvail(typeof(INTran.inventoryID), typeof(INTran.subItemID))]
then I could restrict the list to the current branch that way, but I'm struggling to make that work as well. (Problems around implementing the PXForeignReference attribute in the extension needed to override the field definition.)
How can I restrict (in a manner that can be replicated efficiently throughout Acumatica) branch specific records to only site ID's of the current branch?
The InterBranchRestrictorAttribute is checking the graph to be working from a Report or the Inter-Branch Transactions feature to be turned on in the IsReportOrInterBranchFeatureEnabled method, so you need to remove this from your implementation.
You can write your own PXResrictorAttribute in the way shown in the example below:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = true)]
public class CustomRestrictorAttribute: PXRestrictorAttribute
{
public CustomRestrictorAttribute(Type where) : base(CustomRestrictorAttribute.EmptyWhere, "Restrictor Message.", Array.Empty<Type>())
{
this._interBranchWhere = where;
}
protected override BqlCommand WhereAnd(PXCache sender, PXSelectorAttribute selattr, Type Where)
{
return base.WhereAnd(sender, selattr, this._interBranchWhere);
}
private static readonly Type EmptyWhere = typeof(Where<True, Equal<True>>);
protected Type _interBranchWhere;
}
And apply it to the DAC field like below:
[SiteAvail(typeof(SOLine.inventoryID), typeof(SOLine.subItemID))]
[PXParent(typeof(Select<SOOrderSite, Where<SOOrderSite.orderType, Equal<Current<SOLine.orderType>>, And<SOOrderSite.orderNbr, Equal<Current<SOLine.orderNbr>>, And<SOOrderSite.siteID, Equal<Current2<SOLine.siteID>>>>>>), LeaveChildren = true, ParentCreate = true)]
[PXDefault(PersistingCheck = PXPersistingCheck.Nothing)]
[PXUIRequired(typeof(IIf<Where<SOLine.lineType, NotEqual<SOLineType.miscCharge>>, True, False>))]
[InterBranchRestrictor(typeof(Where2<SameOrganizationBranch<INSite.branchID, Current<SOOrder.branchID>>,
Or<Current<SOOrder.behavior>, Equal<SOBehavior.qT>>>))]
[CustomInterBranchRestrictor(typeof(Where<INSite.branchID,Equal<Current<SOOrder.branchID>>>))]
protected virtual void SOLine_SiteID_CacheAttached(PXCache cache)
{
}
I've been tasked with adding the default vendor inventory ID to grids in multiple screens in Acumatica. The field does not need to be bound and is disabled. I've gotten it as far as showing the field correctly, but only on saved records. Adding a new line and even refreshing the grid will not display the ID, I have to close the screen or switch to another record and come back before the vendor ID will display, even clicking the save button and refreshing will not cause it show. The client is using this field as a reference point so it's important it shows as soon as they select an item.
Below is the code I have for the Kit Specification screen, I need to figure out a way to make it a little more reactive, at least show properly on a refresh. I have tried using Current<> in the where statement, but that just breaks it entirely and always shows nothing.
public class INKitSpecStkDetExt : PXCacheExtension<PX.Objects.IN.INKitSpecStkDet>
{
#region VendorInventoryCode
public abstract class vendorInventoryCode: IBqlField { }
[PXDBScalar(typeof(Search2<PO.POVendorInventory.vendorInventoryID,
InnerJoin<IN.InventoryItem,
On<PO.POVendorInventory.vendorID, Equal<IN.InventoryItem.preferredVendorID>, And<PO.POVendorInventory.inventoryID, Equal<IN.InventoryItem.inventoryID>>>>,
Where<IN.InventoryItem.inventoryID,Equal<IN.INKitSpecStkDet.compInventoryID>>,
OrderBy<Desc<PO.POVendorInventory.vendorInventoryID>>>))]
[PXString(40, IsUnicode = true)]
[PXUIField(DisplayName = "Vendor Inventory Code", Enabled=false)]
public string VendorInventoryCode{ get; set; }
#endregion
}
Once I have the code nailed down it will be used in several other places. Help very much appreciated! Frustrating to have it so close and not be able to cross the finish line...
Follow up based on feedback from HB_Acumatica, working code is below for reference:
public void INKitSpecStkDet_VendorInventoryCode_FieldSelecting(PXCache sender, PXFieldSelectingEventArgs e)
{
var row = e.Row as INKitSpecStkDet;
if (e.Row != null)
{
PO.POVendorInventory vendorInventory = PXSelectReadonly2<PO.POVendorInventory,
InnerJoin<IN.InventoryItem,
On<PO.POVendorInventory.vendorID, Equal<IN.InventoryItem.preferredVendorID>, And<PO.POVendorInventory.inventoryID, Equal<IN.InventoryItem.inventoryID>>>>,
Where<IN.InventoryItem.inventoryID, Equal<Required<IN.INKitSpecStkDet.compInventoryID>>>,
OrderBy<Desc<PO.POVendorInventory.vendorInventoryID>>>.Select(Base, row.CompInventoryID);
e.ReturnValue = vendorInventory != null ? vendorInventory.VendorInventoryID : null;
}
}
PXDBScalar attribute doesn't refresh by itself. Maybe explicitly calling RaiseFieldDefaulting method will refresh it:
object newValue = null;
base.Caches[typeof(INKitSpecStkDet)].RaiseFieldDefaulting<INKitSpecStkDetExt.vendorInventoryCode>(rowINKitSpecStkDet, out newValue);
Using PXFormula instead of PXDBScalar if possible will yield better automatic refresh behavior but has it's own set of limitation as well.
If you're looking for the simplest way that works in most contexts (except when no graph is used like in reports and generic inquiry) that would be the FieldSelecting event.
You can execute BQL and return any desired value from the event. It will be called each time the field is referenced so it should update by itself.
public void INKitSpecStkDet_VendorInventoryCode_FieldSelecting(PXCache sender, PXFieldSelectingEventArgs e)
{
PO.POVendorInventory vendorInventory = PXSelectReadonly2<Po.POVendorInventory […]>.Select(Base);
e.ReturnValue = vendorInventory != null ? vendorInventory.VendorInventoryCode : null;
}
I have a custom field on the CRActivity table in which I need to store the number of records in a related table. I am trying to set the field to the value when screen CR306030 opens. The user needs to be able to override the calculated number so, I'm thinking that I need logic on the calculation to check if the custom field is > 0, in which case, don't populate the custom field and assume it's already been set.
Previously, I've tried to do this in the Field_Selecting events but, this is not working. I'm thinking I might be able to use a PXFormula attribute. Any suggestions?
I tried making a custom attribute which is close but, it won't save the values to the db. The save button enables, I can click it and it looks like it saves but, no dice. Some mundane detail, I'm sure.....
Here's my custom attribute:
public class CFCountIfZeroAttribute : PXIntAttribute
{
public override void FieldSelecting(PXCache cache, PXFieldSelectingEventArgs e)
{
if (e.Row == null)
return;
CRActivity activity = (CRActivity)e.Row;
CRActivityExt activityExt = activity.GetExtension<CRActivityExt>();
if (activityExt.usrCustomField <= 0)
{
int aggregateValue = BQLToFind();
e.ReturnValue = aggregateValue;
cache.SetValue<CRActivityExt.usrCustomField>(e.Row, aggregateValue);
cache.IsDirty = true;
}
}
}
Thanks!
I don't think I've ever done a count within a PXFormula, but what if you created a custom attribute?
[DBUIInt()]
[EditableCount()]
public virtual int? CountField
public class EditableCountAttribute
{
public override void FieldSelecting(PXCache sender, PXFieldSelectingEventArgs e)
{
if (e.Row != null)
{
//if CountField is 0, lookup the count from another table
}
}
}
This is just off the top of my head. You could pass the field you're counting into the attribute if you wanted to do this elsewhere.
I have a user field on an SOOrder DAC extension, which is a sum of some of the lines on the document (based on a field in the SOLine extension). When I add a new line, the total is updating properly. However, when I load the document for the first time, the screen is showing 0.00. I created an SOOrderEntry extension and I put code into the SOLine_RowSelecting event handler. When I load the document, it steps into the code and it looks like it is setting the fields properly, but they don't show on the screen. The same method is called from the SOLine_CuryLineAmt_FieldUpdated, and that works just fine. Here is the code I'm using:
public class SOOrderEntryExt : PXGraphExtension<SOOrderEntry>
{
//Used to prevent recursive calls in RowSelecting
bool _isCalculating = false;
protected virtual void SOLine_RowSelecting(PXCache cache, PXRowSelectingEventArgs e)
{
var row = e.Row as SOLine;
if (row == null) return;
using (new PXConnectionScope())
{
if (!_isCalculating)
CalcTotals();
}
}
protected virtual void SOLine_CuryLineAmt_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e)
{
if (!_isCalculating)
CalcTotals();
}
public void CalcTotals()
{
SOOrder order = Base.CurrentDocument.Select();
if (order == null) return;
_isCalculating = true;
var orderExt = order.GetExtension<SOOrderExt>();
orderExt.UsrMyCustomField = 0m;
//Get totals
foreach (SOLine lineSum in Base.Transactions.Select())
{
var lineSumExt = lineSum.GetExtension<SOLineExt>();
if (lineSumExt.UsrMyCondition)
orderExt.UsrMyCustomField += lineSum.CuryLineAmt;
}
_isCalculating = false;
}
}
RowSelected is called on each callback to select the data. There's no need to re-calculate on FieldUpdated event too because RowSelected will be called when updating records. Therefore consider removing SOLine_CuryLineAmt_FieldUpdated
You have the RowSelected event declared for SOLine DAC. The event then selects all SOLine to compute the totals. This amount to when selecting one of the Detail compute the total of all Detail, that smells lack a recursive pattern. Therefore consider declaring RowSelected on the Master document which is SOOrder in this case and remove all the workarounds you have to break recursion.
There's no null check in computations. Acumatica DAC fields are nullable. With your code you can end up in situation where you add null to a number which would results in type violation at runtime. Therefore consider checking if CuryLineAmt is null before using it's value to compute the total.
You are accumulating the total in the UsrMyCustomField DAC field
using the += addition assignment operator. It works but I would
advise against that. The DAC fields aren't meant as register for
computations or temporary value place-holder. Therefore consider
accumulating the total in a local variable and assign only the final
computed value to the DAC field.
Code to compute a total with all these points considered:
public class SOOrderEntryExt : PXGraphExtension<SOOrderEntry>
{
public void SOOrder_RowSelected(PXCache cache, PXRowSelectedEventArgs e)
{
SOOrder order = e.Row as SOOrder;
if (order != null)
{
SOOrderExt orderExt = order.GetExtension<SOOrderExt>();
if (orderExt != null)
{
decimal total = 0M;
foreach (SOLine line in Base.Transactions.Select())
{
total += line.CuryLineAmt.HasValue ? line.CuryLineAmt.Value : 0M;
}
orderExt.UsrMyCustomField = total;
}
}
}
}