How to update Cost for a stock item - acumatica

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

Related

Why doesn't my BLC 'save' action save all records unless its at the very end of the cache insert?

I have BLC code that is parsing out the AuditHistory 'ModifiedFields' field so that I have multiple lines with separate 'Field' and 'Value' fields. The 'ModifiedFields' field contains null separated values, so my BLC C# code uses the split function to put these into an array. This all works fine. The problem I'm having is saving to a table in Acumatica via the Graph / Cache 'insert' function. If I use the 'Actions.PressSave()' method after every iteration of the array, it doesn't save every record - effectively skipping records. I have no idea why this would happen. If I put the 'Actions.PressSave()' method at the very end of everything, I get all the records - but sometimes it times out, I'm assuming because of (in some cases) the massive amount of records being cached before the save.
Putting the PressSave method at ANY other point in the loop(s) results in missed records.
Here is my BLC code (note the several places I placed the PressSave method for testing, but commented out - leaving the last one):
public PXAction<AUAuditSetup> CreateAuditRecords;
[PXProcessButton]
[PXUIField(DisplayName = "Create Audit Records", MapEnableRights = PXCacheRights.Update, MapViewRights = PXCacheRights.Update)]
protected virtual IEnumerable createAuditRecords(PXAdapter adapter)
{
PXLongOperation.StartOperation(Base, delegate ()
{
//Create graph of TAC screen...
var osdm = PXGraph.CreateInstance<OpenSourceDataMaint>();
xTACOpenSourceDetail osd;
int recordID = 1;
//Get records from AuditHistory
//PXResultset<AuditHistory> res = PXSelect<AuditHistory>.Select(Base);
PXResultset<AuditHistory> res = PXSelect<AuditHistory,
Where<AuditHistory.changeDate, GreaterEqual<Required<AuditHistory.changeDate>>>>.Select(Base, Convert.ToDateTime("01/01/2013"));
var companyID = Convert.ToString(PX.Common.PXContext.GetSlot<int?>("singleCompanyID"));
foreach (PXResult<AuditHistory> rec in res)
{
var ah = (AuditHistory)rec;
if (ah != null)
{
string[] fields = ah.ModifiedFields.Split('\0');
for (int i = 0; i < fields.GetUpperBound(0); i+=2)
{
osd = new xTACOpenSourceDetail();
osd.OpenSourceName = "AuditHistoryTable";
osd.DataID = "1";
osd.String01 = Convert.ToString(PX.Common.PXContext.GetSlot<int?>("singleCompanyID"));
osd.Number01 = ah.BatchID;
osd.Number02 = ah.ChangeID;
osd.Number03 = recordID;
osd.String02 = ah.ScreenID;
osd.String03 = Convert.ToString(ah.UserID);
osd.Date01 = ah.ChangeDate;
osd.String04 = ah.Operation;
osd.String05 = ah.TableName;
osd.String06 = ah.CombinedKey;
osd.String07 = fields[i];
osd.String08 = fields[i + 1];
osd.String09 = Convert.ToString(ah.ChangeDate);
osdm.OpenSourceDataDetail.Insert(osd);
//if (osd != null)
//osdm.Actions.PressSave();
recordID++;
}
recordID = 1;
//osdm.Actions.PressSave();
}
//osdm.Actions.PressSave();
}
osdm.Actions.PressSave();
});
return adapter.Get();
}
Any ideas?
A couple of things you could try:
Instead of instantiating osd directly, replace with:
osd = osdm.OpenSourceDataDetail.Insert();
and then after assigning values and prior to PressSave(), use .Update on the cache:
osdm.OpenSourceDataDetail.Update(osd);
and then after PressSave(), clear the graph:
osdm.Clear();
Importantly, does your DAC have an IsKey = true on the DataID field and marked PXDBIdentity, hence auto-assigned and unique?
Example:
public abstract class dataID : PX.Data.IBqlField {}
[PXDBIdentity(IsKey = true)]
[PXUIField(Visible = false, Enabled = false)]
public virtual int? DataID {get; set;}
If the changes are successful, you could then try moving the .PressSave() and .Clear() after the For loop.

How to programmatically create and confirm shipment for an order using ADD Order popup?

