Showing posts with label bug. Show all posts
Showing posts with label bug. Show all posts

Tuesday, February 20, 2024

Project intercompany invoice sales tax calculation bug in 10.0.37

 While Microsoft working on this issue, let me show you how you can fix it in the current code running on 10.0.37 or lower.

Scenario

When using the project intercompany customer invoice form in Project management and accounting module , you can create a customer invoice to bill to an intercompany customer. As the customer is intercompany, there is a legal entity (LE) associated with that customer. When posting the intercompany customer invoice, a pending vendor invoice is created in the customer’s LE with the same lines from the customer invoice.





You can see calculated Sales taxes via Sales tax button













Issue

If you try to add more lines, it will be added with line number 1, and sales taxes won't be added with this newly added line. Note: If you delete any line here and then you add more, it works fine.

This bug leads to missed taxes transactions in all TaxUncommitted, TmpTaxWorkTrans, and eventually in TaxTrans tables

Fix

Create an extension class with the following code



[ExtensionOf(classStr(ProjIntercompanyCustomerInvoiceCreator))]
final class myProjIntercompanyCustomerInvoiceCreator_Extension
{
    
    public CustInvoiceTable createInvoice()
    {
        // we need to increase the next line number to avoid duplicates in case when new lines added to initially created ones
        lineNum             = this.myGetNextLineNum();

        custInvoiceTable    = next createInvoice();

        if(origTransList.elements())
        {
            TaxUncommitted::deleteForDocumentHeader(tableNum(CustInvoiceTable), custInvoiceTable.RecId);
        }
        
        return custInvoiceTable;
    }


    public LineNum myGetNextLineNum()
    {
        CustInvoiceLine custInvoiceLine;
        
        select maxof(lineNum) from custInvoiceLine
            where custInvoiceLine.ParentRecId == custInvoiceTable.RecId;

        return custInvoiceLine.LineNum + 1;
    }

}

Wednesday, March 31, 2021

Corrupted copy of SSRS report

Sometimes after copying an existing SSRS report or even just a part of its design, parser starts encountering strange errors. For example:

Unidentifiable substring 'Value' in expression. the parser reported error message

There must be something wrong in the mechanism responsible for copying text boxes. In most case, it replaces simple function names with predicates like Microsoft.Value etc. In my case it turned out to be even worse: some bug in strings concatenation; no matter what.

The problem here is that you have no hint about the object name, where such a non-conform string was added. You can try your textboxes one by one by checking all their properties with functions inside: visibility, font, border etc, even labels for its placeholder! And it can become a nightmare when your report contains dozens of them.

Fortunately there is a some workaround. You can open XML files for both of the original report and your copy of it in Notepad++ and run Compare over them (Install this Compare plugin).

Then scroll down to the first bad guy: a couple of strings above you will see its name.

Go back to the editor, find this textbox.

Now check its corrupted property function expressions and fix it.



Friday, June 12, 2020

General journals log update bugs

I found a couple questionable things in General journal log updating.

First, Log field limit of 255 characters. It is evidently not enough for relatively long information to be stored for eventual analysis.

Second, the way to update journal logs by copying lines from Infolog afterwards.

Anyway the standard methods meant to shorten the log content do not work properly. Let's see why.





And the method to find a voucher number can speak one language only.





So, we get a poorly built journal log.


It can be fixed by two extensions.

First, let's gather all lines in a set so that they would be present once only.


/// <summary>
/// Extension of LedgerJournalCheckPost to fix a bug
/// </summary>
[ExtensionOf(classStr(LedgerJournalCheckPost))]
final class LedgerJournalCheckPost_Extension
{    

    /// <summary>
    /// Updates the infolog for the given transaction.
    /// </summary>
    /// <param name = "_ledgerJournalTrans">The transaction.</param>
    public void updateTransInfoLog(LedgerJournalTrans _ledgerJournalTrans)
    {
        this.myUpdateTransInfoLog(_ledgerJournalTrans);
        // transLogPoint is already moved forward; therefore the next call will do no change to the log
        next updateTransInfoLog(_ledgerJournalTrans);
    }

