viernes, 30 de marzo de 2012

Development -> Preproduction -> Production Server

In theory our preferred server setup would be the title of this blog entry.  We can be a bit less robust with out development environment but the pre' and production servers should be in almost exactly the same state.  However:

What if the client hasn't bought a licence for developing with X++ and relies on their Microsoft Partner to do so.  AND what if the client the partner has is an all singing all dancing licence instead of being exactly the same as their client, plus the development modification.  One of the issues I've found was with deleting company data where we couldn't eliminate company (dataarea) related data as we no longer possessed the licence key for that part of the application.
I assume the error is caused by not possessing the licence key to part of the production module.
Applying the all singing all dancing licence finally allowed us to eliminate the company.  The proper licence was then reapplied once more.  When duplicating company data ensure that you have the 'proper' licence installed before exporting across servers! 

sábado, 10 de marzo de 2012

jueves, 1 de marzo de 2012

Setting up a One-Time Batch Job via X++


Here's the premise.  We are inserting some kind of transaction records and need to generate an alert if a collection of entries arrive that are of interest to a group of users.  Adding code to the XTrans.insert() method is our first step but spamming the users with multiple alerts is not satisfactory and so instead we generate a one time batch job to create just one message containing a list of these transaction.  The job will have a delay of 30 seconds before firing (say) but as new transactions arrive we will reset the launch time to another 30 seconds if we find that there is an existing job still pending execution.  In a high usage Ax installation the delay from 30 seconds would be reduced or quite simply this whole approach would be inappropriate.

I've taken code from the Setting up Batch jobs using X++ blog post and modified it so that the job launches only once.  I've also added code examples of terminating the job via the other two alternative methods (no end date or end by date).  Use of the SysRecurrence class is recommended to update the associated periodic data but note that the recurrence data is a container and therefore the setter functions in this class will return a new container.

A word of warning: debugging the server side batch job is well documented and well tricky!

static server void Tutorial_RunBaseTest(Args _args)
{
    BatchHeader         header;
    SysRecurrenceData   sysRecurrenceData;
    Batch               batch;
    BatchJob            batchJob;
    BatchInfo           processBatchInfo;
    BatchRetries        noOfRetriesOnFailure = 4;
    Tutorial_RunbaseBatch accAlerts_rbb;
    #define.timeInSecondsDelay(30)
    ;

    // Create the Tutorial_RunbaseBatch job, only if one does not exist
    select forupdate batch
        join batchJob
        where batchJob.RecId == batch.BatchJobId
                && batch.ClassNumber == classnum(Tutorial_RunbaseBatch)
                && batchJob.Status == BatchStatus::Waiting
                && batch.Company == curext();
    if (!batch)
    {
        // Setup the RunBaseBatch Job
        header = BatchHeader::construct();
        accAlerts_rbb = Tutorial_RunbaseBatch::construct();
        processBatchInfo = accAlerts_rbb.batchInfo();
        processBatchInfo.parmRetriesOnFailure(noOfRetriesOnFailure);
        header.addTask(accAlerts_rbb);
        // batchJob.OrigStartDateTime = DateTimeUtil::addSeconds(DateTimeUtil::utcNow(), #timeInSecondsDelay);

        // Set the recurrence data
        sysRecurrenceData = SysRecurrence::defaultRecurrence();
        // Start time {now + 30 seconds}
        sysRecurrenceData = SysRecurrence::setRecurrenceStartDateTime(sysRecurrenceData, DateTimeUtil::addSeconds(DateTimeUtil::utcNow(), #timeInSecondsDelay));
        // No end date
        //sysRecurrenceData = SysRecurrence::setRecurrenceNoEnd(sysRecurrenceData);
        // Finish after X times
        sysRecurrenceData = SysRecurrence::setRecurrenceEndAfter(sysRecurrenceData, 1);
        // End by {now + 1 day}
        //sysRecurrenceData = SysRecurrence::setRecurrenceEndAfterDate(sysRecurrenceData, DateTimeUtil::date( DateTimeUtil::addDays(DateTimeUtil::utcNow(), 1) ));

        header.parmRecurrenceData(sysRecurrenceData);
        // Set the batch alert configurations
        header.parmAlerts(NoYes::No, NoYes::Yes, NoYes::No, NoYes::Yes, NoYes::No);
        header.save();
    }
    else
    {
        // Push the exec time back 30 seconds - Avoid spamming the users with multiple warnings.
        ttsbegin;
        select forupdate batchJob
            join batch
            where batchJob.RecId == batch.BatchJobId
                && batch.ClassNumber == classnum(Tutorial_RunbaseBatch)
                && batch.Company == curext();

        sysRecurrenceData = batchJob.RecurrenceData;
        // Start time {now + 30 seconds}
        sysRecurrenceData = SysRecurrence::setRecurrenceStartDateTime(sysRecurrenceData, DateTimeUtil::addSeconds(DateTimeUtil::utcNow(), #timeInSecondsDelay));
        batchJob.RecurrenceData = sysRecurrenceData;
        batchJob.OrigStartDateTime = DateTimeUtil::addSeconds(DateTimeUtil::utcNow(), #timeInSecondsDelay);
        batchJob.update();
        ttscommit;
    }

    // Eliminate previous Cancelled and Finished jobs
    ttsbegin;
    while select forupdate batchJob
        join batch
            where batchJob.RecId == batch.BatchJobId
                && batch.ClassNumber == classnum(Tutorial_RunbaseBatch)
                && (batchJob.Status == BatchStatus::Finished
                    || batchJob.Status == BatchStatus::Canceled)
                && batch.Company == curext()
    {
        batchJob.delete();
    }
    ttscommit;
}