I am attempting to create a menu very similar to the 'Reports' menu on the 'Invoices/Memos' form on the 'Contact' form.
I have successfully added the menu button, and populated it with one item (via 'Automation Steps') so that the menu appears with my menu item and successfully launches my report.
My report has a ContactID parameter, as illustrated:
I have a customization created with the following code:
namespace PX.Objects.CR
{
public class ContactMaint_Extension : PXGraphExtension<ContactMaint>
{
public PXAction<Contact> letters;
[PXUIField(DisplayName = "Letters", MapEnableRights = PXCacheRights.Select)]
[PXButton(SpecialType = PXSpecialButtonType.Report)]
protected virtual IEnumerable Letters(PXAdapter adapter, string reportID)
{
PXReportRequiredException ex = null;
Contact contact = Base.Caches[typeof(Contact)].Current as Contact;
var parameters = new Dictionary<string, string>();
parameters["Contact.ContactID"] = contact.ContactID.ToString();
ex = PXReportRequiredException.CombineReport(ex, reportID, parameters);
//this.Save.Press();
if (ex != null) throw ex;
return adapter.Get();
}
}
}
However, the resulting report does not seem to have the Contact.ContactID parameter passed to it.
I have gotten where I am by loosely interpreting the post here.
Could someone help me out? I would appreciate it!
To pass a parameter value to a report you need to use the Parameter name. In your example its just "ContactID" so you can set it like this...
parameters["ContactID"] = contact.ContactID.ToString();
If you need to use non parameter fields by DAC.Field I think you need to add them to Viewer Fields in your report. Then you can use it like you had it (parameters["MyDac.MyFieldName"] = "somevalue").
I had to add viewer fields recently to call a report by non parameter fields. This is the only way I could get that to work. Otherwise the parameters just require the call by parameter name.
Here is an example from Vendor Maintenance calling a vendor report where the report has a parameter named "VendorID":
Dictionary<string, string> parameters = new Dictionary<string, string>();
parameters["VendorID"] = vendor.AcctCD;
throw new PXReportRequiredException(parameters, "AP632500", AP.Messages.BalanceByVendor);
The example I used recently to call a report not using a parameter (using a viewer field) was the shipment confirmation report. It calls multiple shipments which is why a single parameter will not work. It uses the PXReportRequiredException.CombineReport call to append multiple shipments to a single report call/exception. Sample:
//SOShipementEntry.Report(PXAdapter,string)
PXReportRequiredException ex = null;
// Loop on shipments
// ...
parameters["SOShipment.ShipmentNbr"] = order.ShipmentNbr;
// ...
ex = PXReportRequiredException.CombineReport(ex, actualReportID, parameters);
// ...
// End shipments loop
if (ex != null) throw ex;
Related
I am using a PXSmartPanel to display a dialog allowing a user to enter a string. I would like to use a 'Non-persisted field', but that means (I think) that I would have to get the field value by calling the field on the Panel and extracting its value.
The text field's ID is cstFieldSSN and the non-persisted field's ID is UsrSSN
My method looks like this:
(I'm calling the dialog upon clicking a menu item)
// Initialize 'myPanel'
public PXFilter<PX.Objects.CR.Contact> myPanel;
// Make the 'Letters' menu available to 'Automation Steps'
public PXAction<PX.Objects.CR.Contact> letters;
[PXUIField(DisplayName = "Letters", MapEnableRights = PXCacheRights.Select)]
[PXButton(SpecialType = PXSpecialButtonType.Report)]
protected virtual IEnumerable Letters(PXAdapter adapter, string reportID)
{
if (myPanel.AskExt(true) != WebDialogResult.OK) return;
PXReportRequiredException ex = null;
Contact contact = Base.Caches[typeof(Contact)].Current as Contact;
Dictionary<string, string> parameters = new Dictionary<string, string>();
parameters["ContactID"] = contact.ContactID.ToString();
/** Here's the issue **/
parameters["SSN"] = myPanel.Current.UsrSSN;
throw new PXReportRequiredException(parameters, reportID, "");
if (ex != null) throw ex;
return adapter.Get();
}
I'm getting
'PX.Objects.CR.Contact' does not contain a definition for 'UsrSSN' and no extension method 'UsrSSN' accepting a first argument of type 'PX.Objects.CR.Contact' could be found (are you missing a using directive or an assembly reference?)
Could someone help me out or point me to a resource?
Thanks to #Brendan, my final code looks like this:
// Initialize 'myPanel'
public PXFilter<PX.Objects.CR.Contact> myPanel;
// Make the 'Letters' menu available to 'Automation Steps'
public PXAction<PX.Objects.CR.Contact> letters;
[PXUIField(DisplayName = "Letters", MapEnableRights = PXCacheRights.Select)]
[PXButton(SpecialType = PXSpecialButtonType.Report)]
protected virtual IEnumerable Letters(PXAdapter adapter, string reportID)
{
// Launch the PXSmartPanel dialog and test result
if (myPanel.AskExt(true) == WebDialogResult.OK)
{
PXReportRequiredException ex = null;
Contact contact = Base.Caches[typeof(Contact)].Current as Contact;
Dictionary<string, string> parameters = new Dictionary<string, string>();
//*** Get the extended class
var myExt = myPanel.Current.GetExtension<ContactExt>();
parameters["ContactID"] = contact.ContactID.ToString();
//*** Get the extended class's custom field value
parameters["SSN"] = myExt.UsrSSN;
throw new PXReportRequiredException(parameters, reportID, "");
if (ex != null) throw ex;
}
return adapter.Get();
}
But I also had to set the CommitChanges property on the text field to True so that the value would be pushed back to the cached Contact, allowing me to use it.
I'm currently looking for a way to dynamically create a FormDialog from values predefined in the database. In other words, my field types, prompts and settings are all stored in a database, and what I'm trying to achieve is reading those settings and building the appropriate form dynamically.
What I tried so far is something similar to the following. Suppose I have a form with a Name (string) and an Age (int) field (FieldDefinition is a class I created to store the parameters of a field, assuming they are fetched from the database) (The code is stripped just to illustrate the idea):
public static IForm<dynamic> BuildForm()
{
string FormMessage = "Welcome to demo contact form!";
string CompletionMessage = "Thank your for your info. Our team will contact you as soon as possible.";
var fields = new List<FieldDefinition>()
{
new FieldDefinition()
{
Name = "Name",
FieldType = typeof(string),
Prompts = new string[] { "What's your name?", "Please input your name" }
},
new FieldDefinition()
{
Name = "Age",
FieldType = typeof(int),
Prompts = new string[] { "What's your age?", "How old are you?" }
}
};
var builder = new FormBuilder<dynamic>();
builder.Message(FormMessage);
foreach (var f in fields)
{
builder.Field(
new FieldReflector<dynamic>(f.Name)
.SetType(f.FieldType)
);
}
builder.AddRemainingFields()
.OnCompletion(async (context, order) => {
var message = context.MakeMessage();
message.Text = CompletionMessage;
await context.PostAsync(message);
});
return builder.Build();
}
So here's the problems:
I thought I could use a dynamic type. But a method cannot return a dynamic object as it is determined at run-time. Therefore, I got an error when I tried building the form using the following:
dynamic values; var form = new FormDialog<dynamic>(values, ContactForm.BuildForm, FormOptions.PromptInStart, null);`
I need to create the properties of the object dynamically, therefore I looked for a way to create a Type on runtime. I ended up with something called TypeBuilder but I was a bit skeptical if it could solve my problem or not.
Therefore, I guess the ultimate start is by using the FieldReflector but I have no idea how to achieve this. I'm looking for something similar to the above but that does actually work.
Have you looked at FormBuilderJson? You could dynamically construct the .json string, and build the form at runtime:
public static IForm<JObject> BuildJsonForm()
{
string fromFlowJson = GetFormFlowJson();
return new FormBuilderJson(schema)
.AddRemainingFields()
.Build();
}
See here for more information: https://learn.microsoft.com/en-us/azure/bot-service/dotnet/bot-builder-dotnet-formflow-json-schema?view=azure-bot-service-3.0
I have properly overwrite commit in InstallerSetup.cs I do not wish to write the user entered value to app.config but rather I want to pass the string Context.Parameters["TESTPARAMETER"]; to another class in form1.cs on load function. I tried string test = InstallerSetup.Context.Parameters["TESTPARAMETER"];
but getting InstallerSetup.Context is null. Please Help.
InstallerSetup.cs
public static string SQLSERVERNAME = "";
public static string HMSTENANTDB;
public static string SQLLOGIN;
public static string SQLPASSWORD;
public override void Commit(IDictionary savedState)
{
base.Commit(savedState);
try
{
SQLSERVERNAME = Context.Parameters["SQLSERVERNAME"];
HMSTENANTDB = Context.Parameters["HMSTENANTDB"];
SQLLOGIN = Context.Parameters["SQLLOGIN"];
SQLPASSWORD = Context.Parameters["SQLPASSWORD"];
}
catch (Exception e)
{
MessageBox.Show("Failed to update the application configuration file : " + e.Message);
base.Rollback(savedState);
}
}
from1.cs
InstallerSetup InsSetup = new InstallerSetup();
string Vsqlserver = InsSetup.Installers.Count.ToString();
string Vtenant = "";
if (InsSetup.Context != null)
{
Vtenant = InsSetup.Context.Parameters["HMSTENANTDB"];
}
else
{
Vtenant = "context is null";
}
As far as I can tell, the issue is that the property values are not being passed into the custom action. That would be the most obvious explanation. A commentfrom the poster says:
"passed those parameters to the custom action...................................... SQLSERVERNAME = Context.Parameters["SQLSERVERNAME"];
etc...
//................there is only these 4 lines in my custom actions"
which is essentially repeating the code that was previously posted.
This is NOT passing the values into the custom action. This is retrieving values which must already have been passed into the custom action.
Assuming that the custom action has been correctly added to (typically) the install nod of the custom action, and also assuming that the property names are in a TextBoxes dialog in the install, the values must be passed in to the custom action via the CustomActionData settings. To use one example, the CustomActionData setting must be something like:
/SQLSERVERNAME=[SQLSERVERNAME]
or /SQLSERVERNAME=[EDITA1] if EDIOTA1 is being used because that's the default property name.
However there is no reference to the TextBoxes (or any other) install dialog in the original question, so it's not really clear where the value of (say) SQLSERVERNAME is supposed to come from. It may be passed in on the msiexec command line, for example.
At the moment I'm working with Word.dotx files that hold several bookmarks which are being altered by a c# program.
For a Rebranding project I need to add several new bookmark fields and my predecessor code does reference to the Text Form Field Legacy Control inside Office Word 2010.
I create a new Text Form Field with Field Settings Bookmark pointed to TestBookmark1. I'm already aware of a certain bug that the bookmarkname of a text form field can contain max 20 chars.
When I run the testcode, the existing bookmarks are replaced perfectly while it crashes on the new bookmarks. The exception I receive here is "The range cannot be deleted"
The code that is used for replacing the bookmark goes as follows:
public void ReplaceBookmark(string bookmarkName, string text)
{
try
{
var bookmarks = GetProperty("Bookmarks", _wordDoc); //worddoc is the Word.Document equivalent in late binding
var exists = InvokeMember("Exists",
bookmarks,
new object[]
{
bookmarkName
}) != null && (bool)InvokeMember("Exists",
bookmarks,
new object[]
{
bookmarkName
});
if (!exists)
return;
var bookmark = InvokeMember("Item",
bookmarks,
new object[]
{
bookmarkName
});
var range = GetProperty("Range", bookmark);
SetProperty("Text", range, text);
InvokeMember("Add",
bookmarks,
new[]
{
bookmarkName, range
});
}
catch
{
CloseWord(false);
throw;
}
}
The exception get's thrown at SetProperty("Text", range, text);
private static void SetProperty(string propertyName, object instance, object value)
{
if (instance == null)
return;
var type = instance.GetType();
type.InvokeMember(propertyName,
BindingFlags.SetProperty,
null,
instance,
new[]
{
value
});
}
When going deeper here it falls on the type.InvokeMember function.
I already saw a likewise solution found Here, But this example uses the Early binding principle that I for company reasons cannot use.
This leaves me with the following questions:
Am i adding the bookmarks incorrectly, or am i simply forgetting something?
Why do i get the "Range cannot be Deleted Exception"?
When i catch this specific error, is there another way to replace the bookmark?
Thanks in advance
I found it, probably another Office scam...
when adding a new Text Form Field you have the option to add the properties. In the Field Settings you can set the bookmark.
This however doesn't complete the bookmark thingy.
After setting the Text Form Field properties you still need to go to
Insert Tab => Tools Group => Bookmark => Select the correct bookmark (standard correctly highlighted) and press Add.
It's and sounds stupid, but I clearly didn't do the last steps.
greets
I've added a boolean parameter called IsNewReport to some of my reports and I'm trying get a list of these reports using the SSRS web service (ReportService2005.asmx).
But I keep getting an exception thrown by the web service "The IsNewReport field has a value that is not valid."
So how should I setup the SearchCondition to find my reports?
Heres an example of the webservice call:
var reports = ReportingService2005.FindItems("/MyReports",
BooleanOperatorEnum.Or, GetSearchConditions());
And here's an example of the GetSearchCondition method
private static SearchCondition[] GetSearchConditions()
{
List<SearchCondition> conditions = new List<SearchCondition>();
SearchCondition searchCondition = new SearchCondition();
searchCondition.Condition = ConditionEnum.Equals;
searchCondition.ConditionSpecified = true;
searchCondition.Name = "IsNewReport";
searchCondition.Value = "true";
conditions.Add(searchCondition);
return conditions.ToArray();
}
PS I've tried this code changing the property name to "Name" and a value matching the name of one the existing reports and this works without exception,.
Ok I've got it - you can't use FindItems to search though a reports parameters only the properties of the report. "Name" worked because it's a property of the report where as IsNewReport is a parameter that I've added to the report.