Acumatica - Copying a row in Purchase Orders screen - acumatica

We would like to copy current row in acumatica via button in Purchase Orders screen. I asked question about that earlier in Acumatica - Copy last row
None of the solutions work anymore in 2020 R1, build 20.104.0012. It appears that NoteID needs to be set to null, in addition to line ID. Ilya's solution in the link above does not create the second line at all, after the PressSave is run. Brandon's solution generates "An item with the same key has already been added." error. My updated action is below:
public PXAction<POOrder> copyRow;
[PXButton]
[PXUIField(DisplayName = "Copy Row")]
protected virtual IEnumerable CopyRow(PXAdapter adapter)
{
var row = Base.Transactions.Current;
var rowCopy = Base.Transactions.Cache.CreateCopy(row) as POLine;
rowCopy.LineNbr = null;
rowCopy.NoteID = null;
rowCopy = Base.Transactions.Insert(rowCopy);
return adapter.Get();
}
Please advise.

Related

CodedUI "FindMatchingControls()" works 10% of the time but usually returns about half of the controls

Problem: I am using FindMatchingControls() to create a Collection of rows in a WPF table. I have written a loop that will set the ComboBox in each row to a given value. The whole thing works sometimes but more often than not, FindMatchingControls() actually finds about half of the rows. How can I configure timeouts or change settings to make it find all 50 controls every time or perhaps to find the first 10 and then to find the next 10 etc?
Background: I am testing a WPF window and on it, there's a table, each row in the table has a drop down list. There are 50 rows and in future there could be more so it is not feasible for me to record the setting of each one, my recorded test would be out of date with each new version (every month or so).
I have therefore recorded the setting of 1 ComboBox and then I used FindMatchingControls() to create a Collection. I loop through the Collection setting each ComboBox in that collection to the desired selection. The first 23 rows are shown on my current screen resolution. The only problem is that FindMatchingControls() sometimes returns 23, sometimes 26 , sometimes 34 and sometimes it returns all 50 rows! My question is, how do I fix the code below so that it always return all 50 rows (and possibly more in future).
You can see from the code that I found the Parent control twice so pseudo code is below.
Psuedo Code:
1) Find Parent Container (table)
2) Define a row (that is a child of the parent table)
3) Use FindMatchingControls to get a Collection of Rows
4) Loop through the Collection, finding the ComboBox in each row and setting it's selection to a value passed into the method.
CODE:
public void PlaceAnOrderScreen_SelectItems_List(String item /*Value to set all 50 ComboBoxes to*/)
{
WpfControl rowOfOrderItems = new WpfControl(this.UIOptimalOrderSystemClientShWindow.UIItemCustom22.UIListViewAutoID37Table);
rowOfOrderItems.SearchProperties[WpfControl.PropertyNames.ControlType] = "DataItem";
rowOfOrderItems.SearchProperties[WpfControl.PropertyNames.ClassName] = "Uia.ListViewItem";
rowOfOrderItems.WindowTitles.Add("Order Management System");
rowOfOrderItems.Find();
rowOfOrderItems.DrawHighlight(); //Visible diagnostic
//should get a collection of 50 controls ...
//... but this is dodgy, it sometimes finds 23, 26, 34 or ocassionaly all 50 controls.
//There are 23 visible controls and the rest, you have to scroll down to see.
UITestControlCollection itemRows = rowOfOrderItems.FindMatchingControls();
int c = 0;
int i = 1;
string label = String.Empty;
foreach (var auditSelectionBox in itemRows)
{
//After the top 15 drop down selections have been made, strat scrolling down.
//This is because setting the Value for a list box that is off the screen
//causes it to complain the control is blocked...
if (c >= 15)
{
if (i >= 3) //The scroll wheel moves 3 rows at a time, so only scroll once for every 3 rows...
{
Mouse.MoveScrollWheel(-1);
i = 0;
}
}
i++;
c++;
WpfCell auditDDL1 = new WpfCell(auditSelectionBox);
auditDDL1.SearchProperties[WpfCell.PropertyNames.ColumnHeader] = "Total";
auditDDL1.WindowTitles.Add("OrderSystem 5");
//Works but takes 5 - 16 seconds per drop down list
auditDDL1.Value = item;
}
}
Instead of trying to find matching controls based on another row, you could use a method that takes the parent (in your case the table) and returns all it's children in a recursive way. It digs all the way down until all available children have been found. It shouldn't matter how much row's your table has, it will try and get all of them. It's usable for any UITestControl.
public ParentControl GetChildControls(UITestControl parentControl)
{
ParentControl parent = new ParentControl();
if (parentControl != null)
{
List<ParentControl> children = new List<ParentControl>();
foreach (UITestControl childControl in parentControl.GetChildren())
{
children.Add(GetChildControls(childControl));
}
parent.Children = new KeyValuePair<UITestControl, List<ParentControl>>(parentControl, children);
}
return parent;
}
The parent class
public class ParentControl
{
public KeyValuePair<UITestControl, List<ParentControl>> Children { get; set; }
public string Value
{
get
{
return Children.Key.Name;
}
}
}
I just added the Value property for easy access to the name of UITestControl.
PixelPlex (above) has provided the best answer. All I had to add to PixelPlex's code was an If statement to set the ComboBox to a value when it was found. The foreach is therefore as below in my case ...
foreach (UITestControl childControl in parentControl.GetChildren())
{
children.Add(GetChildControls(childControl));
//Added below If statement to set ComboBox selected item to "Carrots"...
if (childControl.ClassName == "Uia.ComboBox")
{
WpfComboBox cb = (WpfComboBox)childControl;
cb.SelectedItem = "Carrots";
}
}
This selects Carrots from my ComboBox... Everything that does not satisfy my If statement is not relevant so I don't do anything with it.

