miércoles, 24 de julio de 2019

D365FO | Cómo personalizar el diseño de un reporte SSRS

En nuestro ejemplo procederemos con el desarrollo y la personalización de un reporte SSRS en D365FO. El objetivo aquí es agregar un campo de extensión al diseño de un reporte estándar sin over-layering. Utilizaremos el reporte de Confirmación de pedido de venta para agregar el nombre del grupo de venta.

Cree un nuevo proyecto en un nuevo modelo, el mismo que podrá ser utilizado como un nuevo paquete de extensión. Es una buena práctica tener un nuevo modelo para la personalización de reportes. Ya que al usar el concepto de extensiones, este nos permite agregar funcionalidad a los elementos de modelos existentes.


Los reportes, como el de Confirmación de pedido de venta, utilizan tablas temporales para facilitar la escritura del diseño. Por lo que crearemos una extensión de dicha tabla.
- Localice la tabla SalesConfirmHeaderTmp en el Explorador de aplicaciones.
- Haga clic derecho en la tabla y elija Crear extensión.



- Cambie el nombre de la extensión. En nuestro caso SalesConfirmHeaderTmp.HAExtension.
- Abra la tabla en el diseñador y agregue el nuevo campo.


  
- Localice el reporte SalesConfirm en el Explorador de aplicaciones.
- Haga clic derecho en el reporte y elija Duplicar en proyecto.



- Cambie el nombre del reporte. En nuestro caso HASalesConfirm.
- Abra el reporte en el diseñador y expanda el nodo Datasets y SalesConformHeaderDS. Luego, busque el nuevo campo de extensión en el nodo Campos.



- Si este no aparece, haga clic derecho en el Dataset y seleccione Restaurar.



- Ahora puede proceder a diseñar el reporte según sus requisitos.



- Cree una nueva clase de extensión que amplíe la clase de control de reportes estándar.


 class HASalesConfirmControllerExt extends SalesConfirmController  
 {  
   public static HASalesConfirmControllerExt construct()  
   {  
     return new HASalesConfirmControllerExt();  
   }  
 }  

- Agregue el método main, que haga referencia al diseño del reporte personalizado.


 class HASalesConfirmControllerExt extends SalesConfirmController  
 {  
   public static void main(Args _args)  
   {  
     SrsReportRunController formLetterController = HASalesConfirmControllerExt::construct();  
     HASalesConfirmControllerExt controller;  
     controller = formLetterController;  
     controller.initArgs(_args, ssrsReportStr(HASalesConfirm, Report));  
     if (classIdGet(_args.caller()) == classNum(SalesConfirmJournalPrint))  
     {  
       formLetterController.renderingCompleted += eventhandler(SalesConfirmJournalPrint::renderingCompleted);  
     }  
     formLetterController.startOperation();  
   }  
 }  

- Finalmente, agregue el método outputReport.


 class HASalesConfirmControllerExt extends SalesConfirmController  
 {  
   /// <summary>  
   ///  
   /// </summary>  
   protected void outputReport()  
   {  
     SRSCatalogItemName reportDesign;  
     reportDesign = ssrsReportStr(HASalesConfirm,Report);  
     this.parmReportName(reportDesign);  
     this.parmReportContract().parmReportName(reportDesign);  
     formletterReport.parmReportRun().settingDetail().parmReportFormatName(reportDesign);  
     super();  
   }  
 }  

- Necesitará implementar el reporte HASalesConfirm. Para hacer esto, haga clic derecho en el reporte y elija Implementar reportes.

- Cree una nueva clase de control de eventos y añada el evento onInserting de la tabla temporal de la siguiente manera:
Para agregar la lógica que ingresará el dato del campo extendido.

 class HASalesConfirmReportManager  
 {  
   /// <summary>  
   ///  
   /// </summary>  
   /// <param name="sender"></param>  
   /// <param name="e"></param>  
   [DataEventHandler(tableStr(SalesConfirmHeaderTmp), DataEventType::Inserting)]  
   public static void SalesConfirmHeaderTmp_onInserting(Common sender, DataEventArgs e)  
   {  
     SalesConfirmHeaderTmp header = sender;  
     SalesTable salesTable;  
     select SalesPoolId from salesTable  
       where SalesTable.SalesId == header.SalesId;  
     header.HASalesPoolName = SalesPool::find(SalesTable.SalesPoolId).Name;  
   }  
 }  

