Exporting Business Account attributes with Acumatica API - acumatica

Our Business Accounts in Acumatica have 13 custom Attributes for our main Business Account Class. I've been able to save values to the Attributes successfully, based on Acumatica's example "Adding Records to the Business Accounts and Opportunities Forms". But I have not been able to figure out how to retrieve the values with an Export.
First, I tried using a format similar to how the field was specified when saving them.
Public Function GetCustomerAttributes(ByVal customerID As String) As String()()
Dim customer As CR303000Content = m_context.CR303000GetSchema()
m_context.CR303000Clear()
Dim idFilter As Filter = New Filter()
idFilter.Field = customer.AccountSummary.BusinessAccount
idFilter.Condition = FilterCondition.Equals
idFilter.Value = customerID
' SIMILAR TO EXAMPLE FOR SAVING
Dim awdField As Field = New Field()
awdField.ObjectName = customer.Attributes.Attribute.ObjectName
awdField.FieldName = "AWD Number"
Dim searchfilters() As Filter = {idFilter}
Dim searchCommands() As Command = {awdField}
Dim searchResult As String()() = m_context.CR303000Export(searchCommands, searchfilters, 0, False, False)
Return searchResult
End Function
I thought this would return one result with the value for our attribute named "AWD Number". Instead, it returned 13 results, one for each attribute, and the value of each one was blank. I changed the FieldName to customer.Attributes.Attribute.FieldName and then it started returning the name of each attribute. So I thought if I added another field for the value, then I might get the name and value in separate results, like this:
Public Function GetCustomerAttributes(ByVal customerID As String) As String()()
Dim customer As CR303000Content = m_context.CR303000GetSchema()
m_context.CR303000Clear()
Dim idFilter As Filter = New Filter()
idFilter.Field = customer.AccountSummary.BusinessAccount
idFilter.Condition = FilterCondition.Equals
idFilter.Value = customerID
Dim awdField As Field = New Field()
awdField.ObjectName = customer.Attributes.Attribute.ObjectName
awdField.FieldName = customer.Attributes.Attribute.FieldName
Dim awdValue As Field = New Field()
awdValue.ObjectName = customer.Attributes.Attribute.ObjectName
awdValue.FieldName = customer.Attributes.Attribute.Value
Dim searchfilters() As Filter = {idFilter}
Dim searchCommands() As Command = {awdField, awdValue}
Dim searchResult As String()() = m_context.CR303000Export(searchCommands, searchfilters, 0, False, False)
Return searchResult
End Function
I did get a 2-item array back for each of the 13 results, but the value in the second field was still blank.
Does anyone know how I can get the values? I don't really care if I have to get them one at a time, but I'd prefer to get them all at once with their names or codes so that I don't have to rely on the indices always staying the same. Below are images of the debugger running on my second example and view in Acumatica. Thanks!

Your first attempt is correct, however you're not using the right object name and field name. The system will dynamically add fields to the primary object (view) of the screen, in this case the object name represented by customer.AccountSummary.BusinessAccount.ObjectName variable (I suggest you use the debugger to see what this value equals too - good learning exercise).
The attribute field name will use the same naming convention as used in How To Retrieve An Attribute Field In StockItems In Acumatica API?. The naming convention is _Attributes. The attribute ID is not the attribute name; I don't see your configuration but I doubt in your case the Attribute ID is "AWD Number". To summarize, the code will look like:
Dim awdField As Field = New Field()
awdField.ObjectName = customer.AccountSummary.BusinessAccount.ObjectName
awdField.FieldName = "AWDNumber_Attributes"
In your example, by putting the Attributes.Attribute.ObjectName object, the system will iterate through all values inside this table, and then return for every row the fields you want. I'm not exactly sure why you're not seeing all the attribute values in this case, but I think you should be fine with the example above.

Related

What sets limits on maximum Collection size/count in VBA?

