How to pass line item custom field value to sales order from opportunity? - acumatica

I have a custom line number field in opportunity product tab for customer to re-sequence the selected products and the grid is sorted on custom field value.
I am trying to pass the value from opportunity to sales order which also having a similar field.
the following code i have tried and it did not work
PXGraph.InstanceCreated.AddHandler<SOOrderEntry>((graph) =>
{
graph.RowUpdated.AddHandler<SOLine>((cache, args) =>
{
CROpportunityProducts product = (adapter.View.Graph as OpportunityMaint).Products.Current;
CROpportunityProductsExtNV productext = PXCache<CROpportunityProducts>.GetExtension<CROpportunityProductsExtNV>(product);
SOLine soline = (SOLine)args.Row;
SOLineExtNV solineext = PXCache<SOLine>.GetExtension<SOLineExtNV>(soline);
solineext.UsrLineNo = productext.UsrLineNo;
});
});
The following piece of code returns same value for all line numbers

You can implement RowInserting Event handler as below:
graph.RowInserting.AddHandler<SOLine>((cache, args) =>
{
var soLine = (SOLine)args.Row;
CROpportunityProducts opProduct = PXResult<CROpportunityProducts>.Current;
SOLineExtNV soLineExt = PXCache<SOLine>.GetExtension<SOLineExtNV>(soLine);
CROpportunityProductsExtNV opProductExt = PXCache<CROpportunityProducts>.GetExtension<CROpportunityProductsExtNV>(opProduct);
soLineExt.UsrLineNo = opProductExt.UsrLineNo;
});

wish they could split up the call to create the order and the call to insert the lines to make it easier to customize. We have done something similar. Here is a sample from what I tested using a graph extension and overriding the DoCreateSalesOrder call in the opportunitymaint graph. (This assumes the select on products is the same order the transaction on the sales order were inserted. I am sure there could be a better answer, but this is an example I have handy.)
public class CROpportunityMaintExtNV : PXGraphExtension<OpportunityMaint>
{
[PXOverride]
public virtual void DoCreateSalesOrder(Action del)
{
try
{
del();
}
catch (PXRedirectRequiredException redirect)
{
var products = this.Products.Select().ToArray();
int rowCntr = 0;
foreach (SOLine soLine in ((SOOrderEntry)redirect.Graph).Transactions.Select())
{
// Assumes inserted rows in same order as products listed (default should be the key)
//Current product
CROpportunityProducts currentProduct = products[rowCntr];
var productExtension = currentProduct.GetExtension<CROpportunityProductsExtNV>();
((SOOrderEntry) redirect.Graph).Transactions.Cache.SetValueExt<SOLineExtNV.usrLineNo>(soLine, productExtension.UsrLineNo);
rowCntr++;
}
throw redirect;
}
}
}
The problem you had with your code is the Current product was always the same which resulted in the same value.

Related

How to update Cost for a stock item

Is possible to call Update Cost process, in the stock item maintenance screen? In my case, my custom code creates BOM records, then executes BOM cost roll. Last step is to execute Update Cost, in order to push the pending cost data. I notice the Update Cost process is actually a method in the base graph, not the stock item maintenance graph. I am unsure how to execute the action button in this case.
Because the Update Cost action on Stock Items takes an additional parameter we need to figure out how to access an action with the following:
public PXAction<InventoryItem> action;
[PXUIField(DisplayName = "Actions", MapEnableRights = PXCacheRights.Select)]
[PXButton(SpecialType = PXSpecialButtonType.ActionsFolder)]
protected virtual IEnumerable Action(PXAdapter adapter,
[PXInt]
[PXIntList(new int[] { 1, 2, 3 }, new string[]
{
"Update Price",
"Update Cost",
"View Restriction Group"
})]
int? actionID
)
{
// button action code here...
}
Looking at examples in Acumatica I was able to find out how to provide parameters to a PXAction that takes more than just the adapter. In our case we need to provide actionID a value.
To get this working we need to work with a new PXAdapter instance and pass this to the action. Here is a working sample:
var itemMaint = CreateInstance<InventoryItemMaint>();
var inventoryID = 151;
itemMaint.Item.Current = itemMaint.Item.Search<InventoryItem.inventoryID>(inventoryID);
// Must use this dummy view - actual view will not work
var view = PXView.Dummy.For<InventoryItem>(itemMaint);
var itemAdapter = new PXAdapter(view)
{
Arguments = new Dictionary<string, object> { { "actionID", 2 } }
};
itemMaint.action.PressButton(itemAdapter);
Putting it all together which is what I used to test the example we end up with something like this...
Coll BOM Cost (cost roll)
Update pending cost (cost roll)
Update Cost (stock item)
Working Example:
PXLongOperation.StartOperation(this, () =>
{
var costRoll = CreateInstance<BOMCostRoll>();
costRoll.Settings.Current.SnglMlti = RollupSettings.SelectOptSM.Multi;
costRoll.Settings.Current.BOMID = "00000000000041";
costRoll.Settings.Current.RevisionID = "A";
// ROLL COSTS
costRoll.start.Press();
var inventoryIds = new HashSet<int>();
foreach (AMBomCost costRollRec in costRoll.BomCostRecs.Select())
{
inventoryIds.Add(costRollRec.InventoryID.GetValueOrDefault());
}
// UPDATE PENDING
costRoll.updpnd.Press();
var itemMaint = CreateInstance<InventoryItemMaint>();
foreach (var inventoryId in inventoryIds)
{
itemMaint.Clear();
itemMaint.Item.Current = itemMaint.Item.Search<InventoryItem.inventoryID>(inventoryId);
if (itemMaint.Item.Current == null)
{
continue;
}
// Must use this dummy view - actual view will not work
var view = PXView.Dummy.For<InventoryItem>(itemMaint);
var itemAdapter = new PXAdapter(view)
{
Arguments = new Dictionary<string, object> {{"actionID", 2}}
};
// UPDATE COST
itemMaint.action.PressButton(itemAdapter);
}
});