- Para probar esto, procedemos a confirmar un pedido de venta en D365FO.



jueves, 18 de julio de 2019

D365FO | Creación de métodos para control de eventos de datos

Los controladores de eventos de datos manejan los delegados expuestos en cada tabla, y estos se activan cuando ocurre el evento.
Hay dos métodos para cada uno: un método presente-continuo (ing) y un método en tiempo pasado (ed). Los métodos ing se invocan antes de que se active el evento, y el método ed se dispara después del evento.  
En nuestro ejemplo, el controlador de eventos se utiliza para asignar, validar y actualizar un campo. Por lo tanto, vamos a colocar estos métodos en una clase de ayuda. 

En nuestro caso, tenemos como escenario de prueba la aplicación para generar Renta de vehículos. Los registros se almacenan en la tabla HAVehicleRentTable.



- Crear una nueva clase. En nuestro caso HAVehicleRentManager.
- Abra el diseño de la tabla en cuestión, expanda el nodo Eventos y ubique onInserting.



- Haga clic derecho en el evento y elija Copiar el método del controlador de eventos.



- Abra la clase HAVehicleRentManager y pegue el código generado anteriormente en el cuerpo de la clase, como se muestra aquí:
 class HAVehicleRentManager  
 {  
   /// <summary>  
   ///  
   /// </summary>  
   /// <param name="sender"></param>  
   /// <param name="e"></param>  
   [DataEventHandler(tableStr(HAVehicleRentTable), DataEventType::Inserting)]  
   public static void HAVehicleRentTable_onInserting(Common sender, DataEventArgs e)  
   {
  
   }  
 }  

- A continuación, tendremos que escribir el código de la siguiente manera:
 class HAVehicleRentManager  
 {  
   /// <summary>  
   ///  
   /// </summary>  
   /// <param name="sender"></param>  
   /// <param name="e"></param>  
   [DataEventHandler(tableStr(HAVehicleRentTable), DataEventType::Inserting)]  
   public static void HAVehicleRentTable_onInserting(Common sender, DataEventArgs e)  
   {  
     HAVehicleRentTable vehicleRentTable = sender;  
     vehicleRentTable.numberDays = vehicleRentTable.toDate - vehicleRentTable.fromDate;  
     info(strFmt("Renta %1",vehicleRentTable.RentId));  
   }  
 }  

- Para probar esto, realice una compilación completa que incluya la sincronización de la base de datos. Y al crear una nueva renta de vehículo, el evento ejecuta nuestro código.



- Asignando el número de días y notificando el número de renta creado.



Al controlar campos en un diseño, también necesitamos controlar eventos como modifiedField y validateFieldPara controlarlos, nos suscribiremos al evento de datos apropiado.

- Considerando lo antes mencionado. Abra el diseño de la tabla HAVehicleRentTable, haga clic derecho en el evento onValidatedField y elija Copiar el método del controlador de eventos.



- Pegue el código generado en el cuerpo de la clase HAVehicleRentManager, y escriba el código del evento de la siguiente manera:
 class HAVehicleRentManager  
 {  
   /// <summary>  
   ///  
   /// </summary>  
   /// <param name="sender"></param>  
   /// <param name="e"></param>  
   [DataEventHandler(tableStr(HAVehicleRentTable), DataEventType::ValidatedField)]  
   public static void HAVehicleRentTable_onValidatedField(Common sender, DataEventArgs e)  
   {  
     ValidateFieldEventArgs fieldArgs = e;  
     HAVehicleRentTable vehicleRentTable = sender;  
     Boolean ok = true;  
     switch(fieldArgs.parmFieldId())  
     {  
       case fieldnum(HAVehicleRentTable,toDate):  
         if(vehicleRentTable.toDate < vehicleRentTable.fromDate)  
         {  
           ok = checkFailed("Fecha");  
         }  
         break;  
     }  
     if(!ok)  
     {  
       fieldArgs.parmValidateResult(false);  
     }  
   }  
 }  

