Bonjour @Pierre LaFay ,

Effectivement comme l'a mentionné @Sylvain Guilbaud IAM t'offrira pas mal de fonctionnalité.

Toutefois si tu veux juste forcer un "Hang 1" lors du login, tu peux t'en sortir une sous classe de %CSP.SessionEvents, ex:

Class dc.pierre.RestEvents Extends %CSP.SessionEvents
{

ClassMethod OnStartRequest() As %Status
{
	#dim %request As %CSP.Request
	Set ^dc.pierre("OnStartRequest", "LastRequest") = $ZDT($Horolog, 3, 1) _ " " _ %request.URL
	Set loginURL = "/login"
	If $Extract(%request.URL, * - $Length(loginURL) + 1, *) = loginURL {	; Vérifie si l'url se termine par /login
		Hang 1
	}
	
	Quit $$$OK
}

}

Il faut alors configurer l'application Web pour utiliser cette classe d'évènements.

Cela peut se paramétrer via le portail admin gestion de la sécurité -> Application Web, ex : 

Lorenzo.

Bonjour @Pierre LaFay

Oui, il est possible de faire du mapping, mais il faut bien distinguer deux choses:

  1. Package mapping Celui-ci permet de rendre accessible des package d'un namespace à un autre.   Dans le cas d'une classe %Persistent, il faut bien comprendre que seul le code sera mappé. En ce qui concerne les données enregistrée cela nous amène au 2ème point.  
  2. Global Mapping Il permet de rentre accessible les données de vos globals à votre namespace en spécifiant la base de données source.

Ces configurations peuvent s'effectuer par programmation ou plus simplement via le portail d'administration au niveau de la configuration du namespace (voici le lien de la doc officiel si cela peut vous être utile).

Dans le cas qui vous occupe peut être qu'il serait plus simple d'effectuer la requête SQL directement dans "RemoveIrisTestUsers" étant donné que vous faites déjà le changement de namespace.

Toutefois, pour un package de classe utilitaire comme "Bna.Utils" peut être que vous pourriez le mapper sur le namespace %ALL.

Le namespace %ALL, est spécial, les packages (ou les globals et routines) qui y sont mappées sont automatiquement accessible pour tous les autres namespace.  Donc le code : 

Set sc = ##class(Bna.Utils.Sql).SelectFirstColsInArray(query, .userIds)

Pourra être exécuter même si vous venez de faire un Set $Namespace = "%SYS".

Si besoin, je peux vous fournir une réponse plus complète avec des captures d'écran concernant la configuration avec %ALL.

Lorenzo.

Entre-temps, j'ai trouvé une meilleure solution, activez l'authentification OS level : 

ensuite dans le "%Service.Console":

Lorsque je fais cela sur mon installation irissession instance_name ne nécessite plus de mot de passe et la session est démarrée avec le compte windows actuellement connecté.  Cela implique toutefois logiquement de créer un utilisateur IRIS avec le même nom que le compte de connection Windows utilisé.  Dans mon cas, ce compte avait déjà été créé.

en faisant:

Write $Username

Vous pourrez vérifier quel est le compte qui a ouvert la session.
Si cette solution peut fonctionner dans votre environnement, c'est probablement la meilleure et plus sécurisée.

Salut @Julia Pertin ,

Pour créer un un %DynamicObject tu peux utiliser les syntaxes suivantes : 

set obj = {}
; ou
Set obj = ##class(%DynamicObject).%New()

ZEN étant déprécié, j'imagine que la classe %ZEN.proxyObject l'est aussi (je déconseil son utilisation dans un nouveau développement ;-) ).  