Sales Price Updating Every Other Time

I have extended the SOOrderEntry graph and added the following code in order to update another line on the same sales order that is related to the current line that is being updated:
protected virtual void SOLine_RowUpdating(PXCache cache, PXRowUpdatingEventArgs e)
{
if (e.NewRow == null)
{
return;
}
SOLine soLine = (SOLine)e.NewRow;
SOLine relatedLine = Base.Transactions.Search<SOLine.inventoryID>
(456);
if (relatedLine != null)
{
relatedLine.Qty = soLine.Qty;
relatedLine.CuryUnitPrice = 24.20;
Base.Transactions.Update(relatedLine);
Base.Transactions.View.RequestRefresh();
}
}
When I try to test this by updating the Qty on the current line, the Unit Price on the related line only updates every other time that I update the Qty. The related item is a Non-stock item where the current item is a Stock Item.
I'm doing this in a Sales Demo environment on 18.102.0048
I tried this but, now the Extended Price is always 0.00:
protected virtual void SOLine_RowUpdating(PXCache cache, PXRowUpdatingEventArgs e)
{
if (e.NewRow == null)
{
return;
}
SOLine soLine = (SOLine)e.NewRow;
SOLine relatedLine = Base.Transactions.Search<SOLine.inventoryID>
(456);
if (relatedLine != null)
{
SOLine oldRelatedLine = PXCache<SOLine>.CreateCopy(relatedLine);
relatedLine.Qty = soLine.Qty;
relatedLine.CuryUnitPrice = 24.20;
Base.Transactions.Cache.RaiseRowUpdated(relatedLine, oldRelatedLine);
Base.Transactions.Update(relatedLine);
Base.Transactions.View.RequestRefresh();
}
}
When a field calculated value depends on another field value it's sometimes required to trigger events for the value to be recalculated.
In some cases it only requires firing the event for the field that you modified. Assigning values using the C# assignment operator (=) or the SetValue method will not trigger the events handler, SetValueExt method will:
// Doesn't trigger events
relatedLine.Qty = soLine.Qty;
cache.SetValue<SOLine.qty>(relatedLine, soLine.Qty);
// This will trigger events
cache.SetValueExt<SOLine.qty>(relatedLine, soLine.Qty);
There are some cases where you need to fire events for the whole row too. You can use the RaiseXxxYyy methods to do that. I used Current in the example but you might have to adapt it if Current is not the row being modified:
SOLine oldRowCopy = PXCache<SOLine>.CreateCopy(Base.Transactions.Current);
Base.Transactions.Current.Qty = soLine.Qty;
Base.Transactions.Cache.RaiseRowUpdated(Base.Transactions.Current, oldRowCopy);
EDIT:
Looking at updated question, it might not make much of a difference but I suggest you switch the order of these 2 lines:
Base.Transactions.Cache.RaiseRowUpdated(relatedLine, oldRelatedLine);
Base.Transactions.Update(relatedLine);
Like this:
// You want the value in Cache to be updated
Base.Transactions.Update(relatedLine);
// After Cache value is set you want to raise the events
Base.Transactions.Cache.RaiseRowUpdated(relatedLine, oldRelatedLine);
Thanks to HB_ACUMATICA, here's the code that resolved this:
protected virtual void SOLine_RowUpdating(PXCache cache, PXRowUpdatingEventArgs e)
{
if (e.NewRow == null)
{
return;
}
SOLine soLine = (SOLine)e.NewRow;
SOLine relatedLine = Base.Transactions.Search<SOLine.inventoryID>(456);
if (relatedLine != null)
{
SOLine oldRelatedLine = PXCache<SOLine>.CreateCopy(relatedLine);
relatedLine.Qty = soLine.Qty;
relatedLine.CuryUnitPrice = 24.20;
object outPrice = 0.01;
outPrice = (relatedLine.CuryUnitPrice * relatedLine.Qty);
cache.SetValueExt<SOLine.curyExtPrice>(relatedLine, outPrice);
Base.Transactions.Cache.RaiseRowUpdated(relatedLine, oldRelatedLine);
Base.Transactions.Update(relatedLine);
Base.Transactions.View.RequestRefresh();
}
}

