Algunas funciones internas de Sales Header y Sales Line

Una de las tablas con las que seguramente más trabajamos es la tabla 36 (Sales Header) y la tabla 37 (Sales Line). Como ya sabemos en ellas empiezan muchos de los procesos de venta pues es ahí donde los usuarios crearan los documentos de venta que posteriormente generaran albaranes, facturas, etc.

Es habitual por lo tanto que tengamos que trabajar continuamente sobre ellas por ejemplo, automatizando procesos que crean cabeceras, líneas o que modifican su información.

Hay unas cuantas funciones que por las prisas del día a día no siempre nos detenemos a observar, pero que es muy interesante conocer y analizar cómo funciona:

SuspendStatusCheck:
Esta función se encuentra en la tabla 37. Su funcionamiento es muy sencillo ya que lo único que nos permite es modificar el valor de una variable global de la tabla. La gracia está en que esa variable condiciona muchos de los controles que se ejecutan con las distintas reglas de negocio. Por lo tanto podemos entender que es como dar un pase VIP a las validaciones que hagamos sobre ese registro desde ese momento en adelante.

SetHideValidationDialog:
Esta función se encuentra en la tabla 36. Su funcionamiento es muy parecido a la función que acabamos de ver, solo que en este caso nos permite pasar por encima de muchos CONFIRM que podemos encontrar en el código. Es muy común utilizarla en procesos que tienen que generar varios documentos de venta de forma automática ya que a través de esta llamada se consigue que el proceso no se pare por un mensaje de confirmación al usuario.

GetSalesHeader/SetSalesHeader:
Por desgracia, estas dos funciones son las grandes desconocidas para muchos y son muy importantes. Se encuentran dentro de la tabla 37 y sirven para situar la variable global SalesHeader en la cabecera relacionada a la línea en la que estamos. Os animo a que busquéis en toda la tabla 37 una sola línea de código que diga SalesHeader.GET. Encontrareis muy pocas o ninguna en una base de datos limpia y en cambio veréis que hay varios montones en bases de datos muy desarrolladas. Ahora os animo a que busquéis GetSalesHeader… hay muchísimas… ¿El motivo? Principalmente hay dos:

  • La función esta optimizada para que solo se haga el GET si es necesario. Si tenemos a la variable colocada sobre la cabecera que toca, ¿para qué volver a hacer el GET? Menos accesos a disco, mayor rendimiento.
  • La función se basa en la colocación de la variable SalesHeader y eso muy bueno cuando la misma variable puede venir referenciada por SetSalesHeader puesto que puede tratarse, por ejemplo de una variable TEMPORAL.
GetSalesHeder y SetSalesHeader
GetSalesHeder y SetSalesHeader

Todo esto de poder jugar a hacer GET de un registro que no existe está muy bien y funciona de maravilla hasta que alguien viene e incrusta su primer SalesHeader.GET en alguno de los disparadores de la tabla 37. A partir de ese momento, nada de todo esto funciona.

Si queréis un ejemplo de todo esto pensad es que tenéis que hacer un simulador de precios en el que tienen que intervenir todas las reglas de negocio que se deberían de aplicar en la confección de un pedido. Podéis empezar creando una cabecera Temporal. En esta cabecera, podéis validar varios campos llamando al inicio a SetHideValidationDialog para ahorrar al usuario el tener que ir interviniendo. Luego podríamos pasar a las líneas que, llamando previamente a SetSalesHeader no tendrían problema en encontrar la cabecera aunque esta no se encuentre realmente en la base de datos. Si además utilizamos SuspendStatusCheck, seguramente también nos ahorraremos algún que otro control que se haga durante la inserción de las líneas.

Error Stack

GETLASTERRORTEXT es una función que permite capturar el último error que se ha dado en la ejecución de un proceso. A priori, no parece algo muy útil, pero cuando pensamos en la ejecución condicional de codeunits, esta función cobra sentido.

A partir de ahí se me ocurrió una idea: ¿Y si utilizo esta función para capturar los posibles errores que pudiera tener en la ejecución de un proceso? ¿Podría diseñar una herramienta capaz de indicarle las acciones o campos que quiero validar y que me devuelva la lista de resultados que obtengo?

