miércoles, 24 de julio de 2013

AX 2012 SSRS error (rsAccessDenied)


Error while setting server report parameters. Error message: The permissions granted to user 'DOMAIN\user' are insufficient for performing this operation. (rsAccessDenied)
This is a well documented error [1], [2] but this happens all of the time.

1. Open IE on the Reporting Server, with Run as Administator…
2. Lets go to http://localhost/reports

3. Select the DynamicsAx installation folder, then Properties, New Role Assignment
Add your AX domain user group or user

4. Check all the checkboxes; Browser, Dynamics Ax Browser, Content Manager, My Reports, Publisher, Report Builder.  This is for a development profile.  Be more restrictive for the production environment.
Click OK.
Format: DOMAIN\AXUserGroup
We now have the users added, and there is no need to restart the services to see the changes.

viernes, 19 de julio de 2013

Unknown source for data method CrmIntegrationStatus. The method does not exist in the data source or table.

Error. Applying AX 2012, CU6 to an AX 2012 CU1 instance,  the following error was generated after compilation:

Unknown source for data method CrmIntegrationStatus. The method does not exist in the data source or table.
Also, we found the following items created in the USR layer?!



Solution: Create the following method in the SalesQuotationProjTable:

// Fix to allow compile of SalesQuotationProjTable form
display NoYes CrmIntegrationStatus()
{
    return NoYes::No;
}

Yuck!

Comments to resolve the problem properly most welcome.  In this case new functionality was added with this new version regarding a Microsoft CRM integration, that our client would not be requiring, and perhaps our method should be throwing an 'unimplemented' error.

jueves, 23 de mayo de 2013

createrecidindex

I find myself using the createrecidindex property more and more on my tables in AX 2009, perhaps due to my growing experience with the AX 2012 product.  One small omission from the table properties documentation in the MSDN is the actual name of the index - RecId.

static EVE_NewsTemplate find(RefRecId _refRecId, boolean _forUpdate = false)
{
    EVE_NewsTemplate      tbl;
    ;

    if (_refRecId > 0)
    {
        tbl.selectForUpdate(_forUpdate);

        select firstOnly tbl
            index hint RecId
            where tbl.RecId == _refRecId;
    }

    return tbl;
}

Reading the AX 2012 best practices it seems to indicate that there is always an index created on the RecId field.

lunes, 1 de abril de 2013

Obligatory Field

I've been working on another team's project and stumbled upon the following version history of a class that I was editing.

Version history comments, by four different authors.

I'm a bit of a Nazi code commentator, I'll admit to that, but 'm'?  I sent an email (containing the letter 'm') to a few of the team members but got no feedback as to what the meaning of 'm' signifies nor if it was part of internal standards or practices.

Happy April Fools Day, says I.

lunes, 19 de noviembre de 2012

Ax's white space aversion

I can think of a few places where I would like to output some nice white space to the end user, cushioning their existence from the vagaries of life...  But Ax 2009 isn't letting me:

  • In table fields. I can confirm that trailing spaces are removed, which is a shame when you want to store pre generated rows of data that are to be exported to plain text files, in a host system that will then parse fixed length fields, padded with either zeros or white space.
  • In the label text. Try as I might, that important white space from the right is cruelly taken away from me. My formatting and design of Ax forms is taken to new heights of frustration and anger. Well, perhaps I exaggerate a little after all we should be changing the margin fields and not manually adding spaces to the labels after the full colon.
  • Directly in the editor, the white space is removed from the end of the lines.  It doesn't interfere normally, but when using the @ operator for strings it can upset your precious formatting.

Use the \n in strings to create a new line... And use labels.

I've discovered this aversion to trailing white space from Microsoft years ago ever since my Hotmail account's password used to have a trailing white space in it... And now it doesn't any more. Same password but minus the w/s. Is white space actually dangerous?

miércoles, 14 de noviembre de 2012

Problem of the slow loading customer invoice journal

The title makes this blog post sound like a Scooby Doo caper.

With the GLP-EE (Eastern Europe) patch installed in an Ax 2009 SP 1 instance we found that the customer invoice journal was loading slower and slower.  The form was nothing short of painful.  It seemed to be okay when working with invoices from Spain, Morocco, Italy...

