Creating Order not setting CustomerID in Inserted Record - acumatica

I'm not sure why this is being difficult, but I can't seem to insert a new SOOrder with a customer ID in the SOOrder page. I'm trying to do this programmatically creating a new instance of SOOrder and using that to insert and save the new order.
If I insert a fresh SOOrder object into the (Current)Document view, then set the customer ID, run an update, and try to save, I get the error that CustomerID cannot be null. Looking into the object during runtime after the update is executed, it shows the Inserted row with a CustomerID of null:
...
SOOrder order = new SOOrder();
SOOrderEntry graph = PXGraph.CreateInstance<SOOrderEntry>();
order.OrderType = "SO";
graph.CurrentDocument.Insert(order);
order.CustomerID = project.CustomerID;
graph.CurrentDocument.Update(order);
graph.Actions.PressSave();
...
If I try to first set the CustomerID in an SOOrder object, then insert that object into the (Current)Document view, it gives me a more complex error in relation to the customer ID:
...
SOOrder order = new SOOrder();
SOOrderEntry graph = PXGraph.CreateInstance<SOOrderEntry>();
order.OrderType = "SO";
order.CustomerID = project.CustomerID;
graph.CurrentDocument.Insert(order);
...
Is there something special I need to do to get a CustomerID to get set within the view object so I can save it?

Will assigning order variable the result of the graph.CurrentDocument.Insert(order); command resovle the error?
SOOrder order = new SOOrder();
SOOrderEntry graph = PXGraph.CreateInstance<SOOrderEntry>();
order.OrderType = "SO";
order = graph.CurrentDocument.Insert(order);
order.CustomerID = project.CustomerID;
graph.CurrentDocument.Update(order);
graph.Actions.PressSave();

Related

Inconsistent data view search in redirect action?