Para probar esto, realice una compilación completa que incluya la sincronización de la base de datos. Y al modificar el valor del campo de fecha "Hasta", el evento ejecuta nuestra validación.



- Finalmente, abra el diseño de la tabla HAVehicleRentTable, haga clic derecho en el evento onModifyingField y elija Copiar el método del controlador de eventos.



Pegue el código generado en el cuerpo de la clase HAVehicleRentManager, y escriba el código del evento de la siguiente manera:
 class HAVehicleRentManager  
 {  
   /// <summary>  
   ///  
   /// </summary>  
   /// <param name="sender"></param>  
   /// <param name="e"></param>  
   [DataEventHandler(tableStr(HAVehicleRentTable), DataEventType::ModifyingField)]  
   public static void HAVehicleRentTable_onModifyingField(Common sender, DataEventArgs e)  
   {  
     ModifyFieldEventArgs fieldArgs = e;  
     HAVehicleRentTable vehicleRentTable = sender;  
     //Boolean ok = true;  
     switch(fieldArgs.parmFieldId())  
     {  
       case fieldnum(HAVehicleRentTable,toDate):  
         vehicleRentTable.numberDays = vehicleRentTable.toDate - vehicleRentTable.fromDate;  
         break;  
     }  
   }  
 }  

Para probar esto, realice una compilación completa que incluya la sincronización de la base de datos. Y al modificar el valor del campo de fecha "Hasta", el evento ejecuta nuestra actualización.



- Otras clases especializadas en DataEventArgs que son útiles son las siguientes:

miércoles, 30 de enero de 2019

D365FO | Creación de nuevo módulo (Secuencias numéricas).

La creación de nuevos módulos con su respectiva parametrización (secuencias numéricas), nos permite exponer al usuario una visión adaptable del sistema. Con un ejemplo, mostraremos la forma de crearlos y hacerlos parametrizables como cualquier otro módulo, propio de D365FO.

- En Visual Studio cree un nuevo proyecto en un nuevo modelo, el mismo que podrá ser utilizado como un nuevo paquete de extensión.
 
- De clic derecho sobre el BaseEnum NumberSeqModule y elija Crear extension.
- Recordando que todos los nombres de los elementos deben ser únicos, cambie el nombre de la nueva extensión de NumberSeqModule.Extension a NumberSeqModule.[PREFIJO]Extension. Para nuestro caso es NumberSeqModule.HAExtension
- De clic derecho sobre NumberSeqModule:HAExtension y elija Nuevo Elemento. Dele un nombre y una etiqueta en sus propiedades. En nuestro caso Name: HAVehicle, Label: Vehículo.
- Crear un nuevo EDT String, dele un nombre y una etiqueta. En nuestro caso Name: HAVehicleId, Label: Código de vehículo, String Size: 20. 
 
- Crear una tabla de parámetros para el nuevo módulo: HAVehicleParameters. La tabla debe tener un campo Key (EDT : ParametersKey) y un método find.
 static HAVehicleParameters find(boolean _forupdate = false)  
   {  
     HAVehicleParameters parameter;  
     if (_forupdate)  
     {  
       parameter.selectForUpdate(_forupdate);  
     }  
     select firstonly parameter  
       index Key  
       where parameter.Key == 0;  
     if (!parameter && !parameter.isTmp())  
     {  
       Company::createParameter(parameter);  
       PriceDiscSalesPolicyParameters::initParameters();  
     }  
     return parameter;  
   } 