After restarting both Ax and MSSQLServer instances it was time to roll my sleeves up and investigate with the SQL Server Profiler.  While you and I may still be an amateur with the tool a quick perusal of the output identifies the problem query.  After the restart of Ax I relaunched the profiler and immediately opened the invoice journal.  Finding the problematic sql can be difficult due to the use of sp_cursorfetch, hiding from us the original query.  The sp_cursorprepexec is eventually identified, and we can see the sql used in the cursor that is being reused all the time.

Next up was shouting at the silicon gods and then creating a new table index (CustInvoiceTrans) within Ax, to include a new field introduced from the aforementioned patch - RefReturnInvoiceTrans_W.
Maybe should have just added the field to the InvoiceIdx, huh?
With our new index we can compare query times and the difference is huge.


All that I can say is that Microsoft owes me a beer.

miércoles, 31 de octubre de 2012

Bug: Version Control


Not using version control on your code is like playing Russian Roulette over the whole lifetime of your project.  You might not 'lose' anything at first, but later on you may regret the omission.

I had a strange bug upon opening the Ax Client related the to the inbuilt version control system, and didn't know where it came from
Translation: Error
A quick look in the logs, indicated the cause of the problem...

   Object Server 01:  
   [Microsoft][SQL Native Client][SQL Server]El nombre de columna 'MODIFIEDBY' no es válido. 
   INSERT INTO SYSVERSIONCONTROLMORPHXITE2541 (ITEMPATH,MODIFIEDBY,CREATEDBY,RECVERSION,RECID) OUTPUT INSERTED.CREATEDDATETIME VALUES (?,?,?,?,?) 

What fool did this then?...  Me!
For some reason we had added the ModifiedBy, ModifiedDatetime, etc, etc fields to the entity!  I may have done this accidentally when I added the fields to entities such as LedgerTable, TaxTable, TaxData...  Some of them had been added to the version control, and some of them were not.

miércoles, 24 de octubre de 2012

Adjusting Sales/Purchase Tax

When importing invoices from an external system you find penny differences between how tax is calculated between these two applications.  Integrating thousands of invoices can surmount to a whole free beer a day appearing on your ledger books, much to the disdain of our client's accountant.  A decision was therefore taken to send me to New Zealand for the week to fix the problem and force Ax to accept some different tax rounding rules.

A penny difference in how Ax and an external system calculates taxes.
When registering a sales invoice it is possible to manually adjust the tax amount, per tax code.
This is what we want to do, via X++.
Fortunately a blogger called KiwiAxGuy has already achieved half of the work for us, adjusting tax on journals and free text invoices.  No need for those plane tickets after all!

static void XXX_AdjustSalesTaxesJob(Args _args)
{    
    //Manually adjust sales taxes
    TaxRegulation       taxRegulation;
    SalesTable          salesTable;
    SalesTotals         salesTotals;
    ;
    salesTable         = SalesTable::find('12A0012');

    if (salesTable)
    {
        salesTotals = SalesTotals::construct(salesTable, SalesUpdate::All);
        salesTotals.calc();

        // Launch the form to manually adjust the taxes (TaxTmpWorkTrans)
        // --------------------------------------------------------------
        //Tax::showTaxes(salesTotals.tax(), salesTable);

        // Or do it by X++
        // ---------------
        ttsBegin;
        // EDIT: I've just realised that this method has been modified in the GLP-EE patch
        // taxRegulation = TaxRegulation::newTaxRegulation(salesTotals.tax(), null);
        taxRegulation = TaxRegulation::newTaxRegulation(salesTotals.tax(), null, salesTable, null, null);
        taxRegulation.allocateAmount(498.54);
        taxRegulation.saveTaxRegulation();
        ttsCommit;
    }
}


The same can be done for purchase invoices.  In my example once more we have a penny difference in the tax calculation, it should be 13.60.
Purchase order, with 14.60 PLN of taxes we need to apply