I am using a nested Collection to store validation data from Excel, of the form: coll1(case)(subtype)(item).
My code is essentially looping through a flat input list and bin sorting the contents into collections - the top level collection is a collection of validation data for all data sources (cases), the second tier is a type collection (within each case, there are different possible types/classes of data) and the final tier is the valid list of tags/labels for things of that particular class/type.
In the code below, inp is read in from a vertical stack of Excel cells, but is essentially a list of (unique) Strings of the form "\validation_data_class\case\type\label" - hence the Split() into the labels() array to then parse.
Public tag_data As Collection
Private Sub load_tags(inp As Variant)
Dim i As Long, label() As String
Dim case_name As String, type_name As String, tag_name As String
Dim tmp_coll As Collection, tmp_coll2 As Collection
Set tag_data = New Collection
For i = LBound(inp) To UBound(inp) ' Check this works if only one entry in the list - may need IsArray() check
label = Split(inp(i, 1), "\")
Select Case label(1)
Case "tag"
' Extract the case name from the label and get its number, so we can store data in the right element of tag_data()
case_name = label(2): If Not KeyExists(tag_data, case_name) Then Set tmp_coll = New Collection: tag_data.Add tmp_coll, case_name
' Extract the type name from the label and store it, if needed
type_name = label(3): Set tmp_coll = tag_data(case_name)
If Not KeyExists(tmp_coll, type_name) Then Set tmp_coll2 = New Collection: tmp_coll.Add tmp_coll2, type_name
' Extract the actual tag and store it in the list (assumes we have ensured no duplicates already)
tag_name = label(4): Set tmp_coll = tag_data(case_name)(type_name)
Debug.Assert i < 719
tmp_coll.Add tag_name, tag_name
Case "prop"
' Still to implement
End Select
Next i
End Sub
Function KeyExists(coll As Collection, key As String) As Boolean
On Error GoTo ErrHandler
IsObject (coll.Item(key))
KeyExists = True
Exit Function
ErrHandler:
' Do nothing
End Function
The problem I am having is that it gets as far as my Debug.Assert line and then silently fails on that 719th addition to the lowest-level Collection. Weirdly it will run if I don't use keys for the lowest-level Collection, which then allows me to add the final 2 items (in this particular case, I need 721 items in that Collection, but it could be more or less in other scenarios).
I will take the workaround of not using Keys for that large Collection if I need to, but it makes my actual validation against this set of lists that bit harder later on, because I cannot just use the KeyExists method, but will have to write a slower function to crawl the un-labelled Collection looking for a match.

Can I dynamically set a PXDataFieldAssign parameter of a PXDataFieldParam object?

I have code that sets the PXDataFieldAssign value as follows:
pf = new PXDataFieldAssign<xTACProjectTask.dueDate>(someValue);
I also have a table, holding the DAC field names, such as "xTACProjectTask.dueDate". This table also has a checkbox field to determine whether to use this DAC field as a parameter.
Is there a way to not have the DAC fieldname hard-coded, and instead (maybe using a 'typeof' call?) use the results of the table query to set that field name - like the following?
pf = new PXDataFieldAssign<typeof("xTACProjectTask.dueDate")>(someValue);
or, using my query result:
pf = new PXDataFieldAssign<typeof(query.value)>(someValue);
with query.value being the value in the table holding the DAC field name?
You can create it using Type.GetType and Activator.CreateInstance. Please see the example below:
string typeName = "PX.Objects.IN.InventoryItem+descr,PX.Objects";
Type typeArgument = Type.GetType(typeName);
Type genericClass = typeof(PXDataFieldAssign<>);
Type constructedClass = genericClass.MakeGenericType(typeArgument);
object created = Activator.CreateInstance(constructedClass,new object[] { "Test Description" });
You will get the below wrapped into object in the created

VBA: Can't set a variable of a Structure within a class

I have a class, implementing two interfaces, one interface for normal use, and one interface that only reveals a function to create the class with initial parameters. The class has a field that is a Structure with two variables within, I am using the Get property of the class to get the structure, and then using dot notation to access the field within the structure, and then I am trying to set it to a number, but it never works. If I try to access the structure with the private variable within the class it works, but I want to be consistent and only use the properties to modify it within the Create function.
Public Function Create(WorksheetName As String, Optional CurrentRow As Long = 4) As ISheetInfo
With New clsSheetInfo
Set .WS = ThisWorkbook.Worksheets(WorksheetName)
Set .Cols = CreateColumnDictionary(.WS, 3)
Let .Rows.Current = CurrentRow
Let .Rows.Final = .WS.Cells(.WS.Rows.Count, 1).End(xlUp).Row
Set Create = .Self
End With
End Function
When I step through, it first goes to this property (RowData is the UDT with two fields, .Current and .Final:
Property Get Rows() As RowData
Rows = pRows
End Property
But then after the assignment, .Rows.Current is still 0, I'm not sure why.

Execute the code stored as a String in VB.NET

I wonder about how I can store a part of code in a string field of an object and convert it at run-time in executable code.
Let's say I have my class:
Public Class Car
Public m_IDCar As String
Public m_Brand As String
Public m_Description As String
Public m_Condition As String ' => here I need to store an If, or an If condition as a String, that will be executed at run-time.
End Class
Then the code that happens when CommandButton1.Click():
Dim carList As List(Of Car)
Dim parameter as String
' here I create carList with data from a database, so for each car In the database I create its relative object, with its .m_IDCar, .m_Brand, .m_Description and .m_Condition, and add it to carList
parameter = TextBox1.Text ' => given in input by the user
For each car As Car in carList
If (car.m_Condition = True) then ' => here there must be something to do cause, as things are now, in car.m_Condition is stored a String, but I need to parse it in code that returns a boolean value and, if this value would be True, the code will enter in the If statement.
'do something
End If
Next
Example of car.m_Condition could be:
car.m_Condition = "(car.m_Name=""BMW"" AND car.m_Description.Contains(parameter)) OR car.m_Brand=""AUDI"""
I need some tips on how to implement this approach, if someone would help me.
TY!
EDIT:
Thanks David, I have seen that question and seems a lot similar. I've seen that the user asked how store the entire If in a string:
Dim code As String = "IIf(1 = 2, True, False)"
(the users that asked that question used IIf)
The best way for me would be to store the condition to evaluate in a string and, maybe, the value to with compare the result in another one. So, for example:
Dim condition As String = "(car.m_Name=""BMW"" AND car.m_Description.Contains(parameter)) OR car.m_Brand=""AUDI"""
Dim valueToCompareWith as String = "True"
and the following If
If (car.m_Name=""BMW"" AND car.m_Description.Contains(parameter)) OR car.m_Brand=""AUDI"" = True) Then
'do something
End If
will become:
If (condition = valueToCompareWith) Then '(conceptually, because in this form it's simply a String comparison that returns always False)
'do something
End If
EDIT2:
Thanks Plutonix, I've read about your hint on Getters and Setters, I've not specified it, but the context I'm working on is a lot more complex then this. I've made a very simple example just for focus the problem, but I'm working with a good amount of objects, with various fields to compare and for every comparison there are different conditions in the If, with different logics and different types of data to compare.

subsonic collection

I've written this code to generate a collection. I've tried to filter the collection using subsonic.where but its not working. Actually the where clause will change on user input so i cannot add the where clause to the sqlquery and also the datatable will be filled with different data from the collection based on the user input. How can I acheive this. Also i want the collection to be unchanged so that i use it further to filter with another where clause. Alo the I've selected only two columns but all columns are showing up. Please help.
Dim sq As SB.SqlQuery = New SB.Select("product.prodcode as 'Product Code'").From(DB.Product.Schema)
Dim wh As SB.Where = New SB.Where()
Dim prod As DB.ProductCollection = sq.ExecuteAsCollection(Of DB.ProductCollection)()
wh.ColumnName = DB.Product.ServiceColumn.PropertyName
wh.Comparison = SubSonic.Comparison.NotEquals
wh.ParameterValue = System.Decimal.One
Dim tab As DataTable = prod.Where(wh).Filter().ToDataTable()
Me.GridControl1.DataSource = tab
What you're doing doesn't make much sense - the where needs to go onto the query, then hit the DB - that's the way it should work. If you want to filter after the fact you can use Linq's Where(), which will filter the list for you.

Resources