- Los métodos delete y update deben sobreescribirse. - Crear en la tabla de parámetros los métodos NumberSeqModule y numRefHAVehicleId.
 static NumberSeqModule numberSeqModule()  
   {  
     return NumberSeqModule::HAVehicle;  
   }
 public server static NumberSequenceReference numRefHAVehicleId()  
   {  
     NumberSeqScope scope = NumberSeqScopeFactory::createDataAreaScope(curExt());  
     return NumberSeqReference::findReference(extendedTypeNum(HAVehicleId),scope);  
   }
- Adicionalmente, crear el indice con el campo Key y;
- Crear la relación de tipo Foreign Key con las siguientes propiedades:

- Crear una nueva clase para los números de secuencias. En nuestro caso es HANumberSeqModuleVehicle. Esta clase debe extender de la clase NumberSeqApplicationModule.

- Los métodos initializeReference y loadModule deben sobreescribirse. En el método loadModule agregue el código para la referencia a la secuencia numérica.
 protected void loadModule()  
   {  
     NumberSeqDatatype datatype = NumberSeqDatatype::construct();  
     datatype.parmDatatypeId(extendedTypeNum(HAVehicleId));  
     datatype.parmConfigurationKeyId(configurationKeyNum(ledgerBasic));  
     datatype.parmReferenceHelp(literalStr("Código único de vehículo"));  
     datatype.parmWizardIsContinuous(false);  
     datatype.parmWizardIsManual(NoYes::No);  
     datatype.parmWizardFetchAheadQty(10);  
     datatype.parmWizardIsChangeDownAllowed(NoYes::No);  
     datatype.parmWizardIsChangeUpAllowed(NoYes::No);  
     datatype.parmWizardHighest(999999999);  
     datatype.parmSortField(1);  
     datatype.addParameterType(NumberSeqParameterType::DataArea, true, false);  
     this.create(datatype);  
     //super();  
   }
- El método buildModulesMapSubsciber es un método controlador de eventos. Abra el diseño de la clase NumberSeqGlobal. De clic derecho sobre el delegado buildModulesMapDelegate y elija "Copiar método controlador de eventos".
- Pegue el metodo en la clase HANumberSeqModuleVehicle y luego sobreescribirlo.
 [SubscribesTo(classstr(NumberSeqGlobal),delegatestr(NumberSeqGlobal,buildModulesMapDelegate))]  
   public static void buildModulesMapSubsciber(Map numberSeqModuleNamesMap)  
   {  
     NumberSeqGlobal::addModuleToMap(classnum(HANumberSeqModuleVehicle), numberSeqModuleNamesMap);  
   }  
- Adicionalmente, crear el método numberSeqModule.
 public NumberSeqModule numberSeqModule()  
   {  
     return NumberSeqModule::HAVehicle;  
   }  
- Crear un formulario para presentar la tabla de parametros. En nuestro caso HAVehicleParameters.
- Arrastre la tabla HAVehicleParameters al DataSource en la parte superior izquierda del panel del diseñador de formas.Cambie las siguientes propiedades:
- Arrastre la tabla NumberSequenceReference al DataSource en la parte superior izquierda panel del diseñador de formas.Cambie las siguientes propiedades:
- Para aplicar el patrón de formulario principal, haga clic derecho en el nodo Diseño y elija Aplicar patrón: Tabla de Contenidos.
- Cuando especificamos el patrón principal del diseño del formulario, nos guiamos en cuanto a los controles que debemos agregar y dónde. Esto ayuda a garantizar que el formulario que diseñamos sigue un diseño de interfaz de usuario con las mejores prácticas.
- Haga clic en el nodo Métodos y elija Sobreescribir el método init(). Antes de la llamada a super(), ingrese la siguiente línea de código:
 public void init()  
 {  
     super();  
     HAVehicleParameters::find();  
 }  
- Crear un nuevo método numberSeqPreInit() en el nodo Métodos, el cual debe contener el siguiente código:
 void numberSeqPreInit()  
 {  
     runExecuteDirect = false;  
     numberSequenceModules = [NumberSeqModule::HAVehicle];  
     numberSeqApplicationModule = new HANumberSeqModuleVehicle();  
     scope = NumberSeqScopeFactory::createDataAreaScope();  
     NumberSeqApplicationModule::createReferencesMulti(numberSequenceModules, scope);  
     tmpIdRef.setTmpData(NumberSequenceReference::configurationKeyTableMulti(numberSequenceModules));  
 }  