static void XXX_AdjustPurchTaxesJob(Args _args)
{
    TaxRegulation       taxRegulation;
    PurchTable          purchTable;
    PurchTotals         purchTotals;
    ;
    purchTable         = PurchTable::find('129/2012');

    if (purchTable)
    {
        purchTotals = PurchTotals::newPurchTable(purchTable, PurchUpdate::All);
        purchTotals.calc();

        // Launch the Form to manually adjust the taxes (TaxTmpWorkTrans)
        // --------------------------------------------------------------
        //Tax::showTaxes(purchTotals.tax(), purchTable);

        // Or do it by X++
        // ---------------
        ttsBegin;
        taxRegulation = TaxRegulation::newTaxRegulation(purchTotals.tax(), null, null, purchTable, null);
        if (taxRegulation.taxLinesExist())
        {
            taxRegulation.allocateAmount(13.60);
            taxRegulation.saveTaxRegulation();
        }
        ttsCommit;
    }
}

HOWEVER.  What happens when we have two tax codes in the same invoice?

Two tax codes, one value to change.
For me it didn't work.  The TaxRegulation class allocated the change in amount to the second tax line, but didn't save the changes in the underlying temporary table :(  Check out the knowledge base article KB2028633 as I'm confident that it's the same issue (more Ax 2009, GLP-EE pains).
You people with the AX 2009 Eastern European patch have been warned.

Our changes are all recorded in the TaxWorkRegulation entity, should you need to check your changes across invoices.
TaxWorkRegulation - The entity that stores the adjusted taxes we have applied... Where is my second line?
BUT, "After posting is successful, all records in this table related to the transaction are deleted." and so within TaxTrans we have the columns SourceTaxAmountCur (calculated sales tax amount shown in the current currency) and SourceRegulateAmountCur (amount that the sales tax is being adjusted to) to compare.

Edit:  Found another possible bug, or it's my lack of knowledge.  Registering changes to taxes for the Purch* entities, I could only do it by changing the HeadingTableId and HeadingRecId values to that of PurchParmTable (343), and not PurchTable (345).  This is probably something to do with rearranging the purchase lines.

Remember people:  Taxes are hard.  Let's go tax-free shopping!

sábado, 22 de septiembre de 2012

When buf2buf is not enough

When the global buf2buf is not enough! Copying two heterogeneous table buffers, but with practically the same field names.
Edit: Peter Chan beat me to it! The only advantage to using my function is an extra semi colon :)
Neither of our methods check to see if the field type is the same...
///Copy two different table buffers but with the same field names.
///May be better to use a Map...
static void bufFields2BufFields(
    Common  _from,
    Common  _to
    )
{
    DictTable   dictTableFr = new DictTable(_from.TableId);
    FieldId     fieldIdFr;
    DictTable   dictTableTo = new DictTable(_to.TableId);
    FieldId     fieldIdTo;
    FieldName   fieldName;
    ;
    fieldIdFr   = dictTableFr.fieldNext(0);
    fieldName   = dictTableFr.fieldName(fieldIdFr);
    fieldIdTo   = dictTableTo.fieldName2Id(fieldName);

    while (fieldIdFr && ! isSysId(fieldIdFr))
    {
        if (fieldIdTo)
        {
            _to.(fieldIdTo)     = _from.(fieldIdFr);
        }
        
        fieldIdFr           = dictTableFr.fieldNext(fieldIdFr);
        fieldName           = dictTableFr.fieldName(fieldIdFr);
        fieldIdTo           = dictTableTo.fieldName2Id(fieldName);
    }
}

miércoles, 5 de septiembre de 2012

AX 2012 Number Sequence, X++

It's feels like having to learn all of this stuff over again.  Here is a brief overview, and there is a white paper available as well, for AX 2012.

static void tutorial_NumberSequenceJob(Args _args)
{
    // AX 2012 Number Sequence, X++
    // Only for sequences marked as continuous
    // ---------------------------------------
    NumberSequenceReference nsRef;    
    NumberSeqScope    scope;
    ProductReceiptId  receipt;
    
    scope      = NumberSeqScopeFactory::createDataAreaScope(curext());
    nsRef      = NumberSeqReference::findReference(extendedTypeNum(VendAccount), scope);
    if (nsRef)
    {
        receipt         = NumberSeq::newGetNum( nsRef ).num();
    }
    
    info(strFmt("New Supplier Id is '%1'", receipt));
}