Imported/Copied Opportunity shift one line

Lines are shifting with custom fields in the Opportunity Products grid one row down at a customized site.
Background:
We have a customization which provides unit margins and % in the header. PXFormula is used on the DAC for any dependent on a calculation.
Opportunity Products has 4 added fields:
Last Cost = Last Cost from InventoryItem
Total Cost = Qty * Last Cost
Margin = ExtAmt - ExtCost
Manual Cost, a checkbox to allow manual override of Last Cost
Opportunity has 2 added fields:
Margin Total = Sum of Margins
Margin % = Margin Total/Sales Total
Problem:
There is an issue with a customization Opportunities where lines shift one line when a record is copied from an existing Opportunity or when an Excel file is imported.
Existing Record
After Copy/Paste or Import from Excel
Code:
My current code:
public PXSelect<INItemCost,
Where<INItemCost.inventoryID,
Equal<Current<CROpportunityProducts.inventoryID>>>> Cost;
protected void CROpportunityProducts_RowInserting(PXCache cache,
PXRowInsertingEventArgs e, PXRowInserting InvokeBaseHandler)
{
if(InvokeBaseHandler != null)
InvokeBaseHandler(cache, e);
var row = (CROpportunityProducts)e.Row;
if (row == null) return;
var rowExt = cache.GetExtension<CROpportunityProductsExt>(row);
if (rowExt == null) return;
var cost = Cost.SelectSingle();
if (cache.GetValue(row, "usrManCost") == null) return;
if (cost != null && (bool)cache.GetValue(row, "usrManCost") == false)
{
cache.SetValueExt<CROpportunityProductsExt.usrLastCost>(row, cost.LastCost);
}
}
What could be causing this? I have thoughts that the RowInserting event returns 0 for the first line since the PXSelect<> statement returns 0 because InventoryItem is not in cache until the next row.
One potential solution I came up with was using RowInserted. This resolves the issue when using Copy/Paste. However, it causes Import from Excel to miscalculate Total Margin.
Could be the Current<> on your view is not really the current you need?
What happens if you simply replace the var cost = Cost.SelectSingle() line with the following to use Required<> as pass in the Inventory ID...
INItemCost cost = PXSelect<INItemCost,
Where<INItemCost.inventoryID, Equal<Required<CROpportunityProducts.inventoryID>>>>
.Select(Base, row.inventoryID);
The answer is because events in this case must be done in the RowSelecting event handler, along with PXConnectionScope().
Brendan was on the right track moving the PXSelect into the handler. My code in the question becomes the below. Also, note use of Required<> versus Current<>.
public void CROpportunityProducts_RowSelecting(PXCache cache,
PXRowSelectingEventArgs e)
{
var row = (CROpportunityProducts)e.Row;
if (row == null) return;
using (new PXConnectionScope())
{
INItemCost cost = PXSelect<INItemCost,
Where<INItemCost.inventoryID,
Equal<Required<INItemCost.inventoryID>>>>.Select(Base, row.InventoryID);
if (cost != null && (bool)cache.GetValue(row, "usrManCost") == false)
{
//decimal dbLastCost = (decimal)cost.LastCost;
var lstCost = cache.GetValue(row, "usrLastCost");
if ((decimal)cost.LastCost == (decimal)0.00)
{
cache.SetValueExt<CROpportunityProductsExt.usrLastCost>(row,
cost.LastCost);
}
}
}
}

Acumatica - Copy last row