I have a custom graph with multiple grids under different tabs. Each grid has a column for sales order order numbers, which have redirect actions to the SOOrderEntry graph. One of the grids use an action with this code to find and insert the sales order into the redirected graph, and this works fine:
SOOrderEntry graph = PXGraph.CreateInstance<SOOrderEntry>();
graph.Document.Current = graph.Document.Search<SOOrder.orderNbr>(row.OrderNbr);
However, using that same code in an action for a different grid does not work. Stepping through the debugger shows that
graph.Document.Search<SOOrder.orderNbr>(row.OrderNbr);
returns an empty result.
Each action has DependOnGrid=true set in the pages data source, each grid has SyncPosition=true. Even when hardcoding a string order number into the search parameter it returns an empty result.
The workaround I've found to work is to use
PXSelect<SOOrder>.Search<SOOrder.orderNbr, SOOrder.orderType>(graph, row.OrderNbr, row.OrderType);
within the actions that aren't working.
Has anyone experienced this or maybe has some insight into what's going on here? It's strange to me that very similar redirect actions within the same graph need different code to work properly.
Edit: adding the view definitions
This is the view that uses the action with Search to find the needed document.
public PXSelectReadonly2<
SOOrder,
InnerJoin<BAccount,
On<SOOrder.customerID, Equal<BAccount.bAccountID>>,
LeftJoin<STSalesTerritory,
On<BAccountExt.usrTerritoryID, Equal<STSalesTerritory.territoryID>>>>,
Where2<
Where<SOOrder.orderType, Equal<SOOrderTypeConstants.salesOrder>,
And<SOOrder.status, NotEqual<SOOrderStatus.completed>,
And<SOOrder.status, NotEqual<SOOrderStatus.invoiced>,
And<SOOrder.status, NotEqual<SOOrderStatus.cancelled>>>>>,
And<
Where2<
Where<Current<STSalesTerritoryInqFilter.ownerID>, IsNotNull,
And<Current<STSalesTerritoryInqFilter.territoryID>, IsNull,
And<Current<STSalesTerritoryInqFilter.repID>, IsNull,
And<BAccountExt.usrTerritoryID, IsNull,
And<SOOrder.ownerID, Equal<Current<STSalesTerritoryInqFilter.ownerID>>>>>>>,
Or<
Where2<
Where<Current<STSalesTerritoryInqFilter.ownerID>, IsNotNull,
Or<Current<STSalesTerritoryInqFilter.territoryID>, IsNotNull,
Or<Current<STSalesTerritoryInqFilter.repID>, IsNotNull>>>,
And<
Where2<
Where<Current<STSalesTerritoryInqFilter.ownerID>, IsNull,
Or<STSalesTerritory.ownerID, Equal<Current<STSalesTerritoryInqFilter.ownerID>>>>,
And<
Where2<
Where<Current<STSalesTerritoryInqFilter.territoryID>, IsNull,
Or<STSalesTerritory.territoryID, Equal<Current<STSalesTerritoryInqFilter.territoryID>>>>,
And<Where<Current<STSalesTerritoryInqFilter.repID>, IsNull,
Or<STSalesTerritory.repID, Equal<Current<STSalesTerritoryInqFilter.repID>>>>>>>>>>>>>>>
OpenSOOrders;
And this is the view with the action where the search doesnt work, and a PXSelect is needed to find the right document
public PXSelectJoinGroupBy<
Customer,
InnerJoin<Address,
On<Customer.defAddressID, Equal<Address.addressID>>,
InnerJoin<SOOrder,
On<Customer.bAccountID, Equal<SOOrder.customerID>>,
InnerJoin<BAccount,
On<Customer.bAccountID, Equal<BAccount.bAccountID>>,
InnerJoin<STSalesTerritory,
On<BAccountExt.usrTerritoryID, Equal<STSalesTerritory.territoryID>>>
>>>,
Where2<
Where<SOOrder.orderDate, GreaterEqual<Current<STSalesTerritoryInqDateFilter.startDate>>,
And<SOOrder.orderDate, LessEqual<Current<STSalesTerritoryInqDateFilter.endDate>>>>,
And<
Where2<
Where<SOOrder.orderType, Equal<SOOrderTypeConstants.salesOrder>,
Or<SOOrder.orderType, Equal<SOOrderTypeConstantsExt.websiteOrder>>>,
And<
Where2<
Where<SOOrder.status, NotEqual<SOOrderStatus.hold>,
And<SOOrder.status, NotEqual<SOOrderStatus.cancelled>,
And<SOOrder.status, NotEqual<SOOrderStatus.voided>,
And<Customer.customerClassID, NotEqual<marketplaceCustomerClassID>
>>>>,
And<
Where2<
Where<Current<STSalesTerritoryInqFilter.territoryID>, IsNull,
Or<BAccountExt.usrTerritoryID, Equal<Current<STSalesTerritoryInqFilter.territoryID>>>>,
And<Where<Current<STSalesTerritoryInqFilter.ownerID>, IsNull,
Or<STSalesTerritory.ownerID, Equal<Current<STSalesTerritoryInqFilter.ownerID>>>>>>>>>>>>,
Aggregate<
GroupBy<Customer.acctCD>>,
OrderBy<SOOrder.orderDate.Desc>>
NewCustomers;
I am not sure I understand enough of what you are doing to be able to help, but I'll try.
The primary key of SOOrder is CompanyID, OrderType, OrderNbr - that means you should search for SOOrder by OrderType and OrderNbr. You did that in the "workaround" but not the first attempt.
If your grids return values and you just need to make the Order Nbr field a clickable link to the SOOrderEntry screen, add the field to the view (in the screen editor) under the grid's "Levels" section and then mark the OrderNbr field as AllowEdit = true to make it a clickable. Assuming you are showing the OrderNbr in the grid from the SOOrder table, the SOOrder DAC defines the primary graph of SOOrder as SOOrderEntry and will know how to redirect there for you.
If you really need to extract the current SOOrder from the view OpenSOOrders to manually redirect to the order, you access the "Current SOOrder" from the view (made possible by your grid setting "SyncPosition=true") via OpenSOOrders.Current. For example, you can find the current SOOrder, create an instance of the SOOrderEntry graph, and set the current SOOrder (Document) as follows:
SOOrder order = OpenSOOrders.Current;
SOOrderEntry graph = PXGraph.CreateInstance<SOOrderEntry>();
graph.Document.Current = order;
This should work because order is the exact record in the database. To search with just the order type and number, you would use:
graph.Document.Current = graph.Document.Search<SOOrder.orderType, SOOrder.orderNbr>
(order.OrderType, order.OrderNbr);
For completeness, if SOOrder was being updated in a method that you are overriding, you would use Locate instead of Search. Search will find the record in the database, but Locate will find it in the cache. This means that if the method updated values then you will retrieve those updates rather than wiping them out with a fresh copy of the record that Search would retrieve. Locate uses the key fields of the specified record to locate the record in the cache as shown below.
graph.Document.Current = graph.Document.Locate(order);
As for the view not finding the current record for you, it seems you aggregating by Customer. I don't use aggregate often, but I belive this means the results will be returned summarized by customer. If there is more than 1 OrderNbr value, it cannot be shown in the aggregate. As the second view aggregates by Customer, you would need to allow the user to select orders of the customer and use Current from THAT view.
If you want your aggregate in 1 grid and have it drive a view of SOOrders of the selected customer, you would add a new view (and secondary grid) for SOOrder as follows:
public PXSelect<SOOrder,
Where<SOOrder.customerID, Equals<Current<Customer.bAccountID>>>> CustomerOrders;
Just to put a plug in for FBQL, you could write that as:
public SelectFrom<SOOrder>
.Where<SOOrder.customerID.IsEqual<Customer.bAccountID.FromCurrent>>
.View CustomerOrders;
The SyncPosition on NewCustomers should cause CustomerOrders to refresh as you change rows of NewCustomers. Again, on this view, I would make the OrderNbr field of SOOrder a clickable link by using AllowEdit = true.
I have encountered scenarios in pages with 2 primary keys where the Search<> is invoked as follows:
graph.Document.Search<SOOrder.orderNbr>(row.OrderNbr, row.OrderType);
There are multiple references in Acumatica's source code. A good example can be found in the SOInvoiceEntry graph >> RecalcUnbilledTax() method:
soOrderEntry.Document.Current = soOrderEntry.Document.Search<SOOrder.orderNbr>(order.SOOrderNbr, order.SOOrderType);
Your second view has an aggregated selection, grouped by Customer.AcctCD. For the same customer you might have multiple orders: which one should be displayed and opened? By default, Acumatica generates the SQL with MAX for unspecified columns.
I would try to take the aggregation out of the view and see if it works.