Il y a eu des changements dans les dernières versions d'IRIS avec la gestion des streams dans les %DynamicObject, j'ai tenté d'adapter votre code en le simplifiant un peu, mais je n'ai pas vraiment pu le tester:

    Set obj = {}
    Set update = $CLASSMETHOD(classname,"%OpenId",id)
    Set property = $Property(update, propertyName)
    If $Isobject(property), property.%IsA("%Stream.GlobalBinary") {
        Set pnewContent = ##class(%Stream.GlobalCharacter).%New()
        Do property.Rewind()
        While 'property.AtEnd {
            Do pnewContent.Write($zcvt(property.ReadLine(),"O", "UTF8"))
        }
        Do obj.%Set(sqlFieldName, pnewContent, "stream>base64")
    }

La méthode %Set avec comme 3ème argument "stream>64" de set le stream dans le %DynamicObject en le convertissant directement en base64.

L'inverse est aussi possible avec la methode %Get (ex : obj.%Get(sqlFieldName,"stream<base64") ).
J'espère que ça aidera.

Bonjour @Jules Pontois ,

Oui, tout à fait, c'est pas évident à voir.

A ma connaissance, on ne sait pas forcer l'affichage de ses méthodes malheureusement.

Sous VSCode, il faut faire clique droit -> View Other ou shift+ctrl+v (dans le panel avec le de code de la classe) et ça ne fonctionne que si le flag "k" a été utilisé pour compiler:

    k - Keep source.  When this flag is set, source code of
        generated routines will be kept.

donc oui, il est parfois nécessaire de refaire un $SYSTEM.OBJ.Compile(<class>, "brck") sinon nous n'y avons pas accès.

Si quelqu'un à une astuce pour afficher les méthodes générées qu'il n'hésite pas a partager, ça m'interesse aussi 😉

Bonsoir @Jules Pontois 

Oui dans le cas ou une classe hérite de "XXXX.Outils.BS.ComplexMap.FTP" effectivement les méthodes sont générées dans toutes les classes et les appels ##super ne vous arrange pas.

En fait, si on retire le keyword "ForceGenerate" des méthodes "objectgenerator", les méthodes ne seront générées que dans la sous classe direct.  J'ai utilisé ce keyword pour être sûr que ce soit au moins généré dans sa première sous-classe, mais après avoir fait un test sur ma machine, ce n'est visiblement pas nécessaire.  Vous pouvez donc retirer ce keyword de la classe common, cela devrait régler le problème.

Toutefois si dans l'une de vos sous classes vous faites un override de SendRequestSync\SendRequestAsync en se terminant par ##super, la méthode exécutée par le ##super sera celle injectée par la sous classe direct de "AuditCommons".  Ce n'est pas vraiment un problème, car nous pouvons contrôler cela via un "Parameter" ou encore plus simplement avec un Cast, exemple:

Class XXXX.Outils.BS.ComplexMap.Sub Extends XXXX.Outils.BS.ComplexMap.FTP
{

Method SendRequestSync(pTargetDispatchName As %String, pRequest As %Library.Persistent, ByRef pResponse As %Library.Persistent, pTimeout As %Numeric = -1, pDescription As %String = "") As %Status
{
    ; ...
    ; lignes de code spécifiques à cette classe
    ; bla bla
    ; ...
    ; maintenant on délègue à EnsLib.RecordMap.Service.ComplexBatchFTPService sans passer
    ; par la méthode générée dans XXXX.Outils.BS.ComplexMap.FTP
    Quit ##class(EnsLib.RecordMap.Service.ComplexBatchFTPService)##this.SendRequestSync(pTargetDispatchName, pRequest, .pResponse, pTimeout, pDescription)
}

}

Voilà, j'ai tenté d'adapter votre classe common avec du codemode = objectgenerator:

Include Ensemble