I am trying to create and confirm shipment programmatically for a single order. But I am stuck on how to select all the lines that are displayed on ADD Order popup for adding SOLines on Shipments screen. Any help on this?
string operation = SOOperation.Issue;
SOShipmentEntry shipmentGraph = PXGraph.CreateInstance<SOShipmentEntry>();
BAccountR customer = null;
INSite warehouse = null;
PXResultset<SOOrder> objSOOrder = PXSelect<SOOrder, Where<SOOrder.customerRefNbr, Equal<Required<SOImportFilter.referenceID>>>>.Select(this, currentFilter.ReferenceID);
foreach(SOOrder order in objSOOrder)
{
shipmentGraph.Clear();
var shipment = shipmentGraph.Document.Insert();
customer = (BAccountR)PXSelect<BAccountR,
Where<BAccountR.bAccountID, Equal<Required<BAccountR.bAccountID>>>>
.SelectSingleBound(shipmentGraph, new object[] { }, order.CustomerID);
shipment.CustomerID = customer.BAccountID;
shipment = shipmentGraph.Document.Update(shipment);
warehouse = (INSite)PXSelect<INSite,
Where<INSite.siteID, Equal<Required<INSite.siteID>>>>
.SelectSingleBound(shipmentGraph, new object[] { }, "159");
shipment.SiteID = warehouse.SiteID;
shipment = shipmentGraph.Document.Update(shipment);
var addorder = shipmentGraph.addsofilter.Insert();
addorder.Operation = operation;
addorder = shipmentGraph.addsofilter.Update(addorder);
addorder.OrderType = order.OrderType;
addorder = shipmentGraph.addsofilter.Update(addorder);
addorder.OrderNbr = order.OrderNbr;
addorder = shipmentGraph.addsofilter.Update(addorder);
foreach (PXResult<SOShipmentPlan, SOLineSplit, SOLine> plan in
shipmentGraph.soshipmentplan.Select())
{
SOShipmentPlan shipmentPlan = (SOShipmentPlan)plan;
shipmentPlan.Selected = true;
shipmentGraph.soshipmentplan.Update(plan);
shipmentGraph.Actions.PressSave();
}
shipmentGraph.Actions.PressSave();
}
I have difficulty understanding the feature you are trying to implement from the description. Usually you would automate CreateShipment and ConfirmShipment actions to do this.
Perhaps you have to handle a special case, if all that is blocking you is selecting the data from the grid inside the "Add Order" smart panel:
Using Inspect Element feature, determine the name of the DataView you target by clicking on the grid:
Use View Business Logic Source to look up the DataView source code:
From the source code for that DataView, we see that it returns 3 DAC (SOShipmentPlan, SOLineSplit and SOLine):
PXSelectJoinOrderBy<SOShipmentPlan,
InnerJoin<SOLineSplit, On<SOLineSplit.planID, Equal<SOShipmentPlan.planID>>,
InnerJoin<SOLine, On<SOLine.orderType, Equal<SOLineSplit.orderType>, And<SOLine.orderNbr, Equal<SOLineSplit.orderNbr>, And<SOLine.lineNbr, Equal<SOLineSplit.lineNbr>>>>>>,
OrderBy<Asc<SOLine.sortOrder, Asc<SOLine.lineNbr, Asc<SOLineSplit.lineNbr>>>>> soshipmentplan;
With that information we can now iterate the DataView using the Select method:
foreach (PXResult<SOShipmentPlan, SOLineSplit, SOLine> plan in Base.soshipmentplan.Select())
{
SOShipmentPlan shipmentPlan = (SOShipmentPlan)plan;
SOLineSplit lineSplit = (SOLineSplit)plan;
SOLine line = (SOLine)plan;
}
I used Base member to reference SOShipmentEntry graph in order to get the DataView. This should be used when you are in the context of a SOShipmentEntry graph extension:
Base.soshipmentplan.Select()
If you have a direct reference to SOShipmentEntry graph instead you can use that directly:
SOShipmentEntry shipmentEntry = PXGraph.CreateInstance<SOShipmentEntry>();
shipmentEntry.soshipmentplan.Select()
EDIT
Code for automating the Add Order dialog:
shipmentEntry.addsofilter.Current.OrderType = SOOrderTypeConstants.SalesOrder;
shipmentEntry.addsofilter.Current.OrderNbr = "000001";
shipmentEntry.addsofilter.Update(shipmentEntry.addsofilter.Current);
foreach (SOShipmentPlan line in shipmentEntry.soshipmentplan.Select())
{
line.Selected = true;
shipmentEntry.soshipmentplan.Update(line);
}
shipmentEntry.addSO.Press();

Call action within another graph, how to pass the adapter

My goal is, from a given screen :
- Add lines to the Adjustments tab of the payment & application graph
- Release
I tried to do this :
override public void createLettering(List<ARRegister> lines)
{
string refNbr = "";
foreach (ARRegister line in lines)
{
if (line.DocType == "PMT") refNbr = line.RefNbr;
}
// Get the paymententry graph, and add the invoice
ARPaymentEntry graphPmt = PXGraph.CreateInstance<ARPaymentEntry>();
ARPayment pmt = PXSelect<ARPayment, Where<ARPayment.refNbr, Equal<Required<ARPayment.refNbr>>,
And<ARPayment.docType, Equal<Required<ARPayment.docType>>>>>
.Select(this,refNbr, "PMT");
graphPmt.Document.Current = pmt;
if (pmt == null) throw new PXException(Constantes.errNotFound);
//pmt.CuryOrigDocAmt = 0m;
//graphPmt.Document.Update(pmt);
ARAdjust adj = new ARAdjust();
foreach(ARRegister line in lines)
{
if (line.DocType == "INV")
{
adj = new ARAdjust();
adj.AdjdDocType = line.DocType;
adj.AdjdRefNbr = line.RefNbr;
graphPmt.Adjustments.Insert(adj);
}
}
PXAdapter adapter = new PXAdapter(new PXView(graphPmt,true, graphPmt.Document.View.BqlSelect));
graphPmt.Persist();
graphPmt.Release(adapter);
}
My problem is I think my adapter gets every single ARPayment in it and thus tries to release them all. (The output of this function is : long processing time and then tells me 'PaymentMethod can't be null', but the paymentMethod of my graphPmt.Document is not null when I check in debug).
so How do I pass a correct PXAdapter to the Release(PXAdapter adapter) method of the PaymentEntry graph, from another custom graph of mine ?
I would think you should be able to call the action such as...
graphPmt.release.Press();
I have not tested this but I recall doing something like this for other actions in the past.

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

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.

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