Ax 2012 X++ Report Controller class caching - axapta

Short Version :
If an error is thrown during the execution of an SSRS report after the associated RDP temporary tables have been updated, the next time the report is run for the same parameters, code execution does not insert fresh data into said tables. Any idea why and how to prevent it?
Long Version :
I am trying to modify the run conditions of a standard report (SalesInvoiceReport) so that the original of any invoice can only be printed once. For all reprints, the "Copy preview" option should be used.
For the most part, the customization is working as intended. I used the CustInvoiceJour.PrintedOriginals field and the CustInvoiceJour.updatePrintedOriginals() (both part of Standard Ax) to maintain the number of originals that have been printed and a slight modification to the SalesInvoiceController.outputReport() method throw an error of an original is being re-printed. Below is the code added to achieve this :
...
// <A147> Code to disable reprints if an original is already printed
isOriginalPrintable = CustInvoiceJour::findRecId(custInvoiceJour.RecId).PrintedOriginals == 0;
srsPrintDestinationSettings = formLetterReport.getCurrentPrintSetting().parmPrintJobSettings();
//srsPrintMediumType = srsPrintDestinationSettings.printMediumType();
if ((printCopyOriginal == PrintCopyOriginal::Original || printCopyOriginal == PrintCopyOriginal::OriginalPrint))
{
// If no originals printed, allow prints.
// Need to re-read the CustInvoiceJour record from the table to get updated data
// If the Original is printed but the prin form is not refreshed, the form's recordset does not update to the latest data.
if(isOriginalPrintable)
{
CustInvoiceJour::updatePrinted(custInvoiceJour, (srsPrintDestinationSettings.numberOfCopies() == 0 ? 1 : srsPrintDestinationSettings.numberOfCopies()));
}
// Don't allow the original document to be printed more than once.
else
{
error(strFmt("#GLS223085", custInvoiceJour.InvoiceId, custInvoiceJour.InvoiceDate));
return;
}
}
// </A147>
...
I placed this in the SalesInvoiceContreller.outputReport() because I found similar code present there as part of standard Ax, albeit for use in specific country regions.
When user uses the Original Preview button, the error is thrown and code execution stops but the RDP temporary tables used to hold the report data have already been updated with report data (specifically the field which hold the Report title is set to Invoice).
When the user reruns the report, this time with the Copy Preview button, code execution somehow skips inserting fresh data into the temp tables (which would set the Report Title field to Invoice Copy) and the first copy output still says Invoice, defeating the requirement.

Related

Cannot add new correlated record to new created record

