Find objects in projects
2018-03-29 13:10:59Trouver ou retrouver dans quel(s) projet(s) de développement se cache un objet est impossible nativement dans Ax.
Voici un job bien pratique pour ce genre de besoin.
void JCHSearchInProjects(Args _args)
{
Aot
TreeNodeSysNodeType
AviFiles
Properties
TreeNode tnRoot, tnProject;
ProjectNode projectNode;
UtilElementType utilElementType;
set projectsNames = new set(Types::Container);
SetEnumerator projNamesEnum;
Dialog diag;
DialogField objNameDlg;
identifiername nameSearch;
int i;
SysOperationProgress progressBar = new SysOperationProgress();
void searchChildren(TreeNode tnParent, str _nameSearch)
{
TreeNode tn;
TreeNodeIterator rootNodeIterator;
str aotName;
;
rootNodeIterator = tnParent.AOTiterator();
tn = rootNodeIterator.next();
while (tn)
{
aotName = tn.AOTname();
if (tn.treeNodeType().id() == NT_PROJECT_GROUP)
{
searchChildren(tn, _nameSearch);
}
else if (aotName like _nameSearch)
{
projectsNames.add([tnProject.AOTname(),tnProject.AOTgetProperty(PropertyCreationDate)]);
return;
}
tn.treeNodeRelease();
tn = rootNodeIterator.next();
}
}
diag = new Dialog();
objNameDlg = diag.addField(extendedTypeStr(identifiername),"Chaine recherchée");
if (!diag.run())
{
info("Recherche annulée");
return;
}
nameSearch = objNameDlg.value();
if (nameSearch == '' nameSearch == '')
{
warning("Valeur interdite ou pas de valeur");
return;
}
progressBar.setCaption("Searching");
progressBar.setAnimation(AviSearch);
tnRoot = SysTreeNode::getSharedProject();
if (tnRoot)
{
tnProject = tnRoot.AOTfirstChild();
while (tnProject)
{
projectNode = tnProject;
progressBar.setText(projectNode.name());
searchChildren(projectNode.loadForInspection(), nameSearch);
tnProject = tnProject.AOTnextSibling();
}
}
if (projectsNames.elements() == 0)
info("Chaine non trouvé");
else
{
setPrefix(strFmt("la chaine 1 est trouvée dans les projets suivants",nameSearch));
projNamesEnum = projectsNames.getEnumerator();
while (projNamesEnum.moveNext())
{
info(strFmt("1 created on 2",conPeek(projNamesEnum.current(),1),conPeek(projNamesEnum.current(),2)));
}
}
}
Regular expression
2017-09-20 11:16:39Cette syntaxe (et suite d'outil) relativement complexe mais extrêmement efficace permet de définir un schéma de comparaison pour une chaîne de texte de manière très étroite. On peut définir un nombre et un type de caractères autorisés, une liste de caractères, au contraire en exclure d'autres, au début, à la fin d'une chaîne, en capital ou pas.
Globalement ça permet de valider un format, par exemple une adresse mail qui doit comporter un unique et un ou plusieurs points ainsi que quelques restrictions de caractères spéciaux et des limites en nombre de caractères avant et après le point ou l'arobase. Tout ça est codable dans une expression régulière ... que voici (enfin en partie parce que mes propres validations m'interdisent certains caractères) : [wd+.-_]+[wd.-]+.[a-zA-Z]{2,} ... Ha c'est quand même barbare hein, j'en conviens
Consultez Wikipédia si vous désirez approfondir, ce sera déjà un bon début, et bon courage, parce que c'est poilu !
Voici un petit bout de code qui démontre l'utilisation des expressions régulières à travers la validation d'une date saisie en texte avec un format de date classique.
static void Job10(Args _args)
{
dialog dialog = new dialog("Type a date");
dialogField dgDate;
str dateStr;
str day,mth,yea;
str regExp;
System.Text.RegularExpressions.Match regExMatch;
System.Text.RegularExpressions.GroupCollection regExGroupColl;
System.Text.RegularExpressions.Group regExGroup;
;
// Sequence DMY with 1 >= Day >= 31, 1 >= Month >= 12 and Year must be 4 digits. Separator may be '/' or '-'
regExp = '(0?[1-9][12][0-9]3[01])[/-](0?[1-9]1[012])[/-](d{4})';
dgDate = dialog.addFieldValue(types::String,'01/01/2000');
dgDate.label("Type a date for test here (free text");
if (!dialog.run())
return;
dateStr = dgDate.value();
// Added interopPermission for server (batch) execution of .net magic
new InteropPermission(InteropKind::ClrInterop).assert();
try
{
regExMatch = System.Text.RegularExpressions.Regex::Match(dateStr,regExp);
// In case of succes, keep the relevant info for final date verif. Groups are delimited by () in the regExp
if (!regExMatch.get_Success())
throw warning(strfmt("Date '1' isn't valid (hint 2)",dateStr,"24/12/3578 or 4-7-1515"));
else
{
regExGroupColl = regExMatch.get_Groups();
regExGroup = regExGroupColl.get_Item(1);
day = regExGroup.get_Value();
regExGroup = regExGroupColl.get_Item(2);
mth = regExGroup.get_Value();
regExGroup = regExGroupColl.get_Item(3);
yea = regExGroup.get_Value();
if (str2int(day) < 1 str2int(day) > dayofmth(dateEndMth(mkdate(01,str2int(mth),str2int(yea)))))
throw warning(strfmt("Day 1 of month 2 doesn't exist in 3",day,mth,yea));
if (str2int(yea) <= year(datenull()) str2int(yea) > year(maxDate()))
throw warning(strfmt("Sorry, year 1 is beyond my comprehension",yea));
}
CodeAccessPermission::revertAssert();
info (strfmt("1 is ok, well done !",mkdate(str2int(day),str2int(mth),str2int(yea))));
}
catch
{
CodeAccessPermission::revertAssert();
}
}
Nota : évidemment, c'est bourré d'opérateurs spéciaux et de caractères d'échappement, par conséquent mon site bloquant tout ça pour des raisons de sécurité, ce code est parfaitement impropre. Désolé.
Find an existing enum's element
2017-08-16 18:41:06Chercher un élément d'enum dans Ax, c'est long. Très long même !
Et je n'insiste pas sur le retour de la recherche non trié ...
Pour autant je trouve dommage de créer un nouvel enum à chaque fois qu'on a besoin d'une liste de valeurs sans prendre la peine de vérifier si la liste n'est pas déjà là.
Voilà ma solution, un petit job que j'utilise assez souvent pour l'avoir conservé jusque là.
Donnez lui un mot ou une phrase et il ira chercher dans tous les enums si la chaîne existe, et vous renvoie ça classé par pertinence.
Bon coding !
static void SearchForAnExistingEnumElement(Args _args)
{
Macrolib.AviFiles
map result;
MapEnumerator resEnum;
DialogField dgvalue;
Dialog diag;
SysDictEnum dictEnum;
SysDictionary dictionary;
EnumId en;
int i=0,j;
SysOperationProgress progress;
str enumName,element,searchStr;
container matched,beginWith;
void removeKeys(container _keys)
{
for (i=1;i<=conlen(_keys);i++)
result.remove(conpeek(_keys,i));
}
setprefix("Search For An Existing Enum Element");
result = new Map(Types::String, Types::String);
Diag = new Dialog("Donner le texte d'un element recherché");
dgvalue = diag.addField(typeId(Name), "Un libellé d'élément");
if (diag.run())
{
// Constitution d'une map enums/elements qui contiennent la chaine
searchStr = dgvalue.value();
dictionary = new SysDictionary();
progress = SysOperationProgress::newGeneral(aviUpdate, 'Ca va prendre un peu de temps ...', dictionary.enumCnt());
en = dictionary.enumNext(0);
while (en)
{
i++;
dictEnum = new SysDictEnum(en);
en = dictionary.enumNext(en);
if (!dictEnum)
continue;
enumName = dictEnum.name();
progress.setText(strFmt("Fouilles en cours ... 1 ...",enumName));
progress.incCount();
j = dictEnum.firstValue();
do
{
if (dictEnum.values() == 0)
break;
element = dictEnum.index2Label(j);
if (strKeep(strUpr(element),strUpr(searchStr)) == strUpr(searchStr))
result.insert(enumName,element);
j = dictEnum.nextValue(j);
} while (j != dictEnum.firstValue());
}
// Arrangement de la map enums/elements par pertinence de correspondance
if (result.elements() == 0)
checkFailed(strFmt("'1' est introuvable dans les 2 enums examinés",searchStr,i));
else
{
resEnum = result.getEnumerator();
while (resEnum.moveNext())
{
setprefix ("Chaine identique");
if (strCmp(strUpr(resEnum.currentValue()), strUpr(searchStr)) == 0)
{
info (strFmt("1 --- 2",resEnum.currentKey(),resEnum.currentValue()));
matched += resEnum.currentKey();
}
}
removeKeys(matched);
resEnum = result.getEnumerator();
while (resEnum.moveNext())
{
setprefix ("Commence par la chaine");
if (strCmp(strUpr(subStr(resEnum.currentValue(),1,strLen(searchStr))), strUpr(searchStr)) == 0)
{
info (strFmt("1 --- 2",resEnum.currentKey(),resEnum.currentValue()));
beginWith += resEnum.currentKey();
}
}
removeKeys(beginWith);
resEnum = result.getEnumerator();
while (resEnum.moveNext())
{
setprefix ("Contient la chaine");
info (strFmt("1 --- 2",resEnum.currentKey(),resEnum.currentValue()));
}
}
}
}
Indentation et alignement vertical du code
2017-08-04 11:03:31
Pondre des trucs lisibles, c'est pas compliqué !
C'est déjà pénible de devoir travailler sur du code modifié 36 fois dans les 4 dernières années mais alors en plus mal indenté et écrit à la va-com'-ch'te-pousse ! c'est horrible.
Alors hop ! j'ai fait une petite méthode pour caler tout ça verticalement.
Méthode à mettre dans la classe editorScript
Plus d'excuse ! :P
Bon coding !
void Comments_VertAlignSpacedWords(Editor _editor)
{
define.space(" ")
int startLine = _editor.selectionStartLine();
int endLine = _editor.selectionEndLine();
map cutPositions = new map(types::Integer,types::Integer);
container lineWords;
int i,j,k,overflow,nxt;
str line,newLine,word;
Char lastChar = space;
int maxNbWords = 3;
;
_editor.unmark();
_editor.gotoLine(startLine);
_editor.gotoCol(1);
if (startLine == endLine)
warning ("You should first select a few lines which needs to be vertically aligned.");
for (i=startLine;i<=endLine;i++)
{
k = 0;
overflow = 0;
_editor.gotoLine(i);
line = _editor.currentLine();
if (strlen(line)==0 subStr(strRem(line," "),1,2) == "//")
continue;
for (j=0;j<=strlen(line);j++)
{
if ((substr(line,j,1) != space && lastChar == space) (substr(line,j,1) == space && lastChar != space))
{
if (lastChar == space)
{
k++;
if (k > maxNbWords)
break;
if (!cutPositions.exists(k))
cutPositions.insert(k,j+overflow);
else if (cutPositions.lookup(k) < j)
{
overflow = j - cutPositions.lookup(k);
cutPositions.insert(k,j);
}
if (overflow)
for (nxt=k+1;nxt<=cutPositions.elements();nxt++)
cutPositions.insert(nxt,cutPositions.lookup(nxt)+overFlow);
}
lastChar = substr(line,j,1);
}
}
}
for(i=startLine;i<=endLine;i++)
{
newLine = nullValueBaseType(types::String);
_editor.gotoLine(i);
_editor.gotoCol(1);
line = _editor.currentLine();
if (strlen(line)==0 subStr(strRem(line," "),1,2) == "//")
continue;
lineWords = str2con(line,space);
k = 0;
for (j=1;j<=conlen(lineWords);j++)
{
word = conpeek(lineWords,j);
if (!word)
continue;
k++;
if (k <= maxNbWords && cutPositions.lookup(k)-1+strlen(word)-strlen(newLine) >= strlen(word))
newLine += strRFix(word,cutPositions.lookup(k)-1+strlen(word)-strlen(newLine),space);
else
newLine += space + word;
}
_editor.deleteChars(strlen(line));
_editor.insertString(newLine);
}
}
List from a container
2017-07-20 19:08:47Hello
J'ai eu besoin de créer une liste à partir d'un container, en fait, un Set, parce que c'est ordonné et que les doublons sont interdits. (Maintenant que j'y pense, c'est aussi un bon moyen de nettoyer un container plein de multiples valeurs identiques...)
Je vous livre mes tests :
static void JCH_SetTests(Args _args)
{
Set is1, is;
int i,j,k;
CustTable custTable;
container packedSet;
container PackedSet2,custRecIds;
;
// Create a set containing the first 10 integers.
is = new Set (Types::Integer);
for (i = 1; i <= 10; i++)
is.add(i);
// Pack it down in a container...
packedSet = is.pack();
// ... and restore it
is1 = Set::create(packedSet);
info (strfmt("1",is1.toString()));
pause;
// Now, how to create a container like a Set pack method would do :
// Simply put 3 elements prior to the list of values,
// First : a version num, Second : int for datatype of the Set, Third number of values
while select recId from custTable
{
j++;
custRecIds += custTable.recId;
if (j> 5)
break;
}
packedSet2 = [1,enum2int(types::Int64),conlen(custRecIds)]+custRecIds;
is1 = Set::create(packedSet2);
info (is1.toString());
// Let's make duplicate entries
while select recId from custTable
{
k++;
custRecIds += custTable.recId;
if (k> 6)
break;
}
packedSet2 = [1,enum2int(types::Int64),conlen(custRecIds)]+custRecIds;
is1 = Set::create(packedSet2);
info (is1.toString());
}
bitwise left or right shifting
2017-03-29 17:22:09
Le bit shifting ou le déplacement de bit
Il s'agit d'une opération binaire nécessaire dans certains calculs très gourmands en ressources.
Ici je le détourne pour simplement 'placer' un bit à un endroit précis d'une chaîne binaire fictive. Ca me sert à définir des options, sélectionnées ou non comme 1 ou 0, et à les transmettre dans une zone de donnée limitée en taille.
Ici j'étais limité à 8 octets, ayant choisi le numérique en sortie je pouvais utiliser 26 positions (max 00000011 11111111 11111111 11111111 soit 2Exp26-1 = 67 108 863 --> 8 caractères), mais en utilisant l'hexadécimal j'aurais pu aller jusqu'à 32 positions toujours sur 8 octets ==> FFFFFFFF.
static void AAF_bitWiseShifting(Args _args)
{
int num;
container selection = [2,3];
int i=1;
int getInt(str value)
{
return str2int(value)-1;
}
;
for (i=1;i<=conlen(selection);i++)
num += (1 << getInt(conpeek(selection,i)));
info(strfmt("1, longueur str : 2, nb options : 3",num,strlen(int2str(num)),conlen(selection)));
}
Ah au fait, je dois vous dire ceci à propos de l'image :
en:User:Cburnett - Own workThis vector image was created with Inkscape., CC BY-SA 3.0, Link
SQL Direct
2017-03-23 11:57:03Faire une requête SQL simple sur une table.
Quand on veut vraiment voir ce qu'il y a dans une table autrement qu'à travers la lorgnette d'Ax.
Mettre ça dans une méthode de classe ou un job :
static void jobSQLDirectQuery(tableId _tableId = 0)
{
dialog diag = new dialog("Choose a table");
dialogField dgTable,dlgSep;
Connection connection = new Connection();
Statement sql;
ResultSet result;
ResultSetMetaData data;
TableId tableId;
str query;
str 1 sep;
int fields,i,numOfLine;
container values;
;
if(_tableId)
dgTable = diag.addFieldValue(typeid(tablename),tableId2name(_tableId));
else
dgTable = diag.addField(typeid(tablename));
dlgSep = diag.addFieldValue(typeId(char),";");
dlgSep.label("Delimiter");
if (diag.run())
{
tableId = tableName2Id(dgTable.value());
if ( connection
&& tableId
&& new SysDictTable(tableId).isSql())
{
sql = connection.createStatement();
query = strfmt("select from 1", dgTable.value());
result = sql.executeQuery(query);
data = result.getMetaData();
sep = strlen(dlgSep.value()) == 1 ? dlgSep.value() : ";";
While (result.next())
{
numOfLine++;
if (numOfLine == 1)
{
i = data.getColumnCount();
for(i=1;i<=data.getColumnCount();i++)
{
values += data.getColumnName(i);
}
info(con2str(values,sep));
values = connull();
}
i = data.getColumnCount();
for(i=1;i<=data.getColumnCount();i++)
{
values += result.getString(i);
}
info(con2str(values,sep));
values = connull();
}
}
error ("No connection and/or the choosen table doesn't exist (Ie : Wrong name or temporary table)");
}
}
array2str
2017-03-21 15:29:27Une méthode qui passe une aire en chaîne de texte
str array2str(anytype _arr[],str _sep = "")
{
integer i;
str ret;
for(i=1;i<=dimOf(_arr);i++)
{
ret+=strlen(ret)==0 ? strfmt("1",_arr[i]) : strfmt("12",_sep,_arr[i]);
}
return ret;
}
Ecrire dans un Fichier
2013-11-06 13:38:54Le job suivant fonctionne bien (Il faut que le repertoire "c:temp" existe)
static void writeFileTest(Args _args)
{
FileIoPermission perm;
FileName file = "c:temptest.tmp.txt"; // Chemin et nom du fichier en sortie
asciiIo asciifile; // Class - Connecteur Ascii
file // Quelques constantes liées aux manipulations de fichiers
;
try
{
if( winapi::fileExists( file ) )
{
perm = new FileIoPermission(file, io_append);
perm.assert();
asciifile = new asciiIo(file, io_append );
codeAccessPermission::revertAssert();
}
else
{
perm = new FileIoPermission(file, io_write);
perm.assert();
asciifile = new asciiIo(file, io_write );
codeAccessPermission::revertAssert();
}
if( !asciifile )
throw error( "Erreur, pas de fichier" );
// Exemple d'écriture de ligne :
asciifile.write("ligne de test");
}
catch
{
error("erreur");
}
}
ET au cas ou 'file' serait manquant :
define.io_read('R')
define.io_write('W')
define.io_append('A')
define.io_translate('T')
define.io_binary('B')
Job test calculs de prix PAMP
2013-10-11 14:24:15Utilisation de diverses solutions pour obtenir le prix lissé en stock d'un article (PAMP, Prix d'Achat Moyen Pondéré). C'est une info qu'on retrouve dans le menu 'Gestion des entrepôts et des stocks' - 'Courant' - 'Stock Disponible'...
static void AAA_ItemPricePAMP(Args _args)
{
Dialog dlg;
DialogField dlgItem,
dlgInventLocationId;
ItemId item;
InventDim inventDim;
InventSum inventSum;
inventCostPriceCache invCostPriceCache = inventCostPriceCache::construct();
InventOnhand inventOnHand;
InventDimParm inventDimParm;
dlg = new Dialog("Item PAMP");
dlgItem = dlg.addField(extendedTypeStr(ItemId));
dlgInventLocationId = dlg.addField(extendedTypeStr(InventLocationId));
if (dlg.run() && dlgItem.value())
{
item = dlgItem.value();
inventOnHand = inventOnHand::newItemId(item);
inventDim .InventSiteId = InventLocation::find(dlgInventLocationId.value()).InventSiteId;
inventDim .InventLocationId = dlgInventLocationId.value();
inventDim = InventDim::findOrCreate(inventDim);
inventDimParm .initFromInventDim (inventDim);
inventOnHand .parmInventDimParm (inventDimParm);
inventOnHand .parmInventDim (inventDim);
InventOnHand .parmItemId (item);
inventSum::find(item,inventDim.inventDimId);
select firstonly inventSum
where inventSum.ItemId == item
&& inventSum.InventDimId == inventDim.inventDimId;
setPrefix ("item : '"+item+"', inventDimId : '"+inventDim.inventDimId+"'");
info (strFmt("inventOnHandt costPricePcs : '1'" ,inventOnHand.costPricePcs()));
info (strFmt("inventOnHandt physicalInvent : '1'" ,inventOnHand.physicalInvent()));
info (strFmt("inventOnHandt reservPhysical : '1'" ,inventOnHand.reservPhysical()));
info (strFmt("inventOnHandt availOrdered : '1'" ,inventOnHand.availOrdered()));
info (strFmt("inventOnHandt availPhysical : '1'" ,inventOnHand.availPhysical()));
info (strFmt("invCostPriceCachet costPricePcs : '1'" ,invCostPriceCache.costPricePcs(inventSum)));
info (strFmt("inventSumt averageCostPrice : '1'" ,inventSum.averageCostPrice()));
info (strFmt("inventSumt Montant cout physique : '1'" ,inventSum.PhysicalValue));
info (strFmt("inventSumt Montant cout financier : '1'" ,inventSum.PostedValue));
}
}