Dynamics Ax 4.0 SP2 (application version: 4.0.2501.347), Dynamics Ax2009 (application version: 5.0.1001.176)
После темы
Баг в InventItemType? из
'спортивного интереса' решил посмотреть на сколько часто в коде Ax встречаются ситуации когда разработчики использовали 'индекс' (порядковый номер) элемента перечисления в качестве его значения, либо наоборот - трактовали значение как порядковый номер элемента перечисления.
По перекрестным ссылкам выборочно посмотрел где(как) используются методы классов
(Sys-)DictEnum.index2Name(), (Sys-)DictEnum.index2Symbol(), (Sys-)DictEnum.value2Name(), (Sys-)DictEnum.value2Symbol() и методы
SysDictEnum: firstValue(), lastValue(), nextValue(). В результате обнаружилось достаточное

количество мест, код которых вызывает вопросы и претендует на рефакторинг. Приведу некоторые примеры кода на анализ которого потратил немного времени.
Forms\SysRecordInfo\Methods\buildInsertScript():
X++:
void buildInsertScript()
{
...
boolean setFieldValue(fieldId _fieldId)
{
...
switch (dictFieldTemp.baseType())
{
...
case Types::Enum :
dictEnum = new DictEnum(dictFieldTemp.enumId());
fieldValue = dictEnum.name() + '::' + dictEnum.index2Symbol(common.(_fieldId));
break;
...
}
...
}
...
}
в поле
common.(_fieldId) - значение перечисления, логичнее ожидать в этом коде использование
dictEnum.value2Symbol:
X++:
fieldValue = dictEnum.name() + '::' + dictEnum.value2Symbol(common.(_fieldId));
Увидеть результаты исходного варианта кода можно к примеру в справочнике номенклатуры (для российской функциональности): выбираем строку номенклатуры с типом
'Основные средства' (
ItemType=Asset_RU(100)), из контекстного меню выбираем
Record Info ('Паспорт записи') и жмем кнопку
Script ('Сценарий'). Получаем результат вида:
Цитата:
InventTable.ItemGroupId = "Электротехника";
InventTable.ItemId = "Эле_000085";
InventTable.ItemName = "Перфоратор с пылесосом, Макита";
InventTable.ItemType = ItemType::;
Classes\CCPivotTable\addEnums():
X++:
void addEnums(SysDictField sysDictField)
{
...
for (n=0; n<dictEnum.values(); n++)
{
OLAPEnums.EnumValue = n;
OLAPEnums.EnumText = dictEnum.value2Name(n);
OLAPEnums.EnumName = dictEnum.name();
OLAPEnums.Language = languageId;
OLAPEnums.insert();
}
...
}
ожидалось:
X++:
OLAPEnums.EnumText = dictEnum.index2Name(n);
Classes\SysLookup\enumLabel2Id():
X++:
// return -1 when enum label does not exsist.
public client server static int enumLabel2Id(enumId _enumId, LabelType _labelType)
{
DictEnum dictEnum = new DictEnum(_enumId);
int i;
;
for (i=0; i < dictEnum.values(); i++)
{
if (!strcmp(strltrim(dictEnum.value2Label(i)), strltrim(_labelType)))
{
return i;
}
}
for (i=0; i < dictEnum.values(); i++)
{
if (!strcmp(strltrim(dictEnum.value2Name(i)), strltrim(_labelType)))
{
return i;
}
}
for (i=0; i < dictEnum.values(); i++)
{
if (!strcmp(strltrim(int2str(i)), strltrim(_labelType)))
{
return i;
}
}
return -1;
}
По коду делаем вывод что метод возвращает порядковый номер в перечислении при совпадении текста поиска.
Результат метода используется в
SysLookup::lookupTableFixedRelation():
X++:
private static RelationName lookupTableFixedRelation(tableId _tableId, Common _common, Set _fixedRelationSet)
{
...
if (relationField.enumId())
{
...
if (tmpSysQuery &&
dictRelation.lineExternTableValue(j) == SysLookup::enumLabel2Id(relationField.enumId(), tmpSysQuery.RangeValue))
...
}
...
}
т.е.
числовое значение перечисления в фиксированной связи между таблицами
dictRelation.lineExternTableValue(j) сравнивается c
порядковым номером элемента перечисления
SysLookup::enumLabel2Id() найденного по тексту? Тогда для перечислений с
'дырками' в значениях элементов метод будет работать не совсем корректно.
Для тестирования набросал небольшой job:
X++:
static void jbSysLookupCheck(Args _args)
{
void checkSysLookup_EnumLabel2Id( ItemType _itemType )
{
SysDictEnum sysDictEnum = new SysDictEnum( enumNum( ItemType ) ) ;
LabelType labelType ;
;
setPrefix( strfmt( "%1: %2 (%3)", sysDictEnum.name(), _itemType, any2int( _itemType ) ) ) ;
labelType = sysDictEnum.value2Label( _itemType ) ;
info( strfmt( "by label: %1 (%2)", labelType, SysLookup::enumLabel2Id( sysDictEnum.id(), labelType ) ) ) ;
labelType = sysDictEnum.value2Name( _itemType ) ;
info( strfmt( "by name: %1 (%2)", labelType, SysLookup::enumLabel2Id( sysDictEnum.id(), labelType ) ) ) ;
labelType = strfmt( "%1", any2int( _itemType ) ) ;
info( strfmt( "by value: %1 (%2)", labelType, SysLookup::enumLabel2Id( sysDictEnum.id(), labelType ) ) ) ;
}
;
info( "SysLookup::enumLabel2Id()" ) ;
checkSysLookup_EnumLabel2Id( ItemType::Service ) ;
checkSysLookup_EnumLabel2Id( ItemType::Asset_RU ) ;
}
результат выполнения:
Цитата:
SysLookup::enumLabel2Id()
-ItemType: Услуга (2)
--by label: Услуга (2)
--by name: Услуга (2)
--by value: 2 (2)
-ItemType: Основные средства (100)
--by label: Основные средства (-1)
--by name: Основные средства (-1)
--by value: 100 (-1)
Возможно что метод должен был выглядеть примерно так (возвращать значение перечисления по искомому тексту?):
X++:
// return -1 when enum label does not exsist.
public client server static int enumLabel2Id(enumId _enumId, LabelType _labelType)
{
DictEnum dictEnum = new DictEnum(_enumId);
int i;
;
for (i=0; i < dictEnum.values(); i++)
{
if (!strcmp(strltrim(dictEnum.index2Label(i)), strltrim(_labelType)))
{
return dictEnum.index2Value(i);
}
}
for (i=0; i < dictEnum.values(); i++)
{
if (!strcmp(strltrim(dictEnum.index2Name(i)), strltrim(_labelType)))
{
return dictEnum.index2Value(i);
}
}
for (i=0; i < dictEnum.values(); i++)
{
if (!strcmp(strltrim(int2str(dictEnum.index2Value(i))), strltrim(_labelType)))
{
return dictEnum.index2Value(i);
}
}
return -1;
}
Classes\smmSourceType\preDefinedType() (
AX2009):
X++:
public static boolean preDefinedType(str _sourceType)
{
SysDictEnum sysDictEnum = new SysDictEnum(enumnum(SmmSourceTypeList));
int i;
;
for (i = sysDictEnum.firstValue(); i <= sysDictEnum.lastValue(); i = sysDictEnum.nextValue(i))
{
if (sysDictEnum.index2Value(i) != SmmSourceTypeList::UserDefined &&
sysDictEnum.index2Name(i) == _sourceType)
{
return true;
}
if (i == sysDictEnum.lastValue())
{
break;
}
}
return false;
}
обратная ситуация: методы
sysDictEnum.firstValue(), sysDictEnum.nextValue() возвращают числовое значение перечисления для переменной
i, однако далее по коду значение
i трактуется как порядковый номер и используются методы
index2Value(i) и
index2Name(i).
imho, ожидалась проверка значения:
X++:
if ( i != SmmSourceTypeList::UserDefined &&
sysDictEnum.value2Name(i) == _sourceType)
Похожая проверка значения перечисления в этом же классе есть на методе
smmSourceType.createDefaultData().
Для тестирования добавил в перечисление
SmmSourceTypeList элемент
CheckCode_Example_100, с меткой
'Example' и значением 100 и запустил job:
X++:
static void jbSmmSourceTypeCheck(Args _args)
{
void checkSmmSourceType( SmmSourceTypeList _smmSourceTypeList)
{
;
info( strfmt( "%1 (%2) - %3", _smmSourceTypeList, any2int( _smmSourceTypeList ), SmmSourceType::preDefinedType( strfmt( '%1', _smmSourceTypeList ) ) ) ) ;
}
;
setPrefix( "SmmSourceType::preDefinedType()" ) ;
checkSmmSourceType( SmmSourceTypeList::Employee ) ;
checkSmmSourceType( SmmSourceTypeList::CheckCode_Example_100 ) ;
}
Результат:
Цитата:
SmmSourceType:: preDefinedType()
- Сотрудник (2) - true
- Example (100) - false
Пример из кода локальной функциональности,
Classes\InventJournalPrintForm_RU.run():
X++:
private void run()
{
...
DictEnum dictEnum = new DictEnum(enumnum(InventJournalReportType_RU));
...
InventJournalReport_RU report;
...
while (it.more())
{
++gridCnt;
report = it.value();
dsName = dictEnum.index2Symbol(report.reportType());
...
}
...
while (it.more())
{
report = it.value();
dsName = dictEnum.index2Symbol(report.reportType());
...
}
...
}
InventJournalReport_RU.reportType() возвращает значение перечисления
InventJournalReportType_RU, а в коде трактуется как
'index' :
dsName = dictEnum.index2Symbol(report.reportType());
Ожидалось
X++:
dsName = dictEnum.value2Symbol(report.reportType());
Это лишь некоторые примеры из числа найденных, описывать все думаю нет необходимости, принцип поиска достаточно прост.
P.S. Надеюсь члены группы
Microsoft Dynamics (если они просматривают форум) обратят внимание на этот топик, по возможности прокоментируют сделанные выводы и инициируют рефакторинг прикладного кода использующего методы
DictEnum.value2XXXX() и
DictEnum.index2XXXX() на предмет устранения подобных накладок (скрытых багов).