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.
Related
I have added a field to the Prepare Replenishment form that needs to be updated when a user selects a row in the Replenishment Item grid. How do I access the field. I know it is in the INReplenishmentFilterExt but I can't figure out how to get access to the extension.
Edit #1: I am able to get the value of the field but it does not update on the screen when I use cache.SetValue. I am trying to update this filter extension field from inside of the Selected event handler.
protected void INReplenishmentItem_Selected_FieldUpdating(PXCache cache, PXFieldUpdatingEventArgs e)
{
var row = (INReplenishmentItem)e.Row;
if (row == null)
return;
INReplenishmentFilter filter = Base.Filter.Current;
INReplenishmentFilterExt filterExt = PXCache<INReplenishmentFilter>.GetExtension<INReplenishmentFilterExt>(filter);
decimal poAmount = filterExt.UsrPOAmount.HasValue ? filterExt.UsrPOAmount.Value : 0;
decimal lastPrice = pvi.LastPrice.HasValue ? pvi.LastPrice.Value : 0;
decimal newPOAmount = poAmount + lastPrice;
cache.SetValue<INReplenishmentFilterExt.usrPOAmount>(filterExt, newPOAmount);
}
You cannot set the value of the Filter using the Cache of INReplenishmentItem.
I have edited my answer, the code below should work.
//Always use virtual methods for Event Handlers
protected virtual void INReplenishmentItem_Selected_FieldUpdating(PXCache sender, PXFieldUpdatingEventArgs e)
{
//Try not to use vars. Especially when you know the Type of the object
INReplenishmentItem row = e.Row as INReplenishmentItem;
if (row != null)
{
INReplenishmentFilter filter = Base.Filter.Current;
INReplenishmentFilterExt filterExt = PXCache<INReplenishmentFilter>.GetExtension<INReplenishmentFilterExt>(filter);
decimal poAmount = filterExt.UsrPOAmount ?? 0;
decimal lastPrice = pvi.LastPrice ?? 0;//Not sure what pvi is
decimal newPOAmount = poAmount + lastPrice;
//"sender" is the cache that specifically stores the datatype of row
//Therefor you cannot use it to update records of a different datatype
//You also should not pass an Extension into the argument that should be the row object you are trying to update
Base.Filter.Cache.SetValueExt<INReplenishmentFilterExt.usrPOAmount>(filter, newPOAmount);
}
}
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.
I have a custom rest control written in Java. Everything works fine for string fields, but not for multi-value string fields. My code returns
"[Value1, Value2, Value3...]". Note there are no commas around the values. On the web page the output looks like this:
If I can get commas around the values, the front-end framework can easily parse it.
I have tried to parse the value from the field and to get it formatted correctly, but cannot seem to get it right.
The first set of code works for a string. The second is an attempt to work for a multi-value field.
Any help would be greatly appreciated.
writer.startProperty("chgTitle");
writer.outStringLiteral(String.valueOf(columnValues.get(0)));
writer.endProperty();
writer.startProperty("plpAffected");
Object tmpObj = columnValues.get(5);
if (tmpObj instanceof Vector) {
String[] copyArr = new String[((Vector) tmpObj).size()];
((Vector) tmpObj).copyInto(copyArr);
writer.outStringLiteral(String.valueOf(copyArr));
} else {
}
writer.endProperty();
I would recommend a slightly different approach. I like to force my JSON structures to use an array anywhere a multiple value situation is possible. It also looks like you're using Philippe Riand's specialized JSON writer, so I'll assume that.
Here's about how I would attack it:
writer.startProperty("chgTitle");
writer.outStringLiteral(String.valueOf(columnValues.get(0)));
writer.endProperty();
writer.startProperty("plpAffected");
Vector<?> tmpVec = Util.getValueAsVector(columnValues.get(5));
writer.startArray();
for ( Object ob : tmpVec ) {
writer.startArrayItem();
// assuming String contents
writer.outStringLiteral(String.valueOf(ob));
writer.endArrayItem();
}
writer.endArray();
writer.endProperty();
To wrap the returning columnValues, I'm using a Java equivalent of my SSJS getValueAsVector helper function. It checks for Vector or ArrayList, which I happen to use almost exclusively; if it's not, it shoves it into a new Vector. Here's what that method looks like,
public static Vector<?> getValueAsVector(Object obj) {
if(obj instanceof java.util.Vector){
return (Vector<?>) obj;
}else if( obj instanceof ArrayList ) {
List<?> o = (List<?>) obj;
Vector<Object> tmpVec = new Vector<Object>();
for(int i=0;i<o.size();i++){
tmpVec.add(o.get(i));
}
return tmpVec;
}else {
Vector<Object> tmpVec = new Vector<Object>();
tmpVec.add(obj);
return tmpVec;
}
}
Is there a way to programmatically access the Label & Value fields that has been created as a custom Field in MS CRM Dynamics please?
I have added a custom field called "new_producttypesubcode" which, for example, has 2 options, Trophy = 1000000 and Kit = 10000001.
I am writing an import utility that mirrors products between the customers website and their CRM and I want to get a list of all possible product options in the CRM to see if they are matched in the website.
So, in essence I want to...
get the list of possible new_producttypesubcodes and their corresponding values.
Iterate through the product variants in the website.
if the product variant name matches any name in the list of new_producttypecodes then add the value 1000000
So, if I find a product added to the website and its marked as a "Trophy" and "Trophy" exists in the CRM then new OptionSetValue(100000001)
I hope that makes sense...
Thanks
This function retrieves a dictionary of possible values localised to the current user. Taken from: CRM 2011 Programatically Finding the Values of Picklists, Optionsets, Statecode, Statuscode and Boolean (Two Options).
static Dictionary<String, int> GetNumericValues(IOrganizationService service, String entity, String attribute)
{
RetrieveAttributeRequest request = new RetrieveAttributeRequest
{
EntityLogicalName = entity,
LogicalName = attribute,
RetrieveAsIfPublished = true
};
RetrieveAttributeResponse response = (RetrieveAttributeResponse)service.Execute(request);
switch (response.AttributeMetadata.AttributeType)
{
case AttributeTypeCode.Picklist:
case AttributeTypeCode.State:
case AttributeTypeCode.Status:
return ((EnumAttributeMetadata)response.AttributeMetadata).OptionSet.Options
.ToDictionary(key => key.Label.UserLocalizedLabel.Label, option => option.Value.Value);
case AttributeTypeCode.Boolean:
Dictionary<String, int> values = new Dictionary<String, int>();
BooleanOptionSetMetadata metaData = ((BooleanAttributeMetadata)response.AttributeMetadata).OptionSet;
values[metaData.TrueOption.Label.UserLocalizedLabel.Label] = metaData.TrueOption.Value.Value;
values[metaData.FalseOption.Label.UserLocalizedLabel.Label] = metaData.FalseOption.Value.Value;
return values;
default:
throw new ArgumentOutOfRangeException();
}
}
So you would then need to do something like:
Dictionary<String, int> values = GetNumericValues(proxy, "your_entity", "new_producttypesubcode");
if(values.ContainsKey("Trophy"))
{
//Do something with the value
OptionSetValue optionSetValue = values["Trophy"];
int value = optionSetValue.Value;
}
Yes, that data is all stored in the metadata for an attribute (SDK article). You have to retrieve the entity metadata for the entity and then find the attribute in the list. Then cast that attribute to a PicklistAttributeMetadata object and it will contain a list of options. I would mention that typically retrieving Metadata from CRM is an expensive operation, so think about caching.
private static OptionSetMetadata RetrieveOptionSet(IOrganizationService orgService,
string entityName, string attributeName)
{
var entityResponse = (RetrieveEntityResponse)orgService.Execute(
new RetrieveEntityRequest
{ LogicalName = entityName, EntityFilters = EntityFilters.Attributes });
var entityMetadata = entityResponse.EntityMetadata;
for (int i = 0; i < entityMetadata.Attributes.Length; i++)
{
if (attributeName.Equals(entityMetadata.Attributes[i].LogicalName))
{
if (entityMetadata.Attributes[i].AttributeType.Value ==
AttributeTypeCode.Picklist)
{
var attributeMD = (PicklistAttributeMetadata)
entityMetadata.Attributes[i];
return attributeMD.OptionSet;
}
}
}
return null;
}
Here is how to write the options to the console using the above call.
var optionSetMD = RetrieveOptionSet(orgService, "account", "accountcategorycode");
var options = optionSetMD.Options;
for (int i = 0; i < options.Count; i++)
{
Console.WriteLine("Local Label: {0}. Value: {1}",
options[i].Label.UserLocalizedLabel.Label,
options[i].Value.HasValue ? options[i].Value.Value.ToString() : "null");
}
I believe this works for global option set attributes as well, but if you know it is a global option set there is a different message for it that would probably a bit more efficient (SDK article).
I have write a custom field that extends SPFieldLookup. I set AllowMultipleValues = true;
Here is Field Control Value:
public override object Value
{
get
{
EnsureChildControls();
SPFieldLookupValueCollection vals = new SPFieldLookupValueCollection();
ICollection s = TinBaiLienQuanPicker.SelectedIds;
if (s != null && s.Count > 0)
{
foreach (var i in s)
{
ListItem z = availableItems.Find(x => (x.Value == i.ToString()));
if (z != null)
{
vals.Add(new SPFieldLookupValue(int.Parse(z.Value), z.Text));
}
}
}
return vals;
}
set
{
EnsureChildControls();
base.Value = value as SPFieldLookupValueCollection;
}
}
When control save field data, I see it return a collection which have multiple value.
But when I retrieve data again, I receive only the first value. I get value from Control Field ' ListItemFieldValue property.
Please give me a tip. Thank you very much.
Data storage logic is different in the database for lookup field with one value compared to multiple values. Check that in the field type definition xml ParentType is set to LookupMulti instead of Lookup.
You have to inherit the field control class from MultipleLookupField not from LookupField. Are you sure you are doing this?