Change String length or decimal precision of field attribute dynamically

I'm trying to use setup data from one table to allow me to format fields on the fly / dynamically. I know I can change field names and visibility based on the PXUIFieldAttribute class, but changing the precision or string length is a bit trickier, obviously. From the research I've done, I've come up with the following example code that seems like it should work - but I get the error:
"Unable to cast object of type 'PX.Data.PXUIFieldAttribute' to type 'PX.Data.PXDBDecimalAttribute'.
I don't see why this is occurring...
protected virtual void xTACOpenSourceDetail_RowSelected(PXCache sender, PXRowSelectedEventArgs e)
{
var osd = (PXCache)sender;
foreach (PXDBDecimalAttribute attribute in this.Caches<xTACOpenSourceDetail>().GetAttributes("Number1"))
{
PXDBDecimalAttribute someAttribute = attribute as PXDBDecimalAttribute;
if (someAttribute != null)
{
someAttribute.DBProperties._precision = 4;
}
}
}
I just tried the below code in sales order screen and it seems working!
var props = typeof(SOOrder).GetProperties().Where(prop => Attribute.IsDefined(prop, typeof(PXDecimalAttribute)));
foreach (System.Reflection.PropertyInfo item in props)
{
PXDecimalAttribute.SetPrecision(this.Base.Caches[typeof(SOOrder)], item.Name, 1);
}
You might need to change this to match your DAC.

Error: Reference Nbr. cannot be found in the system.."}

I customized the ARPaymentEntry in which it creates a Journal Voucher Entry with created Credit Memo, it retrieves the Credit Memo applies the open invoice that is also applied in the current payment. when I create the instance to call the Credit Memo and add the Invoice in ARAdjust table, an error occurs when trying to insert it, giving a Reference Nbr cannot be found in the system, although when I'm trying to manually applying it I could see the open invoice.
public void ReleaseCreditMemo(string refNbr)
{
try
{
ARPaymentEntry docGraph = PXGraph.CreateInstance<ARPaymentEntry>();
List<ARRegister> list = new List<ARRegister>();
ARPayment payment;
ARRegister invoice = PXSelect<ARRegister, Where<ARRegister.docType, Equal<Required<ARRegister.docType>>, And<ARRegister.refNbr, Equal<Required<ARRegister.refNbr>>>>>.Select(docGraph, ARInvoiceType.CreditMemo, refNbr);
docGraph.Document.Current = PXSelect<ARPayment, Where<ARPayment.docType, Equal<Required<ARPayment.docType>>, And<ARPayment.refNbr, Equal<Required<ARPayment.refNbr>>>>>.Select(docGraph, ARInvoiceType.CreditMemo, refNbr);
payment = docGraph.Document.Current;
list.Add(payment);
foreach (ISARWhTax item in ARWhLine.Select())
{
decimal? _CuryAdjgAmt = payment.CuryOrigDocAmt > invoice.CuryDocBal ? invoice.CuryDocBal : payment.CuryOrigDocAmt;
decimal? _CuryAdjgDiscAmt = payment.CuryOrigDocAmt > invoice.CuryDocBal ? 0m : invoice.CuryDiscBal;
ARAdjust adj = new ARAdjust();
adj.AdjdBranchID = item.AdjdBranchID;
adj.AdjdDocType = ARInvoiceType.Invoice;
adj.AdjdRefNbr = item.AdjdRefNbr;
adj.AdjdCustomerID = item.CustomerID;
adj.AdjdDocDate = invoice.DocDate;
adj.CuryAdjgAmt = _CuryAdjgAmt;
adj.CuryAdjdDiscAmt = _CuryAdjgDiscAmt;
if (docGraph.Document.Current.CuryUnappliedBal == 0m && docGraph.Document.Current.CuryOrigDocAmt > 0m)
{
throw new PXLoadInvoiceException();
}
//This line code below OCCURS THE ERROR
docGraph.Adjustments.Insert(adj);
}
docGraph.Save.Press();
PXLongOperation.StartOperation(docGraph, delegate() { ARDocumentRelease.ReleaseDoc(list, false); });
}
catch (Exception ex)
{
throw new PXException(ex.Message);
}
}
I would look at the selector of the field causing the error ("Reference Nbr.") as having a selector on a field will validate the entered value to the selector's select statement (unless validatevalue=false for the selector). Maybe the selector will give you some pointers as to what is missing or causing the validation to fail.
I figured it out it that after the code below executes it does not immediately updates the View. So what I did is to execute my code at ARPayment_RowSelected event with a conditional statement if the document is released.
PXLongOperation.StartOperation(this.Base, delegate() { ARDocumentRelease.ReleaseDoc(list, false); });

