martes, 24 de septiembre de 2013

Mensajes elegantes con Navision

En este post vamos a ver un tema referente a los mensajes que muestra navision con la funcion MESSAGE. Que aunque la mayoria de programadores tienen esta funcion y su manejo más que trillado, vamos a ver como estructurando el contenido a mostrar podemos hacer un mensaje elegantes utilizando un simple salto de línea.
NameDataType
c1Char
c2Char

c1:=10;
c2:=13;

MESSAGE('%1','Las opciones que estan sumando son:' + FORMAT(c1) + FORMAT(c2) + 'Amortización' + FORMAT(c1) + FORMAT(c2) +
'Indirectos Producción' + FORMAT(c1) + FORMAT(c2) + 'Mantenimiento Matricería' + FORMAT(c1) + FORMAT(c2) + 'Cargas Estructura');

El resultado sería así:

miércoles, 18 de septiembre de 2013

Recorrer registros de una tabla temporal en un Formulario

La posibilidad de definir tablas temporales en los Formularios nos permite ampliar la funcionalidad de manera que con un formulario podemos hacer casi todo lo que necesitamos para interactuar con el usuario. Este post esta pensado para ver como tratar estos datos temporales que el usuario a manipulado desde el formulario. Esta claro que en la mayoria de veces necesitaremos recorrer los datos, ahora veremos como hacer el bucle de una manera correcta.
//RangoActual es una variable de tipo record del mismo tipo que el record del formulario
RangoActual := Rec;
CurrForm.SAVERECORD;

CurrForm.UPDATE(FALSE);
RESET;
IF FINDFIRST THEN BEGIN   REPEAT
     //HAGO MIS OPERACIONES
   UNTIL NEXT = 0;
END;

GET(RangoActual.Clave);

jueves, 12 de septiembre de 2013

Crear nuestra propia funcion split en Navision

En más de una ocasión, programando con navision me he lamentado de la falta de funciones para trabajar con variables que facilita el C/AL en general y la falta de funciones para trabajar con cadenas de texto en particular. No voy a hablar en este post la de veces que me toca ir a ver  como se manipulan los Stream en ejemplos porque su manipulacion en mi opinion es enfarragosa.
En este post vamos a tratar algo tan sencillo como es simular la funcion split (Que tantos lenguajes utilizan) en Navision.
El resultado por supuesto no va a ser el mismo pero nos puede ser muy util a la hora de manejar cadenas de texto.
Cuantas veces nos ha tocado tratar de una direccion de un archivo sacar las carpetas, o el archivo en si, por ejemplo:
'C:\CARPETA\ARCHIVOS\PDF\MIPDF.pdf' nos devuelva MIPDF.pdf o 'C:\CARPETA\ARCHIVOS\PDF' o ARCHIVOS

La primera de las funciones la he llamado ExtraeSubcadena. La funcionalidad consiste en pasarla una cadena de texto con unos parámetros y que nos devuelva unaporcion de texto de esta cadena.
A esta funcion le pasaremos:
  1. Un Texto: Sera el texto que deseamos tratar.
  2. Un Caracter Separador: Nos servira de delimitador.
  3. Direccion: Es un indicador que nos informa si deseamos trocear:
    1. '<' desde el principio a la aparicion del texto deseada indicada en el parametro Posicion.
    2. '>' desde la aparicion del texto deseada indicada en el parametro Posicion hasta el final
    3. '<>' desde la aparicion del texto deseada indicada en el parametro Posicion hasta la siguiente aparición
  4. Posición: El número de aparición del texto delimitador
Funcion ExtraeSubcadena()
Parámetros:
NameDataTypeSubtype
Length
txtTextoText1024
txtSeparadorText1
DireccionText2
PosicionInteger

Variables:
NameDataTypeSubtypeTemporary
SeparadoresRecordIntegerYes
iInteger
//Buscamos las apariciones del carácter separador
Separadores.RESET;
Separadores.DELETEALL;
FOR i:=1 TO STRLEN(txtTexto) DO BEGIN
   IF COPYSTR(txtTexto,i,1) = txtSeparador THEN BEGIN
      Separadores.INIT;
      Separadores.Number := i;
      Separadores.INSERT;
   END;
END;

//Ahora tratamos el textoSeparadores.RESET;
IF Separadores.FINDFIRST THEN BEGIN   Separadores.NEXT(Posicion-1);
   IF Direccion = '<>' THEN BEGIN

     //De la posicion deseada hasta la siguiente
      txtTexto := ExtraeSubcadena(txtTexto,txtSeparador,'>',Posicion);
      IF STRPOS(txtTexto,txtSeparador) > 0 THEN BEGIN         txtTexto := ExtraeSubcadena(txtTexto,txtSeparador,'<',1);
      END;
      EXIT(txtTexto);
   END ELSE BEGIN      IF Direccion = '>' THEN BEGIN

         //De la posicion deseada hasta el final         EXIT(COPYSTR(txtTexto,Separadores.Number+1));
      END ELSE BEGIN

         //Del principio a la posicion deseada         EXIT(COPYSTR(txtTexto,1,Separadores.Number-1));
      END;
   END;