So looks like seemingly easy things in Acumatica are terribly complicated to implement. All I wanna do is to copy last row of my grid as a new one. I would like the user to persist the changes himself, so my code would put it just in cache. This is my action so far:
public PXAction<SOOrder> copyLastRow;
[PXUIField(DisplayName = "Copy Last Row", MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select)]
[PXLookupButton]
public virtual IEnumerable CopyLastRow(PXAdapter adapter)
{
SOLine line = Base.Transactions.Select().LastOrDefault();
int? lineNbr = 0;
foreach(SOLine line2 in Base.Transactions.Select())
if (line2.LineNbr > lineNbr)
lineNbr = line2.LineNbr;
line.LineNbr = lineNbr + 1;
Base.Transactions.Cache.Insert(line);
return adapter.Get();
}
So maybe I am not getting something or completely wrong in my code, but I'm getting errors no matter what I do. The grid is not getting refreshed with my row and I keep getting all sorts of errors, such as "This record cannot be saved" or "another process updated this record", etc. Also, any ideas on how to generate a new lineNbr without clunky logic I have? Much appreciated if anyone can help.
If you just want to take the line and copy all values you can use cache copy and null the lineid. Then on insert of the copied line it will get the next linenbr automatically... adding to your sample code in your question...
SOLine line = Base.Transactions.Select().LastOrDefault();
var copy = (SOLine)Base.Transactions.Cache.CreateCopy(line);
copy.LineNbr = null;
Base.Transactions.Cache.Insert(copy);
This method should also be upgrade friendly in the event new fields or customization are added to SOLine they will continue to be copied without having to selectively include all fields to be copied (using CreateCopy).
The code should be like this:
public PXAction<SOOrder> copyLastRow;
[PXUIField(DisplayName = "Copy Last Row", MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select)]
[PXProcessButton]
public virtual IEnumerable CopyLastRow(PXAdapter adapter)
{
SOLine line = Base.Transactions.Select().LastOrDefault();
SOLine newLine = new SOLine();
... (copy all you need from line to newLine)
Base.Transactions.Cache.Insert(newLine);
Base.Actions.PressSave();
return adapter.Get();
}
It is my guess that Line Number will get automatically generated ! It is not in sequence anyway.
I suggest you create a new SOline row and transfer the enterable fields from the selected row (Last row) in the same and Insert. Should work.
If you don't want attachments or notes to copy make sure to add
copy.NoteID = null;

Unique contstaint error when rejecting changes on a DataTable

I have a datatable problem when I reject changes on the table. I've created an example below which demonstrates the problem:
DataTable table = new DataTable();
table.Columns.Add("Name", typeof(string));
table.Columns.Add("Last_Name", typeof(string));
table.Columns.Add("Male", typeof(Boolean));
foreach (DataColumn column in table.Columns)
{
if (column.ColumnName == "Name")
{
column.Unique = true;
}
}
DataRow row;
row = table.NewRow();
row["Name"] = String.Format("Steve");
row["Last_Name"] = String.Format("Smith");
row["Male"] = true;
table.Rows.Add(row);
table.AcceptChanges();
So at this stage I have a DataTable with a unique column constraint and 1 row in that table.
I now delete that row:
row.Delete();
This sets the rowstate to Deleted.
At this stage I realise that I've made a mistake and want to add the row again. So I create the new row again and add it to the DataTable:
row = table.NewRow();
row["Name"] = String.Format("Steve");
row["Last_Name"] = String.Format("Smith");
row["Male"] = true;
table.Rows.Add(row);
At this stage my DataTable contents are in the following state:
table.Rows[0].RowState is Deleted
table.Rows[1].RowState is Added
Now, I've changed my mind about the whole thing and want to get back to how I started so I call RejectChanges:
table.RejectChanges();
When I do this I receive the following error:
Column 'Name' is constrained to be unique. Value 'Steve' is already present.
I understand that there are 2 rows with the same values but I thought reject changes would have ingored this as the RowStates are different.
Any ideas how I can get round this?
In my live code I use this to move rows between to 2 grids (like allowing the user to selected what columns are visible)
I'm using C#4.0 in Visual Studio 2012
Thanks in advance

Selecting DataGridViewRow at a selected Index

I am having problems selecting a row out of the DataGridView on a search. The data source is a DataTable from a database. I am using a search box that checks the DataGridView for a product matching the product key, and i want to select it if found.
Here is what i have:
private void search_btn_Click(object sender, EventArgs e)
{
foreach (DataGridViewRow row in products_dgv.Rows)
{
string tempCode = row.Cells[0].Value.ToString(); //Code comparing
if (tempCode == code_tb.Text) //Checks if code matchs the search code
{
//I would like to do a products_dgv.selectedIndex = row.Index but it
//doesnt work
break;
}
}
}
Any help is much appreciated. Thank You!
You can use the CurrentCell property of the DataGridView to set the selection. If you are using FullRowSelect as selection mode, just use any cells in the row that you would like to select.
For example:
...
if (tempCode == code_tb.Text) //Checks if code matchs the search code
{
products_dgv.CurrentCell = row.Cells[0];
break;
}
...

Resources