When I create new record in Google AppMaker and then try to add correlated record to this new one I get this warning in console:
com.google.apps.appmaker.client.datasource.AbstractModelDataSource
WARNING: Could not find element with key RecordKey{key=private$7,
model key=...
Both datasources are set to:
Manual save mode
Automatically load data
The problem doesn't appear when I refresh the page or try to add correlated record to other existing record.
Anybody knows what could be a reason for this error?
App Maker doesn't allow to have unsaved changes on both ends of relation, most likely it is a reason, why you recieve error message in the first case. But in theory it should work once you save one of the relation ends (first save one of the records and then link them and save again):
var countryDs = app.datasources.Country;
var capitalDs = app.datasources.Capital;
countryDs.createItem();
countryDs.item.Name = 'United States';
countryDs.saveChanges(function() {
capitalDs.createItem();
capitalDs.item.Name = 'Washington, D.C.';
capitalDs.item.Country = countryDs.item;
capitalDs.saveChanges();
});
OK, I fixed it.
I have two forms. First to create item. Second to edit data. In the first form page need to be set to:
On Detach: Clear Changes To Datasource.
Datasources need to be set to autosave.

Anywhere re-orders the sequence of the transactions

We noticed that Anywhere is grouping and ordering the transactions such that the transactions for the parent [i.e.: Work Order] were sent first and then the transaction for the child records [e.g.: Specifications]
Scenario:
Step 1. Alter the description on the WO
Step 2. Enter Specification values
Step 3. Change the WO Status to COMP
The resulting transactions are sent as follows
Step1 and Step3 are grouped and sent to Maximo
On success
Step 2 is sent to Maximo
We want the messages to be sent in the same order that they happened and the reason for this is the validations we have in place in Maximo
e.g.: We validate if the child table has records [in our case, we check if the specifications are populated] before we Complete a WO
Due to the re-order of the events\transactions we are unable to COMP a WO from the device as the child transaction never gets to Maximo because the Parent transaction failing due to missing child data [catch 22]
We found the piece of code in the [/MaximoAnywhere/apps/WorkExecution/common/js/platform/model/PushingCoordinatorService.js] JS file that does this re-order and we commented out the reorder
//if (!transaction.json[PlatformConstants.TRANSACTION_LOCK_FORUPDATE])
//{
// Logger.trace("[PUSHING] Trying to shrink/merge transactions and lock transactions");
// var self = this;
// var promise = this._shrinkSubTransactions(metadata, transaction);
//
// Logger.trace("[PUSHING] going to perform async operations");
// promise.then(function() {
// self._pushSubTransactions(transaction, deferred);
// });
//}
//else
//{
Logger.trace("[PUSHING] going to perform async operations");
this._pushSubTransactions(transaction, deferred);
//}
Once this was done we were able to COMP the WO from the device as the events/transaction are now sent in the same order as they occurred
However, we have noticed that this has created another undesirable problem where on an error the device ends up with two Work Orders the one with the error and the one it refetched from Maximo
Scenario: We have an active timer running on the WO and we click on the clock. This will bring up the Stop Timer View and we select [Complete Work]
So there are two things that should happen the timer should be stopped and the status should be changed.
Due to some validation error from Maximo this transaction fails. The result is that we end up with the same wok order twice one with the new status and the error message and one it re-fetched from Maximo
Once we go into the record with the error and undo the change we end up with two identical WOs on the device
Apart from the above issue, there needs to be a way to clear the local data from the device without having to delete the app
You could try putting some Model.save()'s in, or in the app.xml you can force a save when you show/hide a view.
Without the saves I think that everything gets put into one change ... sent as one message ... and you lose control over how it gets unpicked.
Diggging this one up from the grave, but you can create something called a "priority transaction" that will capture all changes and package them in an isolated request and send it back to the server.
westarAssignmentStatusChange:function(workOrder){
workOrder.openPriorityChangeTransaction();
workOrder.set(ATTRIBUTE,VALUE);
workOrder.closePriorityChangeTransaction();
};
This will send an update to the server to change the ATTRIBUTE of the WORKORDER to VALUE.
We used this to isolate changes of specific items and made sure they processed in the appropriate order.

Open print dialog for report from code

I am attempting to force open the print dialog so that all the user has to do is set the email address and press ok. I've found multiple tutorials on how to print a report to file or a printer without the print dialog, but that's not what I'm looking for.
Typically to email a report, the user displays the report, clicks the print icon in the tool bar, and then chooses email and sends it. I want to cut out the first two steps automatically.
This is one of my many attempts at doing this so far, but to no avail.
void emailInvoice()
{
Args args;
ReportRun rr;
Report rb;
PrintJobSettings pjs;
CustInvoiceJour record;
;
select record where record.RecId == 5637175089;
args = new Args("SalesInvoice");
args.record(record);
args.parmEnum(PrintCopyOriginal::OriginalPrint);
// Set report run properties
rr = new ReportRun(args,'');
rr.suppressReportIsEmptyMessage(true);
rr.query().interactive(false);
// set report properties
rb = rr.report();
rb.interactive(true);
// set print job settings
pjs = rr.printJobSettings();
pjs.fileName(strfmt("C:\\Users\\gbonzo\\Desktop\\%1.pdf", record.SalesId));
pjs.fitToPage(true);
// break the report info pages using the height of the current printer's paper
pjs.virtualPageHeight(-1);
// force PDF printing
pjs.format(PrintFormat::PDF);
pjs.setTarget(PrintMedium::Mail);
pjs.viewerType(ReportOutputUserType::PDF);
// lock the print job settings so can't be changed
// X++ code int the report may try to change the destination
// to the screen for example but this does not make
// sense when running a report here
pjs.lockDestinationProperties(true);
// Initialize the report
rr.init();
rr.run();
}
Thanks in advance for your help!
Have you tried to develop a RunBase standard dialog class (RunBaseBatchPrintable if you need to select the printer) that get all the dialog fields you need and from there, programatically run the report passing all the desired parameters? I'm prety sure it will work, and probably will left a cleaner code separating the report of the logic needed to the user interaction.
Read an example here:
http://waikeatng.blogspot.com.es/2010/10/using-runbasebatchprintable-class.html
You have to call the prompt() method of the ReportRun class before calling the run() method.
if (rr.prompt())
{
rr.run();
}
The prompt() method will show the print dialog. If you want it easier for your users you could use the SysMailer class, take a look to the quickSend() method.

report viewer didn't show reports

I try to view reports in my report viewer but it show nothing
my on load function where the report viewer is
like :
if(!IsPostBack)
{
ReportViewer1.ProcessingMode = ProcessingMode.Local;
ReportViewer1.LocalReport.ReportPath = "Reports/MainReport.rdlc";
DataTable orderDt = controller.SelectFullOrderDetailsInfo(orderID);
ReportDataSource rptds = new ReportDataSource("OrderInfo", orderDt);
ReportViewer1.LocalReport.DataSources.Clear();
ReportViewer1.LocalReport.DataSources.Add(rptds);
ReportViewer1.LocalReport.Refresh();
ReportViewer1.DataBind();
}
Report viewer page just run empty and didn't show any thing ! what I'm missing
I know my reply is 5 years after the question is asked :), but I put my findings here, hopefully, it will save time for others in the future.
Recently I have done 60 reports for my company. Bellow are causes of a report not showing:
1: Most Frequent case: You report has parameters which can be empty or null. If you have parameters make sure you specify that those parameters can accept empty or null values, otherwise re[rot shows as empty. To verify, remove all parameters from the report and run it without any parameters.
2: Check rptds Table(report DataSource) to make sure it is populated.
3: (This one happened to a couple of times). Browse your code to make sure you do not set the report viewer visibility to false.

Programicatlly visit (all) ASP.Net page(s) in a website?

In the Security model for out ASP.Net website (.Net 3.5) we store the page name:
page.GetType().Name
as the primary key in a database table to be able to lookup if a user has access to a certain page. The first time a page is visited this record is created automatically in the database.
We have exported these database statements to insert scripts, but each time a new page gets created we have to update the scripts, not a huge issue, but I would like to find an automated way to do this.
I created an attribute that I tagged a few pages with and then wrote a small process to get all the objects that have this attribute, through the reflection create an instance and insert the record using the same code to for page records mentioned above:
IEnumerable<Type> viewsecurityPages = Assembly.GetExecutingAssembly().GetTypes().Where(t => t.IsDefined(typeof(ViewSecurityAttribute),false));
foreach (Type t in viewsecurityPages)
{
object obj = Activator.CreateInstance(t, false);
//clip..(This code just checks if the record already exists in the DB)
if (feature == null)
{
Attribute attb = Attribute.GetCustomAttribute(t, typeof(ViewSecurityAttribute));
if (attb != null)
{
CreateSecurableFeatureForPage((Page)obj, uow, attb.ToString());
}
}
}
The issue is that page.GetType().Name when the page goes through the actual page cycle process is something like this:
search_accounts_aspx
but when I used the activator method above it returns:
Accounts
So the records don't match the in the security table. Is there anyway to programtically "visit" a webpage so that it goes through the actual page lifecycle and I would get back the correct value from the Name parameter?
Any help/reference will be greatly appreciated.
Interesting problem...
Of course there's a (too obvious?) way to programmatically visit the page... use System.Net.HttpWebRequest. Of course, that requires the URI and not just a handle to the object. This is a "how do we get there from here?" problem.
My suggestions would be to simply create another attribute (or use that same one) which stores the identifier you need. Then it will be the same either way you access it, right?
Alternatively... why not just use a 3rd party web spider/crawler to crawl your site and hit all the pages? There are several free options. Or am I missing something?

Resources