END;

EXIT('');

Una vez tenemos esta función, podemos hacer otra que nos indique cuantas veces aparece nuestro separador, la
Funcion ContarApariciones()
Parámetros:
NameDataTypeSubtype
Length
txtTextoText1024
txtSeparadorText1
DireccionText2
PosicionInteger

Variables:
NameDataTypeSubtypeTemporary
SeparadoresRecordIntegerYes
iInteger
//Buscamos las apariciones del carácter separador
Separadores.RESET;
Separadores.DELETEALL;
FOR i:=1 TO STRLEN(txtTexto) DO BEGIN
    IF COPYSTR(txtTexto,i,1) = txtSeparador THEN BEGIN
       Separadores.INIT;
       Separadores.Number := i;
      Separadores.INSERT;
    END;
END;

//Devolvemos las veces que aparece
Separadores.RESET;
IF Separadores.FINDFIRST THEN BEGIN   EXIT(Separadores.COUNT);
END;
EXIT(0);


Ahora, con la conjuncion de ambas funciones podemos de una manera elegante devolver la carpeta y el archivo de contenido en un texto de la sigiente manera:
MESSAGE('El Archivo es: %1',ExtraeSubcadena('C:\ARCHIVO\MI PDF.pdf','\','>',ContarApariciones('C:\ARCHIVO\MI PDF.pdf','\')));
MESSAGE('La carpeta es: %1',ExtraeSubcadena('C:\ARCHIVO\MI PDF.pdf','\','<',ContarApariciones('C:\ARCHIVO\MI PDF.pdf','\')));

En futuros posts veremos como hacer (Con la misma filosofia, es decir, utilizando recursividad y una tabla temporal del sistema) una funcion que sustituya uno o varios caractéres por uno o varios caractéres.

jueves, 5 de septiembre de 2013

Cambiar Impresora por defecto al imprimir un report de Navision

En este post vamos a tratar un tema que es más que sabido por desarrolladores con experiencia pero por el contrario puede venir bien a más de uno que comience a programar en Navision.
Para esto vamos a utilizar 2 tablas que hay en navision:
  1. La tabla 78 Seleccion Impresora
  2. La tabla Printer que es una tabla virtual y no esta accesible en el object designer.
La primera contiene una relación de usuario, report e impresora para definir como se imprimen los informes para los usuarios. La segunda es una tabla virtual que tiene una relacion de las impresoras del usuario y esta accesible en tiempo de ejcucion.
Con la combinacion de ambas tablas no es complicado ver la manera de asignar una impresora concreta antes de ejecutar un informe.



NameDataTypeSubtype
recImpresoraRecordPrinter
recImpresoraSeleccionRecordSelección impresora
recImpresoraSeleccionPreviaRecordSelección impresora
//Comprueba a ver si la impresora existe en el cliente, si no existe muestro un error
recImpresora.SETFILTER(Name,'EL NOMBRE DE MI IMPRESORA');
IF NOT recImpresora.FIND('-') THEN
   EXIT(FALSE);

//Buscamos una asignación previa, la guardamos para dejarlo todo como estaba y la borramos
IF recImpresoraSeleccion.GET(UPPERCASE(USERID), 50000) THEN BEGIN
   recImpresoraSeleccionPrevia.RESET;
   recImpresoraSeleccionPrevia.INIT;
   recImpresoraSeleccionPrevia.TRANSFERFIELDS(recImpresoraSeleccion);
   recImpresoraSeleccionPrevia.INSERT;
   recImpresoraSeleccion.DELETE;
END;

//Insertamos nuestra asignacion de impresora al report 50000
recImpresoraSeleccion.INIT;
recImpresoraSeleccion."Nº informe"       := 50000;
recImpresoraSeleccion."Id. usuario"      := UPPERCASE(USERID);
recImpresoraSeleccion."Nombre impresora" := 'EL NOMBRE DE MI IMPRESORA';
recImpresoraSeleccion.INSERT;


//Imprimimos nuestro informe
ReportFacturaVenta.RUNMODAL;

//Borramos nuestra asignacion
IF recImpresoraSeleccion.GET(UPPERCASE(USERID), 50000) THEN BEGIN
   recImpresoraSeleccion.DELETE;

  //Restauramos la asignacion previa
   IF DELCHR(recImpresoraSeleccionPrevia."Nombre impresora",'<>',' ') <> '' THEN BEGIN
      recImpresoraSeleccion.INIT;
      recImpresoraSeleccion."Nº informe"       := 50000;
      recImpresoraSeleccion."Id. usuario"      := UPPERCASE(USERID);
      recImpresoraSeleccion."Nombre impresora" := recImpresoraSeleccionPrevia."Nombre impresora";
      recImpresoraSeleccion.INSERT;
   END;
END;



jueves, 29 de agosto de 2013

Añadir a Favoritos del IE un informe de Reporting Services desde Navision

Hemos visto en anteriores posts como lanzar un informe de reporting services. En este posts vamos a ver como podemos antes de abrirlo con el IE guardarlo en los favoritos para que se pueda sacar de manera rapida desde el IE sin necesidad de abrir navision.

Los Favoritos del IE es una carpeta dentro de la carpeta de usuario de windows que contiene los links a las paginas almacenadas como favoritas. En este ejemplo, vamos a ver como crear un link sencillo, pero las posibilidades del link permiten adornarlo mucho más.
Tambien vamos a crear una carpeta dentro de Favoritos para así ver como podriamos crear una estructura organizada por carpetas.

Dependiendo del SO puede ser que tengamos la carpeta Favoritos o en su defecto Favorites. En este ejemplo veremos como comprobar cual es la correcta para almacenar nuestro link.

NameDataTypeSubtype
CarpetaPersonalText
CarpetaAutomation'Microsoft Scripting Runtime'.FileSystemObject
CarpetaMiaText
TempInformesRecordInformes
LCarpetaFavoritosText
ContinuarBoolean
FicheroFile
LinkText

//Creo las variables de la carpeta de favoritos mia que voy a crear, la del link de mi informe y
 //la variable de entorno donde esta la carpeta favoritos
   CarpetaPersonal := ENVIRON('USERPROFILE');
    CarpetaRaiz := 'MIS INFORMES';
    Link := 'http://servidor/......MIS INFORME';
//Creo el controlador de carpetas
   IF ISCLEAR(Carpeta) THEN
      CREATE(Carpeta);
//Compruebo si existe en español o ingles
   IF Carpeta.FolderExists(CarpetaPersonal + '\Favorites') THEN BEGIN
      Continuar := TRUE;
      LCarpetaFavoritos := '\Favorites';
   END ELSE BEGIN
      IF Carpeta.FolderExists(CarpetaPersonal + '\Favoritos') THEN BEGIN
         Continuar := TRUE;
         LCarpetaFavoritos := '\Favoritos';
      END ELSE BEGIN
         Continuar := FALSE;
      END;
   END;
//Continuo si encuentro Favoritos en el idioma español o ingles
   IF Continuar = TRUE THEN BEGIN
      //Creo mi carpeta dentro de favoritos
      IF NOT Carpeta.FolderExists(CarpetaPersonal + LCarpetaFavoritos + '\'+ CarperaRaiz) THEN BEGIN
         Carpeta.CreateFolder(CarpetaPersonal + LCarpetaFavoritos + '\' + CarperaRaiz);
      END;
     //Creo el fichero .url dentro de mi carpeta de favoritos Informe de Prueba.url es el nombre que aparecera en el menu de favoritos
     Fichero.CREATE(CarpetaPersonal + LCarpetaFavoritos + '\' + CarperaRaiz + '\' + 'Informe de Prueba.url');
     Fichero.WRITEMODE := TRUE;
     Fichero.TEXTMODE  := TRUE;
     Fichero.WRITE('[InternetShortcut]');
     Fichero.WRITE('URL='+Link);
     Fichero.WRITE('IDList=');
     Fichero.CLOSE;
END;

Con este código podemos facilitar el sacar informes de Reporting Services haciendolo directamente desde IE.

miércoles, 21 de agosto de 2013

Imprimir archivos con Navision

Como habiamos comentado en un post anterior, vamos a explicar como imprimir archivos de una manera rápida y sencilla desde Navision.
En post previos habiamos visto como descargar un informe desde Reporting Services y guardarlo en PDF. Ahora en este post vamos a coger un fichero PDF e imprimirlo.
Para eso utilizaremos el SHELL de windows. Lo que vamos a hace es simular lo que hace el boton derecho del raton sobre un fichero pdf

Cuando se desplegan las opciones tenemos la posibilidad de imprimir. Desde C/AL vamos a hacer esto mismo:

NameDataTypeSubtype
objShellAutomation'Microsoft Shell Controls And Automation'.Shell
objFolderAutomation'Microsoft Shell Controls And Automation'.Folder
objFolderItemsAutomation'Microsoft Shell Controls And Automation'.FolderItems
objFolderItemAutomation'Microsoft Shell Controls And Automation'.FolderItem
objVerbsAutomation'Microsoft Shell Controls And Automation'.FolderItemVerbs
objVerbAutomation'Microsoft Shell Controls And Automation'.FolderItemVerb
iInteger
 
//Funcion que imprime un archivo pasado de una carpeta especifica
funcion imprimirarchivo(txtCarpeta text 1024,txtArchivo text 1024)
{
//Creamos el objeto SHELL y todos los relacionados para obtener el menu contextual
CREATE(objShell);
objFolder          := objShell.NameSpace(DELCHR(txtCarpeta,'>','\'));
objFolderItems := objFolder.Items;
objFolderItem   := objFolderItems.Item(txtArchivo);
objVerbs           := objFolderItem.Verbs;
i  
                       := -1;
//Recorremos las opciones de menu para encontrar el que pone IMPRIMIR
REPEAT
   i := i+1;
   IF (i < objVerbs.Count) THEN BEGIN
      objVerb := objVerbs.Item(i);
   END;
UNTIL (ControlImprimir(objVerb.Name) = TRUE) OR (i >= objVerbs.Count);

IF (i <= objVerbs.Count) THEN BEGIN
   //Ejecutamos la opción de menú que pqueremos
   objVerb.DoIt;
   EXIT(TRUE);
END ELSE BEGIN
   EXIT(FALSE);
END;

}
//Funcion que nos devuelve si la opcion de menu es la que queremos
funcion imprimirarchivo(txtCarpeta text 1024,txtArchivo text 1024)
{
//Comprobamos que es la de imprimir.
IF STRPOS(UPPERCASE(DELCHR(txtTexto,'=','&')),'IMPRIMIR')>0 THEN BEGIN
   EXIT(TRUE);
END;
EXIT(FALSE);

}

De esta manera podemos imprimir los ficheros deseados. Combinando la descarga del Reporting Services de post anteriores con la impresion de archivos de este post podemos imprimir los informes de reporting directamente sin la intervencion del usuario.

miércoles, 14 de agosto de 2013

Obtener el Servidor y la Base de Datos actual en SQL

Cuando queremos acceder a la Base de datos y/o Servidor actual por algún motivo (Bien sea una consulta por ADO o para ver las BBDD que tenemos creadas) C/AL nos lo pone muy sencillo al tener a nuestra disposición unas tablas virtuales que nos exponen estos datos:
NameDataTypeSubtype
recServerRecordServer
recDatabaseRecordDatabase

Pero cuando accedemos por web service nos da un error porque las tablas virtuales no tienen generados los metadatos para ser utilizadas por estos.
Una de las formas de conseguirlo es mediante el archivo Config de los web services donde esta indicado el servidor y la BBDD. El archivo es CustomSettings.config y esta en la carpeta de la aplicacion. Este archivo es un XML con los parametros de configuracion que los web services desplegan al iniciar el servicio.

NameDataTypeSubtype
recServerRecordServer
recDatabaseRecordDatabase
recUsuarioSQLRecordUsuario
cu50203CodeunitCodeSQL
DomDocAutomation'Microsoft XML, v4.0'.DOMDocument
DomNodeAutomation'Microsoft XML, v4.0'.IXMLDOMNode
MyServerNameText
MyBBDDNameText
//Comprobamos si es el WEB Service o no
IF ISSERVICETIER THEN BEGIN
  //Creamos un objeto XML para tratar el archivo config  IF ISCLEAR(DomDoc) THEN
    CREATE(DomDoc);
  

  //Cargamos el archivo config
  DomDoc.load(APPLICATIONPATH + 'CustomSettings.config');

  //Leemos Los Nodos
  DomNode := DomDoc.selectSingleNode('//appSettings/add[@key='DatabaseServer']');
  MyServerName := DomNode.attributes.item(1).text;
  DomNode := DomDoc.selectSingleNode('//appSettings/add[@key='DatabaseName']');
  MyBBDDName := DomNode.attributes.item(1).text;

END ELSE BEGIN

   //Si no es WEB Service lo hacemos de la manera tradicional
   recServer.RESET;
   recServer.SETRANGE("My Server",TRUE);
   IF recServer.FINDSET THEN BEGIN
       recDatabase.RESET;
       recDatabase.SETRANGE("My Database",TRUE);
       IF recDatabase.FINDSET THEN BEGIN
          MyServerName := recServer."Server Name";

          MyBBDDName :=recDatabase."Database Name";
       END;
   END;
END;


Con esto tenemos resuelto el problema.