Anywhere re-orders the sequence of the transactions - maximo-anywhere

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.

Related

How to make Kaa log upload event based instead of time based

I've only recently started to work with KaaIoT and I am wondering if there is another way to store a log bucked to the server.
/* some headers */
static void main_callback(void *context)
{
kaa_user_log_record_t *log_record = kaa_logging_time_collection_create();
log_record->test_time = kaa_string_copy_create("some_time");
kaa_logging_add_record(kaa_client_get_context(context)->log_collector, log_record, NULL);
}
/* some other configuration */
error = kaa_client_start(kaa_client, main_callback, kaa_client, 5);
When I execute this code, the string "some_time" will be stored to the server every 5 seconds.
I was wondering if there was an other way to do this, like upload the log to the server when I press my 'enter' key? But I can't seem to find a command for this.
To my understanding kaa_logging_add_record, just add the record to the storing bucket waiting to be sent according to the logging strategy you have defined. (https://kaaproject.github.io/kaa/autogen-docs/client-c/v0.10.0/kaa__logging_8h.html#af0fadc09a50f5e38603271a08c581417) . The parameter 5 sec in kaa_client_start is only a delay to cycle the call back function. If you want to register an event, first you have to store it in the log bucket and the timestamp if you want to record at what time happened. If you want to notify at the moment, the I think you should use Notifications or Events. I am also scratching my head in something similar and I wonder if there is a better way.

Deleting user account triggers deletion of another nodes

Db structure:
--followers
-followedUser1
-user1
-followedUser2
-user1
-user2
--users
-user1
-followed
-followedUser1
-followedUser2
-user2(followedUser1)
-followed
-followedUser2
-user3(followedUser2)
Everytime user follows(onCreate) & unfollows(onDelete) under followers/{followedUser}/{followerUser} path, it triggers function which increment or derement count and assigning or detaching posts from follower. It's working via fanout method, and there're no problems. Now, worse part comes when some user deletes account completely together with detaching his followers from himself(because his account will be a ghost), i've set trigger onDelete to indicate whenever it'll happen, then iterating through this user's (i.e.user3) followers removes himself from corresponding followers plus his account, it looks like this then:
--followers
-followedUser1
-user1
-followedUser2
-user1
-user2
--users
-user1
-followed
-followedUser1
-user2(followedUser1)
Now, problematic part - when promise returns i'd like to also remove whole follower/followedUser2(because it's a ghost now) path but... there is a trigger which unfortunately executes for every follower under(onDelete). So, is there any chance to remove path above(levelup) deletetion trigger without triggering children itself? Or any other approach would be great, thanks
edit: Dont get me wrong, it'll work but if number of followers under followedUser would be "a lot" server will die after 100... trigger
At this time, there is no way to filter what deletion events will trigger the function, so you are correct, the function will be triggered once for each user the deleted user was following. I recognize this is one of many use cases where such functionality would be useful, so if you get a chance, please fill out a feature request here.
It looks like your multiple-update problem can be fixed with a multi-location updates
In very quickly hacked together and not tested typescript:
export const cleanupFollowers = functions.auth.user().onDelete(event => {
const user = event.data.userId;
const followersNode = admin.database().ref(`followers/${user}`);
const followers = _.keys(await followersNode.once('value'));
// Every follower also has a reverse node for this user. Get the list of keys:
const reverseNodesToDelete = followers.map(follower => `followers/${follower}/${user}`);
// Model this update as a map of deep key -> null to delete all at once
let cleanup = _.keyBy(reverseNodesToDelete, null);
// add one more update: deleting full node for the deleted user.
cleanup[`followers/${user}`] = null;
// do all deletions as one database request:
return admin.database().ref().update(cleanup);
}
Note that this will still fire your counting function, but that should be fine to run in parallel. It probably makes your app simpler to have each invariant captured separately.

Paypal Processing - Need to grab TransactionId, CorrelationId and TimeStamp

Current Project:
ASP.NET 4.5.2
MVC 5
PayPal API
I am using this example to build myself a PayPal transaction (and yes, my code is virtually identical), as I do not know of any other method that will return the three values in the title.
My main problem is that, the example I am utilizing is much more concise and compact than the one I used for a much older Web Forms application, and as such, I am unsure as to where or even how to grab the three values I need.
My initial thought was to do so right after the ACK, and indeed I was able to obtain the CorrelationId as well as the TimeStamp, but because this was prior to the user being carted off to PayPal’s site (sandbox in this case -- see the return new PayPalRedirect contained within the if), the TransactionId was blank. And in this example, PayPal explicitly redirects the user to a Success page without returning to the Action that sent the user to PayPal in the first place, and I am not seeing any GET values in the URL at all aside from the Token and the PayerId, much less ones that could provide me with the TransactionId.
Suggestions?
I have also looked at the following examples:
For ASP.NET Core, was unsure how to adapt to my current project particularly due to appsettings.json, but it looked quite well done. I really liked how the values were rolled up in lists.
For MVC 4, but I couldn’t find where ACK was being used to determine success or successwithwarning so I couldn’t hook into that.
I have also found the PayPal content to be like trying to drink from a fire hose at full blast -- not only was the content was hopelessly outdated (Web Forms code, FTW!) but there was also so many different examples it would have taken me days to determine which one was most appropriate to use.
Any assistance would be greatly appreciated.
Edit: my initial attempt at modifying the linked code has this portion:
values = Submit(values);
var ack = values["ACK"].ToLower();
if(ack == "success" || ack == "successwithwarning") {
using(_db = new ApplicationDbContext()) {
var updateOrder = await _db.Orders.FirstOrDefaultAsync(x => x.OrderId == order.OrderId);
if(updateOrder != null) {
updateOrder.OrderProcessed = false;
updateOrder.PayPalCorrelationId = values["CORRELATIONID"];
updateOrder.PayPalTransactionId = values["TRANSACTIONID"];
updateOrder.PayPalTimeStamp = values["TIMESTAMP"];
updateOrder.IPAddress = HttpContext.Current.Request.UserHostAddress;
_db.Entry(updateOrder).State = EntityState.Modified;
await _db.SaveChangesAsync();
}
}
return new PayPalRedirect {
Token = values["TOKEN"],
Url = $"https://{PayPalSettings.CgiDomain}/cgi-bin/webscr?cmd=_express-checkout&token={values["TOKEN"]}"
};
}
Everything within and including the using() is my added content. As I mentioned, the CorrelationId and the TimeStamp come through just fine, but I have yet to successfully obtain the TransactionId.
Edit 2:
More problems -- the transactions that are “successful” through the sandbox site (the ReturnUrl is getting called) aren’t reflecting properly on my Facilitator and Buyer accounts, even when I do payments straight from the buyer’s PayPal account (not using the Credit Card). I know I am supposed to see transactions in the Buyer’s account, either through the overall Dev account (Accounts -> Profile -> balance or Accounts -> Notifications) or through the Buyer’s account in the sandbox front end. And yet -- multiple transactions returning me to the ReturnUrl path, and yet no transactions in either.
Edit 3:
Okay, this is really, really weird. I have gone over all settings with a fine-toothed comb, and intentionally introduced errors to see where things should crap out. It turns out that the entire process goes swimmingly - except nothing shows up in my notifications and no amounts get moved between my different accounts (Facilitator and Buyer). It’s like all my transactions are going into /dev/null, yet the process is successful.
Edit 4: A hint!
In the sandbox, where Buyer accepts the transaction, there is a small note, “You will be able to review the transaction before completing it” or something like that -- suggesting that an additional page is not coming up and that the user is being uncerimoniously dumped back to the success page. Why the success page? No clue. But it’s happening.
It sounds like you are only doing the first part of the process.
Express Checkout consists of 3 API calls:
SetExpressCheckout
GetExpressCheckoutDetails
DoExpressCheckoutPayment
SEC generates a token, and then you redirect to PayPal where the user signs in and reviews the transactions before agreeing to pay.
They are then sent to the ReturnURL included in your SEC request, and this is where you'll call GECD in order to obtain all the buyer details that are now available since they signed in.
Using that data you can complete the final DECP request, which is what finalizes the procedure. No money is actually processed until this final call is completed successfully.

Ax 2012 X++ Report Controller class caching

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.

Dynamics AX 2009 AIF Tables

Background
I have an issue where roughly once a month the AIFQueueManager table is populated with ~150 records which relate to messages which had been sent to AX (where they "successfully failed"; i.e. errorred due to violation of business rules, but returned an exception as expected) over 6 months ago.
Question
What tables are involved in the AIF inbound message process / what order to events occur in? e.g. XML file is picked up and recorded in the AifDocumentLog, data's extracted and added to the AifQueueManager and AifGatewayQueue tables, records from here are then inserted in the AifMessageLog, etc.
Thanks in advance.
There are 4 main AIF classes, I will be talking about the inbound only, and focusing on the included file system adapter and flat XML files. I hope this makes things a little less hazy.
AIFGatewayReceiveService - Uses adapters/channels to read messages in from different sources, and dumps them in the AifGatewayQueue table
AIFInboundProcessingService - This processes the AifGatewayQueue table data and sends to the Ax[Document] classes
AIFOutboundProcessingService - This is the inverse of #2. It creates XMLs with relevent metadata
AIFGatewaySendService - This is the inverse of #1, where it uses adapters/channels to send messages out to different locations from the AifGatewayQueue
For #1
So #1 basically fills the AifGatewayQueue, which is just a queue of work. It loops through all of your channels and then finds the relevant adapter by ClassId. The adapters are classes that implement AifIntegrationAdapter and AifReceiveAdapter if you wanted to make your own custom one. When it loops over the different channels, it then loops over each "message" and tries to receive it into the queue.
If it can't process the file for some reason, it catches exceptions and throws them in the SysExceptionTable [Basic>Periodic>Application Integration Framework>Exceptions]. These messages are scraped from the infolog, and the messages are generated mostly from the receive adaptor, which would be AifFileSystemReceiveAdapter for my example.
For #2
So #2 is processing the inbound messages sitting in the queue (ready/inprocess). The AifRequestProcessor\processServiceRequest does the work.
From this method, it will call:
Various calls to Classes\AifMessageManager, which puts records in the AifMessageLog and the AifDocumentLog.
This key line: responseMessage = AifRequestProcessor::executeServiceOperation(message, endpointActionPolicy); which actually does the operation against the Ax[Document] classes by eventually getting to AifDispatcher::callServiceMethod(...)
It gets the return XML and packages that into an AifMessage called responseMessage and returns that where it may be logged. It also takes that return value, and if there is a response channel, it submits that back into the AifGatewayQueue
AifQueueManager is actually cleared and populated on the fly by calling AifQueueManager::createQueueManagerData();.

Resources