Class XXXX.Outils.BS.AuditCommons
{

/// Class of the audit to use for this flow. If the flow use
/// iterative process, for instance a loop to inserted multiple
/// lines in a table, you can use its insertdetails() method to
/// log an error that occured in the loop without poluting
/// the journal.
Property auditClass As %String(MAXLEN = 500) [ InitialExpression = "XXXX.REPORTING.Tables.Commons.Main" ];
/// Name of the flow that will be inserted in the audit.
Property flowName As %String(MAXLEN = 500);
/// Source of the flow that will be inserted in the audit.
Property flowSource As %String(MAXLEN = 500);
/// Target of the flow that will be inserted in the audit.
Property flowTarget As %String(MAXLEN = 500);
/// If true, the message will be sent synchronously.
Property asynchronous As %Boolean;
/// Audit instance of the current session /// XXXX.REPORTING.Tables.Commons.Main;
Property audit As XXXX.REPORTING.Tables.Commons.Main;
Property errMsg As %String(MAXLEN = "");
Parameter auditLogError = 1;
Parameter SETTINGS = "auditClass:flowParameters,flowName:flowParameters,flowSource:flowParameters,flowTarget:flowParameters,asynchronous:flowParameters";
Method SendRequestSync(pTargetDispatchName As %String, pRequest As %Library.Persistent, ByRef pResponse As %Library.Persistent, pTimeout As %Numeric = -1, pDescription As %String = "") As %Status [ CodeMode = objectgenerator, ForceGenerate ]
{
	; Le code ne peut être généré que dans les sous classes
	If %class.Name = "XXXX.Outils.BS.AuditCommons" Quit $$$OK
	Do %code.WriteLine($Char(9) _ "if ..asynchronous quit ..SendRequestAsync(pTargetDispatchName, pRequest, pDescription)")
	Do %code.WriteLine($Char(9) _ "set pResponse = """"")
	Do %code.WriteLine($Char(9) _ "set sc = ..initAudit(pRequest) quit:'sc sc")
	Do %code.WriteLine($Char(9) _ "set req = ..setReq(pRequest, .sc) quit:'sc sc")
	Do %code.WriteLine($Char(9) _ "quit ##super(pTargetDispatchName, req, .pResponse, .pTimeout, pDescription)")
	
	Quit $$$OK
}

Method SendRequestAsync(pTargetDispatchName As %String, pRequest As %Library.Persistent, pDescription As %String = "") As %Status [ CodeMode = objectgenerator, ForceGenerate ]
{
	; Le code ne peut être généré que dans les sous classes
	If %class.Name = "XXXX.Outils.BS.AuditCommons" Quit $$$OK
	Do %code.WriteLine($Char(9) _ "if '..asynchronous quit ..SendRequestSync(pTargetDispatchName, pRequest, .pResponse, .pTimout, pDescription)")
	Do %code.WriteLine($Char(9) _ "set sc = ..initAudit(pRequest) quit:'sc sc")
	Do %code.WriteLine($Char(9) _ "set req = ..setReq(pRequest, .sc) quit:'sc sc")
	Do %code.WriteLine($Char(9) _ "quit ##super(pTargetDispatchName, req, pDescription)")
}

/// Initialize an audit for the current session. You can
/// use the audit instance using the ..audit property.
Method initAudit(req As Ens.Request = "") As %Status
{
    do ..generateSession()
 
    set auditData = {
        "session": (..getSessionId())
        ,"flowName": (..flowName)
        ,"flowSource": (..flowSource)
        ,"flowTarget": (..flowTarget)
        ,"filename": (##class(XXXX.OUTILS.File.FileInfos).getFilename(req))
    }

    set ..audit = $CLASSMETHOD(..auditClass, "initialize", auditData, .sc)

    if 'sc return ..error("<Initialize audit>",, "initAudit", sc).AsStatus()

    if ..errMsg '= "" do ..audit.setError(..errMsg, ..#auditLogError)

    return 1
}

Method setReq(pRequest As %Library.Persistent, Output gsc As %String = 1) As Ens.Request
{
    return pRequest
}

Method generateSession() [ CodeMode = objectgenerator, ForceGenerate ]
{
	; Le code ne peut être généré que dans les sous classes
	If %class.Name = "XXXX.Outils.BS.AuditCommons" Quit $$$OK
    Do %code.WriteLine($Char(9)_"Set (..%SessionId,$$$JobSessionId) = ##class(Ens.MessageHeader).%New().MessageId()")
}

Method getSessionId() As %String [ CodeMode = objectgenerator, ForceGenerate ]
{
	; Le code ne peut être généré que dans les sous classes
	If %class.Name = "XXXX.Outils.BS.AuditCommons" Quit $$$OK
    Do %code.WriteLine($Char(9) _ "Quit ..%SessionId")
}

ClassMethod OnGetConnections(pArray As %String, item As Ens.Config.Item) As %Status
{
    do ##class(XXXX.OUTILS.Admin).linkBsToBp(.pArray, .item)
}

/// Method used as an alias to shorten the code.
ClassMethod error(name As %String, code As %String = "", location As %String = "", data As %String = "") As %Exception.General
{
    return ##class(%Exception.General).%New(name, code, location, data)
}

}

ensuite, pour la classe "XXXX.Outils.BS.ComplexMap.FTP", modifiez juste l'ordre de l'héritage : 

Class XXXX.Outils.BS.ComplexMap.FTP Extends (XXXX.Outils.BS.AuditCommons, EnsLib.RecordMap.Service.ComplexBatchFTPService)
{

}

Après compilation si vous regardez le code de la routine générée pour la classe ComplexMap.FTP (ctrl+maj+v) vous pourrez remarquer que le code des méthodes [ codemode = objectgenerator, ForceGenerate] a bien été inclus:

zSendRequestAsync(pTargetDispatchName,pRequest,pDescription="") public {
	if '..asynchronous quit ..SendRequestSync(pTargetDispatchName, pRequest, .pResponse, .pTimout, pDescription)
	set sc = ..initAudit(pRequest) quit:'sc sc
	set req = ..setReq(pRequest, .sc) quit:'sc sc
	quit ##class(XXXX.Outils.BS.AuditCommons)$this.SendRequestAsync(pTargetDispatchName, req, pDescription)
}
zSendRequestSync(pTargetDispatchName,pRequest,pResponse,pTimeout=-1,pDescription="") public {
	if ..asynchronous quit ..SendRequestAsync(pTargetDispatchName, pRequest, pDescription)
	set pResponse = ""
	set sc = ..initAudit(pRequest) quit:'sc sc
	set req = ..setReq(pRequest, .sc) quit:'sc sc
	quit ##class(XXXX.Outils.BS.AuditCommons)$this.SendRequestSync(pTargetDispatchName, req, .pResponse, .pTimeout, pDescription)
}
zgenerateSession() public {
	Set (..%SessionId,%Ensemble("SessionId")) = ##class(Ens.MessageHeader).%New().MessageId()
}
zgetSessionId() public {
	Quit ..%SessionId }

Logiquement comme la classe common n'hérite plus de Ens.BusinessService, le fait qu'elle soit la classe la plus à gauche dans l'ordre d'héritage ne devrait plus avoir d'influence négative.

Bonjour @Cyril Grosjean ,

Je devrais pouvoir vous fournir quelques informations pouvant vous être utile.

Dans ma boîte, entre d'autres choses, je m'occupe de l'écriture de scripts de deploy pour nos environnements internes.

La solution que je pense la plus simple est d'écrire une classe (ou une routine) qui sera votre script principal pour toutes les actions à effectuer.  

Le tuto que vous avez suivi, n'est pas vraiment adapté.  Il y a quelques années, je l'ai testé par curiosité, mais c'est vraiment pour faire de l'émulation terminal.

Voici un exemple de classe qui se positionne sur un namespace, charge et compile un répertoire:

Class dc.DeployDemo
{

ClassMethod deploy(
	sourceDirectory As %String,
	targetNameSpace As %String)
{
    Set startNameSpace = $Namespace

    Write !, $ZDateTime($Horolog, 3, 1) _ " Start deploy"

    ; L'utilisation du new $namespace permet au système 
    ; de revenir automatiquement sur le namspace d'origine en quittant la méthode
    New $Namespace

    Set $Namespace = targetNameSpace
    
    Write !, $ZDateTime($Horolog, 3, 1) _ " Switch to namespace "_$Namespace

    Write !, $ZDateTime($Horolog, 3, 1) _ " Load and compile directory " _ sourceDirectory

    ; le 4ème argument permet de faire un chargement récursif (inclusion des sous répertoires)
    Set sc = $SYSTEM.OBJ.LoadDir(sourceDirectory, "ck", , 1)

    Set msgStatus = $Select($$$ISERR(sc):$SYSTEM.Status.GetOneErrorText(sc),1:"OK")

    Write !, $ZDateTime($Horolog, 3, 1) _ " Status " _ msgStatus

    Quit
}

}

Préparer ensuite un fichier texte contenant le code à exécuter (pour l'exemple le fichier texte est c:\dev\script.txt): 

Do ##class(dc.DeployDemo).deploy("c:\src\cls","namespace")
Halt

dès que le fichier est prêt, vous pouvez utiliser irissession et y injecter les commandes:

irissession IRIS -U USER < c:\dev\script.txt

Ceci reste qu'un exemple assez basic, n'hésitez pas à me contacter sur Discord, nous pourrons échanger verbalement plus rapidement sur le sujet si vous le souhaitez.

Lorenzo.

Re,

Attention l'utilisation de CDATA doit être : 

        	<![CDATA[<IDBRIEFS>2</IDBRIEFS>
        	<Numero>8</Numero>]]>

Dans ce cas, il faut modifier le type de la property "Contenu" en %Stream.GlobalCharacter:

Class demo.correlate.structure.TableName Extends (%RegisteredObject, %XML.Adaptor)
{

Property Table As %String;
Property Action As %String;
Property CleIris As %String;
Property nIdentifiant As %String;
Property IdIris As %String;
Property Contenu As %Stream.GlobalCharacter;
}

Class demo.correlate.Test
{

/// Do ##class(demo.correlate.Test).Test()
ClassMethod Test()
{
	#dim data As demo.correlate.structure.Data
	#dim tableName As demo.correlate.structure.TableName
	
	Set xmlTest = ##class(%Dictionary.CompiledXData).%OpenId($classname()_"||XMLTest",,.sc).Data
	
	If $$$ISERR(sc) Do $SYSTEM.Status.DisplayError(sc) Quit sc
	
	Set reader=##class(%XML.Reader).%New()
	Do reader.Correlate("data","demo.correlate.structure.Data")
	Set sc = reader.OpenStream(.xmlTest)
	Do reader.Next(.data)
		
	Set key = ""
	Set tableName = data.tablename.GetNext(.key)
	While key '= "" {
		Write !,"+ table: ", tableName.Table
		Write !,"+ Contenu: ", tableName.Contenu.Read()
		Set tableName = data.tablename.GetNext(.key)
	}
	
	Quit $$$OK
}

XData XMLTest
{
<?xml version="1.0" encoding="UTF-8"?>
<data>
    <tablename>
        <Table>Erp.BRIEFS</Table>
        <Action>HAjoute</Action>
        <CleIris>IDBRIEFS</CleIris>
        <nIdentifiant>2</nIdentifiant>
        <IdIris>137</IdIris>
        <Contenu>
        	<![CDATA[<IDBRIEFS>2</IDBRIEFS>
        	<Numero>8</Numero>]]>
        </Contenu>
    </tablename>
    <tablename>
        <Table>Erp.COMMANDES</Table>
        <Action>HAjoute</Action>
        <CleIris>IDCOMMANDES</CleIris>
        <nIdentifiant>5</nIdentifiant>
        <IdIris>138</IdIris>
        <Contenu>
       		<![CDATA[<IDCOMMANDES>5</IDCOMMANDES>
            <CLISId>4399</CLISId>]]>
        </Contenu>
    </tablename>
</data>
}

}

A l'exécution de la méthode test, vous aurez

USER>Do ##class(demo.correlate.Test).Test()
 
+ table: Erp.BRIEFS
+ Contenu:
                        <IDBRIEFS>2</IDBRIEFS>
                                                        <Numero>8</Numero>
                                                                                 
+ table: Erp.COMMANDES
+ Contenu:
                                <IDCOMMANDES>5</IDCOMMANDES>
                                                                        <CLISId>4399</CLISId>

Vous pouvez donc récupérer toutes les occurrences de "Contenu" dans des streams séparés et faire d'autres correlate :)

Bonjour @Julia Pertin ,

Voici un exemple avec votre xml: 

La classe de test avec votre XML (par facilité pour je l'ai mis dans un XData): 

Class demo.correlate.Test
{

/// Do ##class(demo.correlate.Test).Test() As %Status
ClassMethod Test()
{
	#dim data As demo.correlate.structure.Data
	#dim tableName As demo.correlate.structure.TableName
	
	Set xmlTest = ##class(%Dictionary.CompiledXData).%OpenId($classname()_"||XMLTest",,.sc).Data
	Quit:$$$ISERR(sc) sc
	
	Set reader=##class(%XML.Reader).%New()
	Do reader.Correlate("data","demo.correlate.structure.Data")
	Set sc = reader.OpenStream(.xmlTest)
	Do reader.Next(.data)
		
	Set key = ""
	Set tableName = data.tablename.GetNext(.key)
	While key '= "" {
		Write !,"+ table ", tableName.Table
		Set tableName = data.tablename.GetNext(.key)
	}
	
	Quit $$$OK
}

XData XMLTest
{
<?xml version="1.0" encoding="UTF-8"?>
<data>
    <tablename>
        <Table>Erp.BRIEFS</Table>
        <Action>HAjoute</Action>
        <CleIris>IDBRIEFS</CleIris>
        <nIdentifiant>2</nIdentifiant>
        <IdIris>137</IdIris>
        <Contenu></Contenu>
    </tablename>
    <tablename>
        <Table>Erp.COMMANDES</Table>
        <Action>HAjoute</Action>
        <CleIris>IDCOMMANDES</CleIris>
        <nIdentifiant>5</nIdentifiant>
        <IdIris>138</IdIris>
        <Contenu></Contenu>
    </tablename>
</data>
}

}

Ensuite les deux classes correspondantes à la structure du xml (Data et TableName):

Class demo.correlate.structure.Data Extends (%RegisteredObject, %XML.Adaptor)
{

Parameter XMLNAME = "data";
Property tablename As list Of demo.correlate.structure.TableName(XMLPROJECTION = "ELEMENT");
}
Class demo.correlate.structure.TableName Extends (%RegisteredObject, %XML.Adaptor)
{

Property Table As %String;
Property Action As %String;
Property CleIris As %String;
Property nIdentifiant As %String;
Property IdIris As %String;
Property Contenu As %String;
}

En exécutant la méthode Test dans le terminal, elle affichera le contenu de property "Table":

USER>Do ##class(demo.correlate.Test).Test()
 
+ table Erp.BRIEFS
+ table Erp.COMMANDES

J'espère que cet exemple vous aidera dans votre developpement.

Bonne journée.

Bonsoir @Jean-Charles Cano ,

Avez-vous déjà essayé avec runw?  Voici un exemple de structure de la commande pour démarrer une classmethod:  

iris runw INSTANCENAME ##class(Package.Class).ClassMethod(\"argument\") NAMESPACE

L'exe iris se trouve dans le sous répertoire bin de l'instance IRIS.

pour démarrer un terminal en ligne de commande utilisez irissession:

irissession INSTANCENAME -U NAMESPACE

# ex:
irissession IRIS -U USER

# il est également possible de démarrer un programme, ex:

irissession IRIS -U %SYS ^%SS

Re @Cyril Grosjean 

Pour exporter la configuration ça doit être se faire via les lignes de commandes que je vous ai communiqué dans le premier message.  Pour le exporter le code, en effetça peut être effectué depuis le portail.

Allez dans "Explorateur système -> classes".

L'interface vous permettra de sélectionner les classes (également les routines et globals) et les exporter\importer:

A ma connaissance, le portail d'administration ne permet pas d'exporter les pages CSP.  Si vous en avez, il faut les copier directement à partir du disque.

Voilà @Franck Hanotin ,

Vous pouvez créer une custom class query comme ceci : 

Class dc.Frank
{

Query QueryAFO() As %Query(ROWSPEC = "key1:%String,key2:%String,key3:%String,key4:%String,key5:%String,key6:%String,datavalue:%String") [ SqlProc ]
{
}

ClassMethod QueryAFOExecute(ByRef qHandle As %Binary) As %Status
{
    Set qHandle("node") = $Name(^AFO)
    Quit $$$OK
}

ClassMethod QueryAFOFetch(
	ByRef qHandle As %Binary,
	ByRef Row As %List,
	ByRef AtEnd As %Boolean) As %Status [ PlaceAfter = QueryAFOExecute ]
{
    Set sc = $$$OK
    Set qHandle("node") = $Query(@qHandle("node"), 1, data)

    If qHandle("node") = "" Set Row = "", AtEnd = $$$YES Quit $$$OK
    ; alimente les champs key x sur base des subscripts de la global
    For i=1:1:$QLength(qHandle("node")) Set $List(Row, i) = $QSubscript(qHandle("node"), i)

    If i < 6 {  ; si nous n'avons pas 6 subscripts, on alimente le reste avec une chaine vide
        For j = i+1:1:6 Set $List(Row, j) = ""
    }

    Set $List(Row, 7) = data, AtEnd = $$$NO
    Quit sc
}

ClassMethod QueryAFOClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = QueryAFOExecute ]
{
	Kill qHandle Quit $$$OK
}

/// juste pour quelques data de tests
ClassMethod set()
{
    s ^AFO("Site","Ville")="66722,3743"
    s ^AFO("Site","Ville","111BB","OBT")=",MMM,XXX,"
    s ^AFO("Site","Ville","111OW","OBT")=",XXX,MMM,"
    s ^AFO("Site","Ville","AANVRBIBS","zzz")    =    "1^^1"
    s ^AFO("Site","Ville","AANVRBIBS","zzz","*","dut")    =    "*afhalen waar gevonden"
    s ^AFO("Site","Ville","AANVRBIBS","zzz","*","eng")    =    "*Pickup where found"
    s ^AFO("Site","Ville","AANVRBIBS","zzz","*","fre")    =    "*Lieu où trouvé"
}

}

Vous pourrez ensuite facilement l'exploiter avec la requête suivante : 

select *
from dc.Frank_QueryAFO()



En mode terminal, vous pouvez aussi simplement utiliser cette ligne pour afficher les résultats : 

Do ##class(dc.Frank).QueryAFOFunc().%Display()

De mon coté, j'ai effectué le test sur IRIS 2023.2, il ne devrait  pas y avoir d'incompatibilité, mais si toutefois vous constatez un problème n'hésitez pas à répondre avec le message d'erreur.

Bonjour @lilian taroua, 

Quel type de documentation cherchez-vous?

La documentation technique officiel est effectivement en anglais et disponible en ligne ici.  Elle peut aussi être téléchargée en version PDF via ce lien.  Sur le site officiel FR, vous pourrez trouver des informations relatives aux différents produits.  Des formations sur IRIS en français sont également possible à ma connaissance.