- En el nodo Métodos del DataSource NumberSequenceReference, sobreescribir el método removeFilter() e ingrese el siguiento código:
 public void removeFilter()  
 {  
       runExecuteDirect = false;  
       numbersequenceReference_ds.executeQuery();  
       //super();  
 }  
- Finalmente, sobreescribir el método executeQuery() e ingresar el siguiente código:
 public void executeQuery()  
 {  
       if (runExecuteDirect)  
       {  
         super();  
       }  
       else  
       {  
         runExecuteDirect = true;  
         this.queryRun(NumberSeqReference::buildQueryRunMulti(numberSequenceReference,  
                                    tmpIdRef,  
                                    numberSequenceTable,  
                                    numberSequenceModules,  
                                    scope));  
         numbersequenceReference_ds.research();  
       }  
       //super();  
 }  
- La presentación integrada del código en el formulario:
 [Form]  
 public class HAVehicleParameters extends FormRun  
 {  
   boolean runExecuteDirect;  
   container numberSequenceModules;  
   NumberSeqApplicationModule numberSeqApplicationModule;  
   NumberSeqScope scope;  
   TmpIdRef tmpIdRef;  
   /// <summary>  
   ///  
   /// </summary>  
   public void init()  
   {  
     this.numberSeqPreInit();  
     super();  
     HAVehicleParameters::find();  
     //this.numberSeqPostInit();  
   }  
   void numberSeqPostInit()  
   {  
     numberSequenceReference_ds.object(fieldNum(NumberSequenceReference, AllowSameAs)).visible(numberSeqApplicationModule.sameAsActive());  
     referenceSameAsLabel.visible(numberSeqApplicationModule.sameAsActive());  
   }  
   void numberSeqPreInit()  
   {  
     runExecuteDirect = false;  
     numberSequenceModules = [NumberSeqModule::HAVehicle];  
     numberSeqApplicationModule = new HANumberSeqModuleVehicle();  
     scope = NumberSeqScopeFactory::createDataAreaScope();  
     NumberSeqApplicationModule::createReferencesMulti(numberSequenceModules, scope);  
     tmpIdRef.setTmpData(NumberSequenceReference::configurationKeyTableMulti(numberSequenceModules));  
   }  
   [DataSource]  
   class NumberSequenceReference  
   {  
     public void removeFilter()  
     {  
       runExecuteDirect = false;  
       numbersequenceReference_ds.executeQuery();  
       //super();  
     }  
     public void executeQuery()  
     {  
       if (runExecuteDirect)  
       {  
         super();  
       }  
       else  
       {  
         runExecuteDirect = true;  
         this.queryRun(NumberSeqReference::buildQueryRunMulti(numberSequenceReference,  
                                    tmpIdRef,  
                                    numberSequenceTable,  
                                    numberSequenceModules,  
                                    scope));  
         numbersequenceReference_ds.research();  
       }  
       //super();  
     }  
   }  
 }  
- Crear un nuevo MenuItem tipo Display. Dele un nombre y una etiqueta, para nuestro caso HAVehicleParameters. En sus propiedades coloque el formulario de parámetros.
- Crear un nuevo Menú (HAVehicleTable), y dentro un Submenú (HASetUp) donde se coloque el MenuItem HAVehicleParameters.
- De clic derecho sobre el Menú MainMenu y elija crear extensión. Cambie el nombre de la nueva extensión a MainMenu.HAExtension. Coloque dentro el nuevo menú HAVehicleTable.
- Compile e implemente el proyecto.
- Explore el nuevo módulo que se presenta en la sección Módulos de D365FO.
- En la ruta Configurar / Parámetros de vehículo se encuentra la parametrización de Secuencias numéricas.