Para llegar a tener esta herramienta, pensé en que funciones debían de ser importantes:

  • Primero, necesitaría una función para indicarle el registro que quiero validar, el campo y el valor que quiero informar.
  • Segundo, seria necesario una función para lanzar la validación del propio campo, con los valores aportados anteriormente. El éxito o error de la validación del campo, se tendría que guardar en una pila o stack con el resultado de la ejecución.
  • Tercero A, disponer de una función para mostrar la lista de errores encontrados hasta el momento.
  • Tercero B, si no deseo mostrar la lista de errores, tener la posibilidad de recoger los resultados para tratarlos posteriormente.
  • Cuarto, disponer de una función para decidir manualmente cuando quiero limpiar la pila de errores.

Las utilidades de una herramienta como esta pueden ser varias como por ejemplo la importación masiva de datos, registro masivo de documentos, etc. Es cierto que sólo está pensado para validar campos, pero yo puedo perfectamente crear un campo booleano que al validarse, lance un pedido, por ejemplo.

Os dejo la herramienta para que la utilicéis a vuestro antojo. Espero en breve poder añadir uno o varios ejemplos de como utilizarla:

Sergisoft Error Stack v1

Primeros pasos con OData (Parte 1)

Con el lanzamiento de NAV 2013, disponemos de una nueva forma de acceder a los datos de NAV. Esta nueva vía ofrece de manera muy sencilla, segura y rápida autonomía a todo tipo de usuarios para poder extraer la información y construir sus propios listados interactivos.

En este ejemplo vamos a ver paso a paso como utilizar los servicios de OData con un plug-in de Excel que puede obtenerse totalmente gratis: PowerPivot. De forma práctica veremos cómo podemos construir una tabla dinámica segmentada por prácticamente cualquier campo que pueda afectar al Stock. Simplemente es una ejemplo didáctico, aunque las posibilidad son infinitas.

Lo primero que tenemos que identificar es el puerto con el que se ha configurado la instalación de NAV. En mi caso, es el puerto 7078, pero en una instalación estándar se utiliza el puerto 7048:

Microsoft Dynamics NAV Administration

Podemos consultar el puerto que se configuró durante la instalación consultando el nuevo Microsoft Dynamics NAV Administration.

A continuación, abrimos una nueva hoja de excel y buscamos la opción de PowerPivot:

Acceso a Power Pivot desde Excel

Para poder insertar datos en PowerPivot, estos tienen que estar publicados en NAV. Para ver y modificar los elementos que están publicados iremos a Departamentos – Administración – Administración de IT – General – Servicios web.

Para este ejemplo vamos a publicar las siguientes páginas de NAV:

Servicios Web publicados

Ahora ya está todo listo. Para indicarle a PowerPivot cómo tiene que recoger nuestros datos utilizaremos la opción de Obtener datos externos de una fuente de distribución de datos:

Obtener datos externos de una fuente de distribución de datos

En este punto, indicaremos en el segundo campo la ruta de acceso a los servicios de OData (teniendo en cuenta lo que hemos comentado más arriba sobre el puerto):

Acceso a los datos publicados

En la siguiente pantalla le indicaremos que conjuntos de datos queremos obtener:

Acceso a los datos publicados

En este punto, PowerPivot hará la conexión con nuestra base de datos y recuperará todos los datos:

Datos recuperados correctamente

Una vez hemos recuperado los datos, tenemos que indicarle a PowerPivot la relación que hay entre las distintas tablas. En este ejemplo, hemos recuperado la tabla de productos y la tabla de movimientos de producto. Lo que hacemos es decirle que el campo Item_No de los movimientos de producto está relacionado con el campo No de la tabla producto:

Crear relación de PowerPivot

Crear relación de PowerPivot

En este punto ya tenemos los datos en PowerPivot y las relaciones que hay entre todos ellos. Por lo tanto, podemos cerrar PowerPivot y regresar a la hoja de Excel origen.

Ya en Excel vamos a insertar una nueva tabla dinámica, pero atención, esta tabla dinámica va ser una tabla dinámica de PowerPivot y no una tabla dinámica convencional. Pronto veremos las diferencias:

