Mer
Sable

Find objects in projects

Trouver 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

Cette 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

Chercher 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



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 verticaly 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

Hello
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



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

Faire 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

Une 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

Le 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

Utilisation 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));
    }
}