PXSelect not bringing back recently persisted rows

I am writing an override for CalculateSalesPriceInt() (The function that calculates the sale price for an item) Here is the code in question
[PXOverride]
public virtual decimal? CalculateSalesPriceInt(PXCache sender, string custPriceClass, int? customerID, int? inventoryID, int? siteID, CurrencyInfo currencyinfo, decimal? quantity, string UOM, DateTime date, bool alwaysFromBaseCurrency,
Func<PXCache, string, int?, int?, int?, CurrencyInfo, decimal?, string, DateTime, bool, decimal?> del)
{
Customer cust =
PXSelect<Customer, Where<Customer.bAccountID, Equal<Required<Customer.bAccountID>>>>.Select(
Base, customerID);
InventoryItem item =
PXSelect<InventoryItem,
Where<InventoryItem.inventoryID, Equal<Required<InventoryItem.inventoryID>>>>
.Select(Base, inventoryID);
var itemExt = item.GetExtension<Arctc001InventoryItem>();
var custExt = cust.GetExtension<Arctc001Customer>();
The issue is with the InventoryItem and Customer selects. The process is such:
Create new sales order for a customer
Insert Item A into SOLines
Decide I dont like the Descr(or any other field) for item A and click on the hyperlink to pop open an instance of the StockItemMaint Page
Edit the Descr Field so it is different
Press Save and close the StockItemMaint page
Delete said InventoryItem from SOLines
Re-Add the same InventoryItem back in
Calculate SalesPriceInt fires off as I add the item back in
In debug mode, take a look at the results of PXSelect and even though I have Persisted my changes in the window, the old values still come up.
I have tried using either PXSelect or PXSelectReadonly, and no matter what the old values before I changed the inventoryitem record still come back as a result
How can I work with the cache so I get these persisted values?
Thanks

Grab LineNbr of Newly Inserted Line

I'm trying to get the line number of a newly inserted INTran row.
Here is the code I'm using:
INRegister issue = new INRegister();
//Code to populate INRegister...
INIssueEntry graph = PXGraph.CreateInstance<INIssueEntry>();
graph.issue.Insert(issue);
graph.Actions.PressSave();
//PXSelect to get new RefNbr for INRegister object
issue = PXSelect<INRegister, Where<INRegister.refNbr, Equal<Current<INRegister.refNbr>>,
And<INRegister.docType, Equal<Current<INRegister.docType>>>>>.Select(graph);
graph.issue.Current = issue;
INTran issueRow = new INTran();
//Code to populate issueRow...
graph.transactions.Insert(issueRow);
graph.transactions.Current = issueRow;
graph.Actions.PressSave();
//Trying to get transaction line number
issueRow = PXSelect<INTran, Where<INTran.refNbr, Equal<Current<INTran.refNbr>>,
And<INTran.docType, Equal<Current<INTran.docType>>,
And<INTran.lineNbr, Equal<Current<INTran.lineNbr>>>>>>.Select(graph);
//At this point, issueRow is now null because LineNbr was null above
row.TranRefNbr = issueRow.RefNbr;
row.TranLineNbr = issueRow.LineNbr;
row.Released = true;
ItemReqs.Update(row);
//... ending code...
I've examined trying to save before setting as current (setting as current item for convenience for when I write the PXSelect to get the LineNbr), but I've found that the LineNbr, even on the graph, stays null during the entire excution. I've looked in the database as the graph is saving the new line and it does contain the line number. I'm not sure why my PXSelect isn't grabbing the line number.
When you insert your transaction row the return should have the linenbr
var issueRow = graph.transactions.Insert(issueRow);
I am guessing your issue is that you are setting current after using the issueRow that was NOT returned which would have a null linenbr.
Also you really do not need to PressSave until the end (or ready to generate the batch) as the linenbr will still get set through the LineNbr attribute. Referring to a save after inserting the INRegister record.
I think current will also be set after you insert so I don't see a need to set current which will remove your need for the PXSelects.
Example getting the LineNbr in the simplest of steps:
INIssueEntry graph = PXGraph.CreateInstance<INIssueEntry>();
graph.issue.Insert(new INRegister());
INTran issueRow = graph.transactions.Insert(new INTran());
//issueRow will now have a LineNbr value...
PXTrace.WriteInformation($"My Line Nbr is {issueRow.LineNbr}");
//RefNbr will receive its value when perform the Persist (Actions.PressSave)

How do I prevent SOLineSplit and INItemPlan from deleting when Qty = 0?

I noticed that when the quantity of a SOLine is zero, there are no SOLineSplit or INItemPlan records available for that line. The second the quantity is greater than 0, the system makes those records, and if the qty is set back to 0, the records are deleted.
Is there a way to prevent the SOLineSplit and INItemPlan objects from deleting when a record is set to 0 quantity?
Is there a way to still have the system create an SOLineSplit and INItemPlan if the SOLine is initially created with a 0 quantity?
The reason for the question is that a customer wants the system to lock the SOLine after a certain point, but also allow for the Qty to be adjusted from another screen. Since this is not directly changing the value on the SOLine screen, this isn't triggering the events to create the split and plan.
I have tried creating an instance of SOOrderEntry in the custom screen as follows:
SOOrderEntry graph = PXGraph.CreateInstance<SOOrderEntry>();
//Also tried graph.Transactions.Current = line, but did not work
graph.Transactions.Update(line);
graph.Actions.PressSave();
Doing that keeps resulting in a null object reference error:
Error: An error occurred during processing of the field OrderQty : Object reference not set to an instance of an object..
System.NullReferenceException: Object reference not set to an instance of an object.
at PX.Objects.SO.SOOrderEntry.SOLine_OrderQty_FieldUpdated(PXCache sender, PXFieldUpdatedEventArgs e) ...
I think the problem with your statement in using SOOrderEntry is you are not truly loading the order in the graph to update it correctly. After you create the graph instance and before the transaction update, you should load the document header like this...
SOOrderEntry graph = PXGraph.CreateInstance<SOOrderEntry>();
graph.Document.Current = graph.Document.Search<SOOrder.orderNbr>(line.OrderNbr, line.OrderType);
if(graph.Document.Current == null)
{
return;
}
graph.Transactions.Update(line);
graph.Actions.PressSave();
As for controlling how the plan and split records are entered from SOOrderEntry... The entries are controlled through the attributes on PlanID. The cache attached is where this gets added on SOOrderEntry...
[PXMergeAttributes(Method = MergeMethod.Append)]
[SOLineSplitPlanID(typeof(SOOrder.noteID), typeof(SOOrder.hold), typeof(SOOrder.orderDate))]
protected virtual void SOLineSplit_PlanID_CacheAttached(PXCache sender)
{
}
You can make your own graph extension of Sales order and replace the attribute with your own version of SOLineSplitPlanID... it might be a battle for you as I am not sure why you would want the plan record to exist when zero qty to plan.

Updating DAC Extension

I am trying to update some custom fields in a DAC extension via code, it is not saving the changes to the DB. The code works fine to retrieve the extension and data. What am I missing - do i have to somehow update the myLS with the extension (i thought it did that automatically)?
myLS = LineItemSerial.Select();
INItemLotSerialExt myext = myLS.GetExtension<INItemLotSerialExt>();
myext.UsrFrame1 = "xyz";
myext.UsrFrame2 = "zzz";
myext.UsrFrame3 = "yyy";
LineItemSerial.Update(myLS);
graph.Actions.PressSave();
You should say to cache of Acumatica that you want to update value:
LineItemSerial.Cache.SetValueExt(myLS , "UsrFrame1", "xyz");
LineItemSerial.Cache.SetValueExt(myLS , "UsrFrame2 ", "zzz");
LineItemSerial.Cache.SetStatus(myLS , PXEntryStatus.Modified);
LineItemSerial.Cache.Update(myLS);
LineItemSerial.Cache.IsDirty = true;
NB. LineItemSerial.Cache.IsDirty = true; for some cases can be omitted but in my experience it was often helpful.
INItemLotSerialExt myext = LineItemSerial.GetExtension<INItemLotSerialExt>(myLS); //if LineItemSerial is a view related to the DAC. I hope LineItemSerial is a public view defined in the graph as you are trying to save the changes when u press the save of graph.
OR
INItemLotSerialExt myext = PXCache<INItemLotSerial>.GetExtension<INItemLotSerialExt>(myLS);
Is'nt this the rite way to get the extension?
From documentation
GetExtension(object)
InventoryItem item = cache.Current as InventoryItem;
InventoryItemExtension itemExt =
cache.GetExtension<InventoryItemExtension>(item);
OR
GetExtension(Table)
The code below gets an extension data record corresponding to the given instance of the base data record.
InventoryItem item = cache.Current as InventoryItem;
InventoryItemExtension itemExt =
PXCache<InventoryItem>.GetExtension<InventoryItemExtension>(item);
Try something like this...
ContractExtension cExt = PXCache<PMProject>.GetExtension<ContractExtension>(project);
ARInvoiceEntry graph = PXGraph.CreateInstance<ARInvoiceEntry>();
graph.Document.Current = graph.Document.Search<ARInvoice.projectID, ARInvoice.docDate>(projectID.Value, invoiceDate.Value);
if(graph.Document.Current !=null)
{
ARInvoice i = graph.Document.Current;
i.InvoiceNbr = cExt.CustomerPO;
graph.Document.Update(i);
graph.Actions.PressSave();
}

Resources