Get order information such as inventory id, customer id and location id in a sales order during Business Logic Customization

As I mentioned in my previous question( How to customize the sales order process to trigger an automatic "adding contract" process when sales order is successfully completed), I need to automatically add a contract for each of some particular products that are in a sales order after this sales order is added.
I have learned adding contract part in previous questions,thanks to #Gabriel's response, and now I need to know how to get those order information such as inventory id in order items, customer id and location id in a sales order business logic (screen SO301000). Would anybody please kindly provide me some sample code?
Thanks.
Now I seem to be able to get customer id and location id from code:
SOOrder SalesOrder = (SOOrder)Base.Caches[typeof(SOOrder)].Current;
int customer_id = SalesOrder.CustomerID;
int Location ID = SalesOrder.CustomerLocationID;
....
but I still need to find out how to iterate through product list (SOLine item) in the order...the code I found as below (it was an example for implementing a SO release operation) in T200 training PDF seems too old and not helpful to me:
public static void ReleaseOrder(SalesOrder order)
{
SalesOrderEntry graph = PXGraph.CreateInstance<SalesOrderEntry>();
graph.Orders.Current = order;
foreach (OrderLine line in graph.OrderDetails.Select())
{
ProductQty productQty = new ProductQty();
productQty.ProductID = line.ProductID;
productQty.AvailQty = -line.OrderQty;
graph.Stock.Insert(productQty);
}
order.ShippedDate = graph.Accessinfo.BusinessDate;
order.Status = OrderStatus.Completed;
graph.Orders.Update(order);
graph.Persist();
}
This is how you have to loop through the lines while you override persist in your extension
foreach (SOLine line in this.Base.Transactions.Select())
{
}
What you are doing here is you are looping through the valid records in the cache by executing select method. For this you have to find the view definition (Transactions) related to the DAC(SOLine) from the Base BLC definitions.
I figured out how to do it and the below is the code what I have and it's working for me so far - you can see I use "SOLine_RowPersisted" instead of customizing "Persist()" as I did before.
protected virtual void SOLine_RowPersisted(PXCache sender,PXRowPersistedEventArgs e)
{
if (e.TranStatus == PXTranStatus.Completed)
{
if ((e.Operation & PXDBOperation.Command) == PXDBOperation.Insert)
{
SOOrder SalesOrder = (SOOrder)Base.Caches[typeof(SOOrder)].Current;
SOLine line = (SOLine)e.Row;
// Lookup inventory
InventoryItem template = PXSelect<InventoryItem,
Where<InventoryItem.inventoryID, Equal<Required<InventoryItem.inventoryID>>>>
.Select(Base, line.InventoryID);
if (template.InventoryCD == null)
{
throw new PXException("Inventory CD can not be blank.");
}
if (template.InventoryCD.StartsWith("AAABBB"))
{
ContractMaint contractMaint = PXGraph.CreateInstance<ContractMaint>();
CTBillEngine engine = PXGraph.CreateInstance<CTBillEngine>();
DateTime StartDate = DateTime.Now;
........
string contractCD = ......
Contract contract = SetupActivateContract(contractMaint, contractCD, StartDate , line.CustomerID, SalesOrder.CustomerLocationID, engine);
}
} else if ((e.Operation & PXDBOperation.Command) == PXDBOperation.Delete)
{
.....
} else if ((e.Operation & PXDBOperation.Command) == PXDBOperation.Update)
{
....
}
}
}

Resources