    /// <summary>
    /// Updates the infolog for the given transaction.
    /// </summary>
    /// <param name = "_ledgerJournalTrans">The transaction.</param>
    private void myUpdateTransInfoLog(LedgerJournalTrans _ledgerJournalTrans)
    {
        #Define.UserTab('\t')

        Log             logTxt;
        Integer         x = transLogPoint;
        Integer         y = infolog.num(0);
        str             currentLine;
        str             strLine;
        Voucher         voucher;
        List            list;
        ListEnumerator  listEnumerator;
        Set             setAllLines = new Set(Types::String);
        // <GEERU>
        Log             tableLogTxt;
        #ISOCountryRegionCodes
        boolean         countryRegion_RU = SysCountryRegionCode::isLegalEntityInCountryRegion([#isoRU]);
        // </GEERU>

        if (postingResults)
        {
            postingResults.parmLedgerPostingMessageLog(ledgerPostingMessageCollection);
        }

        while (x < y)
        {
            x++;
            // parse all the lines if there are some prefixes
            currentLine     = infolog.text(x);
            list            = strSplit(currentLine, #UserTab);
            listEnumerator  = list.getEnumerator();
            while (listEnumerator.moveNext())
            {
                currentLine = listEnumerator.current();
                if(setAllLines.in(currentLine))
                {
                    continue;
                }
                setAllLines.add(currentLine);
                // <GEERU>
                if (countryRegion_RU)
                {
                    logTxt      =  currentLine + '\r\n';
                    tableLogTxt += logTxt;
                }
                else
                {
                    // </GEERU>
                    logTxt += currentLine + '\r\n';
                    // <GEERU>
                }
                // </GEERU>

                if (logTxt && postingResults != null)
                {
                    if (_ledgerJournalTrans.Voucher == '')
                    {
                        voucher = LedgerJournalCheckPostResults::getVoucherFromLogText(currentLine);
                        if (voucher == '')
                        {
                            // continue because calling the LedgerJournalCheckPostResults.updateErrorLog
                            // method with a blank voucher has terrible performance and will not change the results
                            continue;
                        }
                    }
                    else
                    {
                        voucher = _ledgerJournalTrans.Voucher;
                    }

                    postingResults.updateErrorLog(voucher, logTxt);
                }
            }
        }

        // <GEERU>
        if (countryRegion_RU)
        {
            logTxt = tableLogTxt;
        }
        // </GEERU>

        if (logTxt)
        {
            tableErrorLog += logTxt;
        }

        transLogPoint = y;
     }

}

Second, teach the voucher searching method to speak any language and do not return rubbish instead of a real voucher number.


/// <summary>
/// Extension of LedgerJournalCheckPostResults to fix a bug
/// </summary>
[ExtensionOf(classStr(LedgerJournalCheckPostResults))]
final class LedgerJournalCheckPostResults_Extension
{    
    /// <summary>
    /// See the description for the standard method
    /// </summary>
    /// <param name = "_logText">log text</param>
    /// <returns>Voucher text</returns>
    public static Voucher getVoucherFromLogText(Log _logText)
    {
        // this standard function does not works correctly
        next getVoucherFromLogText(_logText);
        return LedgerJournalCheckPostResults::myGetVoucherFromLogText(_logText);
    }

    /// <summary>
    /// Finds voucher numbers as it should be in all languages
    /// </summary>
    /// <param name = "_logText">Log text</param>
    /// <returns>Voucher text</returns>
    private static Voucher myGetVoucherFromLogText(Log _logText)
    {
        List            list;
        ListEnumerator  listEnumerator;
        Voucher         voucher;
        str             strLine;
        const str       voucherText = '@GeneralLedger:Voucher';
        int             voucherLen  = strLen(voucherText);

        list = strSplit(_logText, ',');

        listEnumerator= list.getEnumerator();
        while (listEnumerator.moveNext())
        {
            strLine = listEnumerator.current();
            if (strContains(strLine, voucherText))
            {
                // delete the word VOUCHER in any language as well as all the spaces around
                voucher = strLRTrim(strDel(strLine, 1, voucherLen));
                break;
            }
        }
        return voucher;
    }

}

Now it looks much better and contains condensed information.


For storing more information, a new memo field can be a solution.

Hope, Microsoft will eventually come up with an optimized approach.

Good logging!


Friday, March 13, 2020

Visual Studio 2015 crash with "The supplied SnapshotPoint is on an incorrect snapshot" error

This error is really annoying bug in VS2015 when you barely touch your code, and the VS crashes all the time.

Thanks Joris, there is a workaround in VS Options:


Thursday, November 14, 2019

Hidden Rules form part in Policy form

Just discovered that the Policy rules form part is not properly shown in the Policy form.

If you want to get it back, create an extension to SysPolicyListPage and change Part location to Auto





Once it is done, this part will be visible for all kinds of policies, e.g. Purchasing etc.

Friday, May 3, 2019

D365 bug: The transactions on voucher do not balance. Process source document lines in parallel

The transactions on voucher do not balance as per 5/3/2019. (accounting currency: -13.34 - reporting currency: -13.34)

Does it look familiar? Yes, it still happens in D365, while you try to post a partial invoice matching one of product receipts.

A simple scenario. We confirmed a purchase order with one line of quantity 3 and price $10. Receive 1 and then 2. Then try to invoice the first line only.





If you get this error, it is probably because of the general ledger parameter.



Switch it off and try again.

Technical details. Unfortunately I did not manage to find an exact reason, but what I can see is that there are two different ways of creation accounting distribution.

SourceDocumentProcessor.submitSourceDocumentLinesForHeader


When it comes to the parallel creation via TransitionRequest, D365 generates some unbalanced debit and credit records (on the left), but it does not happen when you post a full invoice against both receipts at once (on the right).



There is another article about this parameter, which can be of your interest, too.

The call stack for the aforementioned method call.


> Dynamics.AX.SourceDocumentation.1.netmodule!Dynamics.AX.Application.SourceDocumentStateInProcess.transitionSourceDocumentLines() Line 592 X++
  Dynamics.AX.SourceDocumentation.1.netmodule!Dynamics.AX.Application.SourceDocumentStateInProcess.`doTransition() Line 132 X++
  Dynamics.AX.SourceDocumentation.1.netmodule!Dynamics.AX.Application.SourceDocumentStateBase.`transition() Line 196 X++
  Dynamics.AX.SourceDocumentation.1.netmodule!Dynamics.AX.Application.SourceDocumentStateBase.`performTransitionOrMaintenance() Line 133 X++
  Dynamics.AX.SourceDocumentation.1.netmodule!Dynamics.AX.Application.SourceDocumentStateBase.transitionTo() Line 290 X++
  Dynamics.AX.SourceDocumentation.1.netmodule!Dynamics.AX.Application.SourceDocumentProcessorBase.`submit() Line 133 X++
  Dynamics.AX.SourceDocumentation.1.netmodule!Dynamics.AX.Application.SourceDocumentProcessor.`submit() Line 231 X++
  Dynamics.AX.SourceDocumentation.1.netmodule!Dynamics.AX.Application.SourceDocumentProcessor.`submitSourceDocumentImplementation(Dynamics.AX.Application.SourceDocumentHeaderImplementation _sourceDocumentImplementation, bool _updateImplementation, Dynamics.AX.Application.SourceDocumentAccountingStatus _targetSourceDocumentAccountingStatus, Dynamics.AX.Application.SourceDocumentProcessorCaller _caller, Dynamics.AX.Application.SourceDocumentLineItemList _sourceDocumentLineItemList, string _user, bool _doTransitionToCompletedStateAccountFullyQualifiedValidation, bool _doAsynchronousLineProcessing, bool @_updateImplementation_IsDefaultSet, bool @_targetSourceDocumentAccountingStatus_IsDefaultSet, bool @_caller_IsDefaultSet, bool @_sourceDocumentLineItemList_IsDefaultSet, bool @_user_IsDefaultSet, bool @_doTransitionToCompletedStateAccountFullyQualifiedValidation_IsDefaultSet, bool @_doAsynchronousLineProcessing_IsDefaultSet) Line 378 X++
  Dynamics.AX.SourceDocumentation.1.netmodule!Dynamics.AX.Application.SourceDocumentProcessorFacade.`submitSourceDocumentImplementation(Dynamics.AX.Application.SourceDocumentHeaderImplementation _sourceDocumentImplementation, bool _updateImplementation, Dynamics.AX.Application.SourceDocumentAccountingStatus _targetSourceDocumentAccountingStatus, Dynamics.AX.Application.SourceDocumentProcessorCaller _caller, string _user, bool _doTransitionToCompletedStateAccountFullyQualifiedValidation, Dynamics.AX.Application.SourceDocumentLineImplementationList _sourceDocumentLineImplementationList, bool _doAsynchronousLineProcessing, bool @_updateImplementation_IsDefaultSet, bool @_targetSourceDocumentAccountingStatus_IsDefaultSet, bool @_caller_IsDefaultSet, bool @_user_IsDefaultSet, bool @_doTransitionToCompletedStateAccountFullyQualifiedValidation_IsDefaultSet, bool @_sourceDocumentLineImplementationList_IsDefaultSet, bool @_doAsynchronousLineProcessing_IsDefaultSet) Line 245 X++
  Dynamics.AX.ApplicationSuite.83.netmodule!Dynamics.AX.Application.PurchInvoiceJournalPost.`endLedgerVoucher() Line 1333 X++
  Dynamics.AX.ApplicationSuite.36.netmodule!Dynamics.AX.Application.FormletterJournalPost.post() Line 1622 X++
  Dynamics.AX.ApplicationSuite.36.netmodule!Dynamics.AX.Application.FormletterJournalPost.`run() Line 2277 X++
  Dynamics.AX.ApplicationSuite.37.netmodule!Dynamics.AX.Application.FormletterService.`postJournal(Microsoft.Dynamics.Ax.Xpp.Common _parmTable) Line 714 X++
  Dynamics.AX.ApplicationSuite.37.netmodule!Dynamics.AX.Application.FormletterService.`createAndPostJournal() Line 1708 X++
  Dynamics.AX.ApplicationSuite.37.netmodule!Dynamics.AX.Application.FormletterService.`processJournal(Dynamics.AX.Application.Printout _printout) Line 1684 X++
  Dynamics.AX.ApplicationSuite.37.netmodule!Dynamics.AX.Application.FormletterService.`processFormLetterParmTable(Dynamics.AX.Application.Printout _printout) Line 1476 X++
  Dynamics.AX.ApplicationSuite.37.netmodule!Dynamics.AX.Application.FormletterService.`runSingleThread(Dynamics.AX.Application.Printout _printout) Line 1508 X++
  Dynamics.AX.ApplicationSuite.37.netmodule!Dynamics.AX.Application.FormletterService.`run() Line 1634 X++
  Dynamics.AX.ApplicationSuite.37.netmodule!Dynamics.AX.Application.FormletterService.`postPurchaseOrderInvoice(Dynamics.AX.Application.PurchFormLetterInvoiceContract _contract) Line 836 X++
  [External Code] 
  Dynamics.AX.ApplicationFoundation.9.netmodule!Dynamics.AX.Application.SysOperationServiceController.runOperationInstance(Dynamics.AX.Application.SysOperationServiceController controller) Line 780 X++
  Dynamics.AX.ApplicationFoundation.9.netmodule!Dynamics.AX.Application.SysOperationServiceController.runOperationSynchronously() Line 695 X++
  Dynamics.AX.ApplicationFoundation.9.netmodule!Dynamics.AX.Application.SysOperationServiceController.`run() Line 581 X++
  Dynamics.AX.ApplicationSuite.37.netmodule!Dynamics.AX.Application.FormLetterServiceController.`run() Line 847 X++
  Dynamics.AX.ApplicationSuite.83.netmodule!Dynamics.AX.Application.PurchFormLetter.`run() Line 1526 X++
  Dynamics.AX.ApplicationPlatform.4.netmodule!Dynamics.AX.Application.SysOperationSandbox.operationExecutionWrapper(object[] serializedOperation) Line 311 X++
  [External Code] 

Thursday, April 18, 2019

D365: Bug in Payment method lookup in Expense report form

You cannot select a payment method in an Expense report line, if you opt for any category containing comma separated value.



There is a bug in standard TrvExpenseLookupHelper class in paymentMethodLookupQuery method: it must use old good queryValue() method.




Otherwise you will get a few values instead of one in the range. Compare them before and after the fix.



[ExtensionOf(classStr(TrvExpenseLookupHelper))]
final static class myTrvExpenseLookupHelper_Extension
{
    /// <summary>
    /// A fix for the standard method which does not treat comma separated cost type correctly
    /// </summary>
    /// <param name = "_costType"></param>
    /// <param name = "_excludeImportOnly"></param>
    /// <returns></returns>
    public static Query paymentMethodLookupQuery(TrvCostTypeEDT _costType, boolean _excludeImportOnly)
    {
        Query                   query = next paymentMethodLookupQuery(_costType, _excludeImportOnly);
        QueryBuildDataSource    qbPayMethod, qbValidatePayment;
        TrvCostType             trvCostType;

        trvCostType = TrvCostType::find(_costType);

        query = new Query();

        qbPayMethod = query.addDataSource(tableNum(TrvPayMethod));

        if (trvCostType)
        {
            query.queryType(QueryType::Join);

            qbValidatePayment = qbPayMethod.addDataSource(tableNum(TrvValidatePayment));
            qbValidatePayment.addLink(fieldNum(TrvPayMethod,PayMethod),fieldNum(TrvValidatePayment,PayMethod));
            qbValidatePayment.joinMode(JoinMode::ExistsJoin);
            //it should be converted by standard queryValue()
            qbValidatePayment.addRange(fieldNum(TrvValidatePayment, CostType)).value(queryValue(trvCostType.CostType));
        }

        if (_excludeImportOnly)
        {
            QueryBuildRange qbr = qbPayMethod.addRange(fieldNum(TrvPayMethod, AutomaticPayment));
            qbr.value(queryValue(NoYes::No));
            qbr.Status(RangeStatus::Hidden);
        }

        return query;
    }

}

Friday, April 12, 2019

Dangerous bug in CustCollectionsPoolsListPage form AX2012/D365

There is a form CustCollectionsPoolsListPage where two data sources are outer joined to the root data source with no relations (no links).



If by any reason the initial query does not contain links for these two aforementioned, SQL starts producing a Cartesian product and generating a huge temporary table. The latter can potentially lead to SQL server crash, like it happened in our environment.



The following fix may leave much to be desired but at least it creates needed links in case they are absent.

On CustTable data source we have to add an additional check for the existing query.


public void executeQuery()
{
    element.populateAgingIndicators(selectedCustAging);

    // Use the query from the cue?
    if (!useInitialQuery 
                        // Begin
                        || !this.wblCheckQuery(this.query())
                        // End: 
                        )
    {
        this.query(element.addOriginalPoolQuery(listPageHelper.getCurrentPoolQuery()));
    }


    super();

    element.setButtonAccess();
    element.setGridColumnLabels();
}


// to avoid the cartesian product in case of absent link for this outer join
private boolean wblCheckQuery(Query _query)
{
    QueryBuildDataSource custAgingDs;
    QueryBuildDataSource custAgingLegalEntityDs;
    boolean ret = true;

    custAgingDs             = _query.dataSourceName(#CustAgingDsName);
    custAgingLegalEntityDs  = _query.dataSourceName(#CustAgingLegalEntityName);
    if (!custAgingDs || !custAgingLegalEntityDs || custAgingDs.linkCount() <= 0 || custAgingLegalEntityDs.linkCount() <= 0)
    {
        ret = checkFailed("Saved query is corrupted. Try to recreate the cue");
    }
    return ret;
}

From SQL perspective we can catch such an issue by the following query.


use tempdb
select * from sys.dm_db_session_space_usage spu
join sys.dm_exec_sessions s on s.session_id = spu.session_id
join sys.dm_exec_requests r on s.session_id = r.session_id
cross apply sys.dm_exec_sql_text(sql_handle) t 
order by internal_objects_alloc_page_count desc

Friday, April 5, 2019

AX2012-D365 Bug: TrvWorkflowExpLines query misses fields

The issue appears once you post project or inter-company expenses.

Once you approved and never opened Accounting distribution form for an expense, Project activity number field will be empty, or, for the Inter-company case, even the project information will be lost.






This is how TrvWorkflowExpLines query should look like to overcome the issue.


Wednesday, January 23, 2019

bugs in SysLookupMultiSelectGrid class and form

You can find a lot of example about how to support multiple selection lookups. AX 2012/D365 provide us with SysLookupMultiSelectGrid class and form to implement such scenarios.

There are two bugs however still existing in the standard code.

SysLookupMultiSelectGrid class, method lookup() must be as follows in order to refresh Query in case QueryRun is given.


// the standard method does not update the query in case if queryrun is given
public static void wblLookup(Query _query, FormStringControl _ctrlIds, FormStringControl _ctrlStrs, container _selectField, queryRun _queryRun = null)
{
    SysLookupMultiSelectGrid    lookupMS = new SysLookupMultiSelectGrid();

    lookupMS.parmCallingControlId(_ctrlIds);
    lookupMS.parmCallingControlStr(_ctrlStrs);
    lookupMS.parmQuery(_query);
    lookupMS.parmQueryRun(_queryRun);
    if(_queryRun)
    // Begin: Alexey Voytsekhovskiy
    {
        lookupMS.parmQuery(_queryRun.query());
    }
    // End: Alexey Voytsekhovskiy
    lookupMS.parmSelectField(_selectField);
    lookupMS.run();
}

SysLookupMultiSelectGrid form, method executeQuery() on common data source must be as follows in order not to consider referenced data sources, which may come with a given query.


public void executeQuery()
{
    QueryRun qr;
    Query lookupMultiSelectQueryCopy = new Query(lookupMS.parmQuery());
    FormRun formRun;
    FormDataSource formDataSource;
    int dsCount, i = 1;
    Common formDataSourceCursor, queryRunCursor;

    // Always use the query defined on the SysLookupMultiSelectGrid class. Note that a copy is used
    // so that any modifications made to the Query by the Form at runtime aren't fed back through
    // the next time the lookup is presented to the user. (The Query is used to define which fields
    // are present on the Form during lookup construction. Therefore, if any fields are added at runtime
    // by the Forms engine, duplicated or non-original-Query defined fields may be presented on the
    // 2nd or later presentations of the lookup if a copy isn't used.)
    this.query(lookupMultiSelectQueryCopy);

    // check if user has set any queryrun. If yes, that means the cursors are set by user explicitly,
    // usually the case where query contains tmp tables and needs to be populated.
    qr = lookupMS.parmQueryRun();
    if(qr)
    {
        formRun = this.formRun();
        dsCount = formRun.dataSourceCount();

        // get data source from query run, get the cursor and set it on the form data source cursor.
        for(i = 1; i<=dsCount; i++)
        {
            formDataSource = formRun.dataSource(i);
            if(formDataSource
                            // Begin: Alexey Voytsekhovskiy
                            // we don't need a reference data source here!
                            && !formDataSource.isReferenceDataSource()
                            // End: Alexey Voytsekhovskiy
                            )
            {
                // get form data source cursor and set the queryrun cursor as data on it.
                formDataSourceCursor = formDataSource.cursor();
                queryRunCursor = qr.get(formDataSourceCursor.TableId);
                if(queryRunCursor)
                {
                    if(queryRunCursor.isTempDb() || queryRunCursor.isTmp())
                    {
                        formDataSourceCursor.setTmpData(queryRunCursor);
                    }
                    else
                    {
                        formDataSourceCursor.data(queryRunCursor);
                    }

                }
            }
        }
    }

    super();
}

Thursday, March 1, 2018

AX 2012 R3 CU13 Bug: DMF Bundle batch processing misses records

If you load many records via DMF, the most probable scenario is that you would divide them by setting a number of threads.

Unfortunately, there is a bug with it in the CU13.



When this batch job ends you will find 8*1041 records processed only.


As you can see the records ids are calculated correctly but the processing method gets wrong variables instead.


Down in the code, the batch task itself is set for a wrong number of records, too.

This is how to fix this bug.




There are two lessons we can take from this story:
- do not touch the code if it works;
- use really good names for your variables (it is not strong-typed environment!)

AX 2012 R3 CU13 Bug: DMF default value mapping for String type

There is bug in AX 2012 R3 CU13.

If you check Default type for your data entity staging mapping field of String type, then the system tries to find any first record matching to "Default" value as XML name to get the linked field and compare against its string length.






To fix it, we need to make the following changes in three methods of the form DMFStagingDefaultTable


public class FormRun extends ObjectRun
{
    SysDictField             dictField;
    DMFFieldType             dmfFieldType;
    DMFSourceXMLToEntityMap  dmfSourceXMLToEntityMap;
    Types                    valueType;
    // Begin: Alexey Voytsekhovskiy
    int                     myMaxStrSize;
    // End: Alexey Voytsekhovskiy
}


public void init()
{
    Args                                args;
    DMFEntity                           entity;
    DMFDefinitionGroupEntityXMLFields   dmfDefinitionGroupEntityXMLFields;
    SysDictType                         dictType;

    #define.Integer('Integer')
    #define.String('String')
    #define.RealMacro('Real')
    #define.DateMacro('Date')
    #define.DateTimeMacro('UtcDateTime')
    #define.Time('Time')

    super();

    if (!element.args() || element.args().dataset() != tableNum(DMFSourceXMLToEntityMap))
    {
        throw error(strfmt("@SYS25516", element.name()));
    }

    args = element.args();

    dmfSourceXMLToEntityMap = args.record();
    // Begin: Alexey Voytsekhovskiy
    select firstOnly FieldType, FieldSize from dmfDefinitionGroupEntityXMLFields
        where dmfDefinitionGroupEntityXMLFields.FieldName == dmfSourceXMLToEntityMap.EntityField
        && dmfDefinitionGroupEntityXMLFields.Entity == dmfSourceXMLToEntityMap.Entity
        && dmfDefinitionGroupEntityXMLFields.DefinitionGroup == dmfSourceXMLToEntityMap.DefinitionGroup
        ;
    myMaxStrSize = dmfDefinitionGroupEntityXMLFields.FieldSize ? dmfDefinitionGroupEntityXMLFields.FieldSize : 256;
    // End: Alexey Voytsekhovskiy
    if (dmfDefinitionGroupEntityXMLFields.FieldType && !dmfSourceXMLToEntityMap.IsAutoDefault)
    {
        dmfFieldType = dmfDefinitionGroupEntityXMLFields.FieldType;
        if (dmfsourceXMLToEntityMap.EntityField)
        {
            entity = DMFEntity::find(dmfsourceXMLToEntityMap.Entity);
            dictField = new SysDictField(tableName2id(entity.EntityTable), fieldName2id(tableName2Id(entity.EntityTable),dmfsourceXMLToEntityMap.EntityField));
        }
        switch(dmfDefinitionGroupEntityXMLFields.FieldType)
        {
            case #Integer:
                DMFStagingConversionTable_StagingIntValue.visible(true);
                break;
            case #String:
                DMFStagingConversionTable_StagingStrValue.visible(true);
                break;
            case #RealMacro:
                DMFStagingConversionTable_StagingRealValue.visible(true);
                break;
            case #DateMacro:
                DMFStagingConversionTable_StagingDateValue.visible(true);
                break;
            case #DateTimeMacro:
                DMFStagingConversionTable_StagingUtcDataTime.visible(true);
                break;
            case #Time:
                DMFStagingConversionTable_StagingTimeValue.visible(true);
                break;
            default:
                warning("@DMF694");
                element.close();
        }
    }
    else if(dmfSourceXMLToEntityMap.IsAutoDefault)
    {
        entity = DMFEntity::find(dmfsourceXMLToEntityMap.Entity);
        dictField = new SysDictField(tableName2id(entity.EntityTable),fieldName2id(tableName2Id(entity.EntityTable),dmfsourceXMLToEntityMap.EntityField));
        dictType = new SysDictType(dictField.typeId());
        if (dictType && dictType.isTime())
        {
            valueType = Types::Time;
        }
        else
        {
            valueType = dictField.baseType();
        }
        dmfFieldType = enum2str(dictField.baseType());
        switch(valueType)
        {
            case Types::Integer:
                DMFStagingConversionTable_StagingIntValue.visible(true);
                break;
            case Types::String:
                DMFStagingConversionTable_StagingStrValue.visible(true);
                break;
            case Types::Real:
                DMFStagingConversionTable_StagingRealValue.visible(true);
                break;
            case Types::Date:
                DMFStagingConversionTable_StagingDateValue.visible(true);
                break;
            case Types::UtcDateTime:
                DMFStagingConversionTable_StagingUtcDataTime.visible(true);
                break;
            case Types::Time:
                DMFStagingConversionTable_StagingTimeValue.visible(true);
                break;
            case Types::Guid:
                DMFStagingConversionTable_StagingGuidValue.visible(true);
                break;
            default:
                warning("@DMF694");
                element.close();
        }
    }

    else
    {
        element.close();
    }
}

and on the  sole form data source field StagingStrValue

public boolean validate()
{
    boolean                             ret;
    DMFDefinitionGroupEntityXMLFields   dmfDefinitionGroupEntityXMLFields;

    ret = super();

    if(ret)
    {
        // Begin: Alexey Voytsekhovskiy
        if(strLen(DMFStagingConversionTable.StagingStrValue) > myMaxStrSize)
        {
            throw error("@DMF788");
        }

//        select firstOnly FieldSize from dmfDefinitionGroupEntityXMLFields
//            where dmfDefinitionGroupEntityXMLFields.FieldName == dmfSourceXMLToEntityMap.XMLField;
//
//        if(dmfDefinitionGroupEntityXMLFields.FieldSize)
//        {
//            if(strLen(DMFStagingConversionTable.StagingStrValue) > dmfDefinitionGroupEntityXMLFields.FieldSize)
//            {
//                throw error("@DMF788");
//            }
//        }
        // End: Alexey Voytsekhovskiy
    }

    return ret;
}

Thursday, January 23, 2014

Bug: One character in dialog field label causes wrong redrawing

Working on my previous project to change batch caption dialog field, I found a strange bug when it came to redrawing dialog window.

If we place any dialog field below a combobox with a label even of one character longer than the latter, AX fails to redraw the window entirely after modifying combobox value and changing Main instruction text.



protected Object dialog()
{
    FormComboBoxControl  combobox;
    dlg                 = super();

    dialogEventType = dlg.addFieldValue(EnumStr(uapInterfaceEventType), eventType, '1234567890' );
    dialogAnother   = dlg.addFieldValue(identifierStr(VendName), vendName, '12345678901');
    // to add details to the caption and task description in batch tasks
    dialogEventType.registerOverrideMethod(methodstr(FormStringControl, modified), methodstr(tmxRunBaseBatchSample, eventType_modified), this);
    // to avoid user input in the field
    combobox = dialogEventType.control();
    combobox.comboType(1);

    return dlg;
}

Thursday, January 16, 2014

Bug: Drag-n-Drop Creates New Element In Enums With Duplicate Values

Bug in AX 2012 R2.

When you use drag-n-drop in AOT to create a new element for a enum, it creates this element with the exactly same value.





But all enum values must be unique.



It can lead to eventual errors in run time.
Please send a bug report to Microsoft.