Insertar tabla dinámica

Como vemos, tenemos a nuestra disposición todos los datos de PowerPivot y ya podemos empezar a jugar colocando campos como etiquetas de fila, suma de valores y segmentos. En este ejemplo hemos montado esta configuración (todos los campos pertenecen a mov. producto):

Configuración de tabla dinámica de stock con segmentos

Y el resultado es el siguiente:

Tabla dinámica de stock con segmentos

La principal ventaja de los segmentos es que podemos interactuar con ellos seleccionando uno o varios. Al seleccionar los datos que se muestran varían pero también afecta al resto de segmentos de la tabla dinámica.

Tabla dinámica de stock con segmentos

 Por lo tanto, se convierte en una herramienta muy ágil para la explotación avanzada de los datos.

 

Pasar de XMLDocument a BigText y viceversa

Después de bastante tiempo utilizando mis propias funciones para hacer la conversión de BigText a XML y de XML a BigText, he descubierto que ya estaban hechas de forma estándar en NAV.  Para copiar y reutilizar estas funciones simplemente hay que editar la Codeunit 8701 – Mobile Document Management:

Como consuelo me queda el ver que lo que yo había pensado se parece bastante a lo que habían pensado los MS Redmond Guys.

Una idea feliz: Construir una cadena de ordenación de ficheros

En ocasiones, hacemos procesos que generan ficheros de forma masiva. Estos ficheros se acumulan en una ruta especificada y, cuando tenemos que consultarlos, no vemos la forma sencilla de ordenarlos por fecha. Es cierto que una de las propiedades de un fichero es la fecha de modificación pero en ocasiones esta no puede ser del todo fiable. Con esta función, podemos construir fácilmente una cadena que contiene AÑO MES DÍA HORA MINUTO SEGUNDO que resulta ideal para utilizar como nombre de un fichero:


FechaInversa

l_txtAnyo := FORMAT(DATE2DMY(TODAY,3));
l_txtMes := FORMAT(DATE2DMY(TODAY,2));
l_txtDia := FORMAT(DATE2DMY(TODAY,1));

l_txtHoraCompleta := FORMAT(TIME);
l_txtHora := COPYSTR(l_txtHoraCompleta,1,STRPOS(l_txtHoraCompleta,':')-1);

l_txtHoraCompleta := COPYSTR(l_txtHoraCompleta,STRPOS(l_txtHoraCompleta,':')+1);
l_txtMinuto := COPYSTR(l_txtHoraCompleta,1,STRPOS(l_txtHoraCompleta,':')-1);

l_txtHoraCompleta := COPYSTR(l_txtHoraCompleta,STRPOS(l_txtHoraCompleta,':')+1);
l_txtSegundo := l_txtHoraCompleta;

IF STRLEN(l_txtAnyo) = 2 THEN l_txtAnyo := '20'+l_txtAnyo;
IF STRLEN(l_txtMes) = 1 THEN l_txtMes := '0'+l_txtMes;
IF STRLEN(l_txtDia) = 1 THEN l_txtDia := '0'+l_txtDia;
IF STRLEN(l_txtHora) = 1 THEN l_txtHora := '0'+l_txtHora;
IF STRLEN(l_txtMinuto) = 1 THEN l_txtMinuto := '0'+l_txtMinuto;
IF STRLEN(l_txtSegundo) = 1 THEN l_txtSegundo := '0'+l_txtSegundo;

EXIT(l_txtAnyo+l_txtMes+l_txtDia+l_txtHora+l_txtMinuto+l_txtSegundo);

Espero que os sea muy útil.

Cuando NAS nos dice que las opciones posibles son Yes/No…

En este blog se ha hablado largo y tendido sobre NAS. Es posible que alguna vez os hayáis encontrado con algún tipo de problema al arrancar el servicio y que este os diga algo parecido a “Sí no es una opción. Las opciones posibles son Yes/No”. El principal problema de esto es el idioma que está utilizando NAS para entrar en NAV. Buscando un poco de información he visto que se comenta lo siguiente:

  • Copiar las carpetas de idiomas de la carpeta client a la carpeta application server.
  • Utilizar código en la codeunit 1 (evento NasHandler) que modifique el idioma que se utiliza al entrar en la base de datos. Para esto se suele utilizar GLOBALLANGUAGE.

Pero últimamente lo que me ha funcionado ha sido lo siguiente:

  • Abrir la carpeta %AppData% (se puede escribir directamente en inicio > ejecutar).
  • Buscar y borrar el archivo NaviBP.xml. Este archivo sólo contiene una lista con los breakpoints que el usuario ha marcado en el debbuger de NAV.

Puede parecer absurdo, pero en algunas ocasiones, borrar este fichero ha sido la solución al problema.

Fechas en inglés en WebServices

Quizás habéis visto que las fechas que son devueltas por un WebService de NAV en formato texto y que han sido convertidas a través de FORMAT(fecha) vienen en el formato MM/DD/YYYY en vez de DD/MM/YYYY.

La solución es muy sencilla: Abrid el cliente de roles utilizando el mismo usuario que utiliza vuestro WebService. Una vez dentro poned el idioma a español y salid. Con esto, se modificará un registro en la tabla 2000000073 (User personalization) donde queda guardado el idioma que utiliza el usuario. Después de esto, hay que reiniciar el servicio NAV Business Web Services.

Si después de esto, todavía sigue sin aparecer correctamente, hay que verificar que en la carpeta C:Program Files (xxx)Microsoft Dynamics NAV60Service no sólo tenemos el idioma ENU. Para instalar más idiomas, hay que buscar en el DVD de NAV la ruta InstallersESServer y ejecutar el instalador. Por último, como no, hay que volver a reiniciar el servicio.

ACTUALIZACIÓN: No recomiendo reiniciar los servicios de NAV. Es mejor parar los dos servicios de tercera capa y volver a arrancarlos.

El log de cambios (Parte 2)

En la primera entrega de este “especial” log de cambios conocimos la funcionalidad del log de cambios, como podemos activarla y cómo podemos configurarla para monitorizar los cambios que se producen en cualquier registro.

En esta segunda entrega, nos vamos a centrar en cómo funciona el log de cambios de forma interna. Conociendo cómo funciona, podremos tener la base para desarrollar funcionalidades que afecten a toda la aplicación como gestiones de aprobaciones de cambios, alertas, sincronización con otros sistemas, etc.

Si vemos la codeunit 423 (Change Log Management) vemos que hay una función un tanto curiosa. La función GetTableTriggerMask devuelve un valor entero en función de la configuración de monitorización que tengamos en log de cambios. El entero se incrementa en 1 si se controlan las inserciones, 10 si se controlan las modificaciones y 4 si se controlan las eliminaciones. Si no se hace ningún tipo de control el valor del entero se queda en 0 (cero). Si pensamos en todas las combinaciones posibles, no hay ninguna combinación que repita una valor entero igual:

Inserción Modificación Eliminación Total
I+M+E 1 10 4 15
I+M 1 10   11 
I+E 1   4
I 1     1
M+E   10 4 14
M   10   10
E     4 4
(nada)       0

OK, todo esto está muy bien pero ¿para que sirve este número?
A nosotros no nos sirve para nada, pero NAV incorpora en su codeunit 1 una función que le dice a NAV si debe disparar 3 eventos especiales. En la codeunit 1 existe la función GetGlobalTriggerMask que precisamente llama a la función que acabamos de ver. Lo que hace esta función es devolverle al sistema que tiene que hacer (devolviendo el valor de la tabla que hemos visto antes).

De acuerdo, supongamos que tenemos una tabla en la que controlamos todo. Esta función devolverá a NAV el valor 15. ¿que hace ahora NAV?. Internamente llama a las funciones (también de la codeunit 1) OnGlobalInsert, OnGlobalModify, OnGlobalDelete y OnGlobalRename.

Estas funciones se ejecutan con el parámetro de la referencia del registro, que podemos recoger y usar a nuestro antojo. En el caso del log de cambios, estas funciones vuelven a llamar a la codeunit 423 que se encarga de guardar los posibles cambios que ha sufrido el registro.