Tabla de Contenidos

Ejemplo de desarrollo de un plugin

Supongamos que se desean redefinir o implementar un conjunto de cambios funcionales que comprenden un nuevo plugin denominado disytel. Dicho plugin comprende ampliaciones funcionales en lógica de negocios, lógica de documentos, callouts, procesos e informes. Se detallará en cada caso la metodología a respetar a fin de construir correctamente las clases correspondientes.

Configuración en Libertya para iniciar el desarrollo

Primeramente será necesario registrar el nuevo componente a desarrollar en la ventana Componentes, bajo el perfil System Administrator. En dicha ventana deberán especificarse los siguientes datos (para más detalle, ver Datos del componente y versión):

Una vez especificados estos valores, será necesario crear la primera versión de este plugin, accediendo a la segunda pestaña de la ventana, y especificando el número de versión de la misma. Por ejemplo: 1.01.

A fin de testear el desarrollo del plugin, se simula que el mismo se encuentra instalado, a fin de poder invocar a los métodos de las clases implementadas. Para ésto deberemos cargar manualmente una entrada en la ventana de Plugins, también desde System Administrator, relacionando esta entrada con la versión de componente cargada previamente (siguiendo el ejemplo: DISY 1.01). Tener en cuenta que esta ventana es de solo lectura debido a que no es correcto para un usuario final ingresar tuplas manualmente desde la ventana (se crean cuando se hace la instalación de un plugin). Es por esto que es necesario editar los metadatos de la ventana Plugins, y marcar la única pestaña que contiene como editable (quitar la marca de Solo Lectura). Vale la aclaración de que esta modificación es solo para desarrollo y no debería aparecer en una versión de producción.

Configuración en Eclipse para iniciar el desarrollo

Una vez realizada la configuración básica en LY para el desarrollo del plugin, será necesario crear un proyecto en Eclipse que nos permita crear las clases correspondientes; referenciando además el proyecto Libertya. De esta manera, los fuentes del plugin se encuentran completamente separados de los fuentes de Libertya, lo cual presenta varias ventajas: acceso sencillo e intuitivo a los componentes de ambos proyectos, versionado por separado, etc.

Desde el proyecto del plugin se referencia al proyecto LY, desde las propiedades del primero, indicando al segundo en la pestaña Projects del Java Build Path. En el ejemplo denominaremos al proyecto: disytel_plugin.

A fin de que el framework de plugins instancie correctamente las clases que desarrollamos, la estructura de packages del proyecto deberá ser la siguiente:

En cada package se almacenarán las clases correspondientes según las modificaciones a realizar (callout, proceso, etc.). Es IMPORTANTE respetar esta convención ya que en caso contrario el framework no encontrará las clases a instanciar.

A fin de iniciar la aplicación, en la debug configuration de nuestro proyecto de plugin, deberemos especificar la main class: org.openXpertya.OpenXpertya (la cual se encuentra en el proyecto referenciado).

Implementación de clases del package ar.com.disytel.plugin.model

Este package comprende los componentes que redefinen las clases que extienden de org.openXpertya.model.PO y/o que implementan org.openXpertya.process.DocAction

Redefinición de métodos de persistencia (beforeSave(), afterSave(), beforeDelete(), afterDelete())

Estas clases deberán extender de MPluginPO (para más detalles, ver plugins en persistencia de datos).

Si por ejemplo queremos redefinir o ampliar la funcionalidad de la clase org.openXpertya.model.MInvoice, deberemos crear la clase ar.com.disytel.plugin.model.MInvoice, en conjunto con los métodos pre y post para cada evento (beforeSave, afterSave, beforeDelte, afterDelete). Esto lleva a la implementación de varios métodos como preBeforeSave, postBeforeSave, preAfterSave, postAfterSave, etc.; pero únicamente los métodos de interés deberán definirse. No es necesario implementación alguna en caso de no requerir modificaciones, ya que la superclase MPluginPO se encarga de continuar con la ejecución correspondiente.

El framework buscará los plugins activos. Específicamente para este caso, chequeará si existe una clase MInvoice para el package definido en el plugin (ar.com.disytel.plugin), más model. De ser así, y suponiendo que se ha disparado el método beforeSave(), el framework verificará si se encuentra definido alguno de los métodos preBeforeSave() y postBeforeSave().

La clase MPluginPO ya cuenta con una instancia de MPluginStatusPO a fin de devolver el estado de la ejecución del método, la cual puede ser accedida por sus subclases. El método recibe además la instancia de PO a almacenar/borrar, con la que es posible validar la información que se está modificando, como por ejemplo:

public MPluginStatusPO preBeforeSave(PO po, boolean newRecord)

En el siguiente ejemplo, la implementación del preBeforeSave() del plugin de ar.com.disytel.plugin.model.MInvoice verifica si existe una C_Order, antes de guardar los cambios. En caso de que la misma no se encuentre cargada, devolverá el error correspondiente, cancelando las siguientes ejecuciones de los métodos beforeSave() y postBeforeSave().

package ar.com.disytel.plugin.model;
 
import java.util.Properties;
import org.openXpertya.model.PO;
import org.openXpertya.plugin.MPluginPO;
import org.openXpertya.plugin.MPluginStatusPO;
 
public class MInvoice extends MPluginPO {
 
	public MInvoice(PO po, Properties ctx, String trxName, String aPackage) {
		super(po, ctx, trxName, aPackage);
	}
 
	public MPluginStatusPO preBeforeSave(PO po, boolean newRecord) {
		org.openXpertya.model.MInvoice invoice = (org.openXpertya.model.MInvoice)po;
		if (invoice.getC_Order_ID() == 0)
		{
			status_po.setContinueStatus(MPluginStatusPO.STATE_FALSE);
			status_po.setErrorMessage("Numero de pedido obligatorio");
		}
		return status_po;
	}
 
}

El normal flujo de ejecución - si el mismo no se detiene en alguna validación - abarcará la invocaciones a:

Redefinición de métodos de lógica de documentos (prepareIt(), completeIt(), voidIt(), closeIt(), etc.)

Estas clases deberán extender de MPluginDocAction (para más detalles, ver plugins en logica de documentos). Es normal que una clase inicialmente extienda de MPluginPO para redefinición de persistencia de datos, y posteriormente se amplie la lógica de la misma para gestión de documentos, extendiendo de MPluginDocAction.

Una vez creada la clase, o modificada la superclase de la misma, es posible redefinir los métodos de interés para lógica de documentos; respetando los prefijos pre y post según corresponda; a fin de codificar los métodos prePrepareIt(), postPrepareIt(), preVoidIt(), preVoidIt, etc. Al igual que en el caso anterior, sólo es necesario implementar los métodos de interés; ya que la superclase se encarga de continuar con la ejecución correspondiente.

Nuevamente, el framework buscará los plugins activos. Específicamente para este caso, chequeará si existe una clase MInvoice para el package definido en el plugin (ar.com.disytel.plugin), más model. De ser así, y suponiendo que se ha disparado el método prepareIt(), el framework verificará si se encuentra definido alguno de los métodos prePrepareIt() y postPrepareIt().

La clase MPluginDocAction ya cuenta con una instancia de MPluginStatusDocAction a fin de devolver el estado de la ejecución del método, la cual puede ser accedida por sus subclases. Cada método recibe además la instancia de DocAction a procesar, con la que es posible validar la información que se está gestionando, como por ejemplo:

public MPluginStatusDocAction prePrepareIt(DocAction document)

En el siguiente ejemplo, ampliamos la clase anteriormente creada, extendiendo ahora de MPluginDocAction, e implementando el método prePrepareIt(). Dicho método verifica que la fecha de facturación y la fecha contable sean iguales. En caso de que éstas difieran, devolverá el error correspondiente, cancelando las siguientes ejecuciones de los métodos prepareIt() y postPrepareIt().

package ar.com.disytel.plugin.model;
 
import java.util.Properties;
import org.openXpertya.model.PO;
import org.openXpertya.plugin.MPluginDocAction;
import org.openXpertya.plugin.MPluginStatusDocAction;
import org.openXpertya.plugin.MPluginStatusPO;
import org.openXpertya.process.DocAction;
 
public class MInvoice extends MPluginDocAction {
 
 
	public MInvoice(PO po, Properties ctx, String trxName, String aPackage) {
		super(po, ctx, trxName, aPackage);
		// TODO Auto-generated constructor stub
	}
 
	public MPluginStatusPO preBeforeSave(PO po, boolean newRecord) {
		org.openXpertya.model.MInvoice invoice = (org.openXpertya.model.MInvoice)po;
		if (invoice.getC_Order_ID() == 0)
		{
			status_po.setContinueStatus(MPluginStatusPO.STATE_FALSE);
			status_po.setErrorMessage("Numero de pedido obligatorio");
		}
		return status_po;
	}
 
	public MPluginStatusDocAction prePrepareIt(DocAction document) {
		org.openXpertya.model.MInvoice invoice = (org.openXpertya.model.MInvoice)document;
		if (invoice.getDateInvoiced().compareTo(invoice.getDateAcct()) != 0)
		{
			status_docAction.setContinueStatus(MPluginStatusDocAction.STATE_FALSE);
			status_docAction.setDocStatus(DocAction.STATUS_Invalid);
			status_docAction.setProcessMsg("La fecha contable difiere de la fecha de facturación");
		}
		return status_docAction;
	}
}

Notar que la modificación de la superclase no presenta problema alguno: Esta es la misma clase del ejemplo anterior (el cual implementaba el método preBeforeSave()), ampliada no solo para persistencia sino también para lógica de documentos.

Modificacion de las opciones de acciones de documento

Es posible incorporar o eliminar acciones sobre la lista de posibles acciones sobre un documento en particular. Simplemente debe implementarse la clase correspondiente al documento (ya sea extendiendo de MPluginPO o MPluginDocAction) e implementando la interface DocOptions.

Dicha interface tiene simplemente un método llamando customizeValidActions() que recibe la información relevante del documento, las opciones actuales, etc. Dicho método debe retornar el indice actual que referencia al total de opciones válidas.

Se cuenta además con la clase DocOptionsUtils, la cual simplifica la tarea de incorporar o eliminar acciones.

NOTA: Esta funcionalidad también es válida para una clase que extienda de PO (sin utilizar lógica de plugins), siempre y cuando implemente DocOptions y corresponda con el documento que se quiere procesar.

Ejemplo: se requiere modificar las opciones de acción para un Payment. La clase tiene que ser MPayment y debe implementar DocOptions:

package org.libertya.ejemplo.model
 
import java.util.Properties;
 
import org.openXpertya.model.PO;
import org.openXpertya.plugin.MPluginDocAction;
import org.openXpertya.process.DocAction;
import org.openXpertya.process.DocOptions;
import org.openXpertya.util.DocOptionsUtils;
 
public class MPayment extends MPluginDocAction implements DocOptions {
 
	public MPayment(PO po, Properties ctx, String trxName, String aPackage) {
		super(po, ctx, trxName, aPackage);
		// TODO Auto-generated constructor stub
	}
 
	@Override
	public int customizeValidActions(String docStatus, Object processing,
			String orderType, String isSOTrx, int AD_Table_ID,
			String[] docAction, String[] options, int index) {
 
		// Si se esta completando el documento, incorporar la opción de WaitComplete
		if (DocAction.ACTION_Complete.equals(docAction[0])) {
			index = DocOptionsUtils.addAction(options, DocAction.ACTION_WaitComplete, index);	
		} else {
			// En caso contrario permitir invalidar el documento
			index = DocOptionsUtils.addAction(options, DocAction.ACTION_Invalidate, index);
		}
		// En todos los casos, quitar la accion ReActivate
		index = DocOptionsUtils.removeAction(options, DocAction.ACTION_ReActivate, index);
		return index;
	}
}

Implementación de clases del package ar.com.disytel.plugin.callout

Este package comprende los componentes que redefinen las clases CalloutXXX que extienden de org.openXpertya.model.CalloutEngine.

Estas clases deberán extender de CalloutPluginEngine, y deberán respetar una serie de convenciones a fin de que el framework pueda determinar la clase y métodos a ejecutar (para más detalles, ver plugins en callouts).

Básicamente, la asociación para determinar el método de la clase a ejecutar es: Tabla→Clase, Columna→Método. Por ejemplo: si se desea implementar el Callout para el campo M_Product_ID de la tabla C_InvoiceLine, se deberá crear la clase ar.com.disytel.plugin.callout.CalloutInvoiceLine, y se deberá implementar alguno de los métodos: preM_Product_ID() o postM_Product_ID(). Aquí tampoco es necesario implementar todos los métodos, sólo los de interés.

El framework buscará existencias de plugins que implementen callouts para la tabla y columna dada, e insertará dichas invocaciones (tanto pre como post) a los métodos ya definidos en el campo Callout de la tabla AD_Column. Siguiendo el ejemplo anterior, y suponiendo que en AD_Column se encuentra definida la invocación a org.openXpertya.model.CalloutInvoice.product para la columna M_Product_ID de la tabla C_InvoiceLine; la colección de invocaciones sería la siguiente (la cual se ejecutará por completo o no según las validaciones en tiempo de ejecución):

De no existir un callout definido para un campo específico, se respetará el uso común de callouts, definiendo directamente el valor (package.class.method) correspondiente en los metadatos.

En el siguiente ejemplo para el campo M_Product_ID de la tabla C_InvoiceLine, si se verifica que el producto seleccionado comienza con “XYZ”, se saltea todo tipo de validación posterior (STATE_TRUE_AND_SKIP). Si por el contrario el producto seleccionado comienza con “ABC”, se informa que esto no es posible de realizar para estos productos (STATE_FALSE). Por último, si no suceden ninguna de las otras dos cosas, la ejecución continuará normalmente (STATE_TRUE_AND_CONTINUE) hacia el método product() de la clase org.openXpertya.model.CalloutInvoice

package ar.com.disytel.plugin.callout;
 
import java.util.Properties;
import org.openXpertya.model.MField;
import org.openXpertya.model.MProduct;
import org.openXpertya.model.MTab;
import org.openXpertya.plugin.CalloutPluginEngine;
import org.openXpertya.plugin.MPluginStatus;
import org.openXpertya.plugin.MPluginStatusCallout;
 
public class CalloutInvoiceLine extends CalloutPluginEngine {
 
	public MPluginStatusCallout preM_Product_ID( Properties ctx,int WindowNo,MTab mTab,MField mField,Object value ) {
		int productID = (Integer)value;
		MProduct product = new MProduct(ctx, productID, null);
 
		if (product.getName().startsWith("XYZ"))
			state.setContinueStatus(MPluginStatusCallout.STATE_TRUE_AND_SKIP);
 
		if (product.getName().startsWith("ABC"))
		{
			state.setContinueStatus(MPluginStatusCallout.STATE_FALSE);
			state.setErrorMessage("No usar productos que inician con ABC");
		}
 
		return state;
	}

Implementación de clases del package ar.com.disytel.plugin.process

Este package comprende los componentes que redefinen las clases de procesos que extienden de org.openXpertya.process.SvrProcess.

Estas clases deberán podrán extender tanto de SvrProcess, como de la misma clase a redefinir, según sea conveniente en cada caso. La unica convención a respetar es que si se está redefiniendo un proceso (por ejemplo org.openXpertya.process.CacheReset), el nombre de la clase deberá ser el mismo (por ejemplo ar.com.disytel.plugin.process.CacheReset). Para más detalles, ver plugins en procesos).

Debido a las características de los procesos, y a diferencia de los casos anteriores (PO, DocAction, Callout), los plugins deben redefinir completamente un proceso (métodos prepare() y doIt()), ya que no se presenta lógica de ContinueStatus. Esto significa un override completo del proceso. De todas maneras, se puede por ejemplo reutilizar el prepare() del proceso original, subclasificando y redifiniendo únicamente el método doIt() en el plugin.

En el siguiente ejemplo, redefinimos la clase CacheReset original, creando una nueva clase con igual nombre y extendiendo de la primera. La implementación del doIt() simplemente invoca a la limpieza total de la cache (con true en lugar de false como lo hace la superclase). Se delega a la superclase la implementación del prepare().

package ar.com.disytel.plugin.process;
 
import org.openXpertya.util.Env;
 
public class CacheReset extends org.openXpertya.process.CacheReset {
 
	@Override
	protected String doIt() throws Exception 
	{
		Env.reset(true);
		return "OK";
	}
 
}

Implementación de clases del package ar.com.disytel.plugin.info

Este package comprende los componentes que redefinen las ventanas Info que extienden de org.openXpertya.apps.search.Info.

Según el origen de información a mostrar, estas clases deberán deberán extender de una superclase a partir de la siguiente convención:

Al igual que en los procesos, la redefinición de una ventana implica su completo override. No tiene sentido alguno una lógica de ContinueStatus. Para más detalles, ver plugins en ventanas info).

El siguiente ejemplo presenta - parcialmente - una clase que redefine la clase InfoBPartner, en conjunto con su constructor.

package ar.com.disytel.plugin.info;
 
public final class InfoBPartner extends Info 
{
 
    public InfoBPartner( Frame frame,boolean modal,int WindowNo,String value,boolean isSOTrx,boolean multiSelection,String whereClause ) 
    {
    ...
    }
 
}

El siguiente ejemplo presenta - parcialmente - una clase que define la clase InfoWindow, en conjunto con su constructor. Notar la diferencia en los parámetros del constructor con respecto al ejemplo anterior.

package ar.com.disytel.plugin.info;
 
public class InfoWindow extends InfoGeneralPlugin 
{
 
    public InfoWindow(Frame frame, boolean modal, int WindowNo, String value, boolean multiSelection, String whereClause) {
    {
    ...
    }
 
}

Implementación de clases del package ar.com.disytel.plugin.client

En este package se implementan las clases que están relacionadas con el cliente de Libertya.

Crear Desde (VCreateFrom)

Es posible agregar nuevas ventanas Crear Desde para cualquier entidad del sistema, o incluso extender o modificar la funcionalidad de las ventanas Crear Desde actualmente implementadas. Todas las ventanas Crear Desde son clases cuya superclase es la clase VCreateFrom, ubicada en el paquete org.openXpertya.grid.

Las ventanas Crear Desde existentes en el sistema están implementadas por las siguientes clases:

Si se quiere implementar un Crear Desde para una nueva entidad simplemente se debe crear una clase que extienda la funcionalidad de VCreateFrom. La clase en cuestión debe respetar una convención de nombre. A continuación se muestra una tabla con ejemplos de nombres de clase Crear Desde para nuevas entidades

Entidad Tabla Nombre Clase
Pedido C_Order VCreateFromOrder
Pedido de Reparación C_Order_Repair VCreateFromOrderRepair
Remitos M_InOut VCreateFromInOut

Es decir, el nombre debe comenzar con VCreateFrom junto con el nombre de la tabla a la cual está destinado, sin el prefijo de tabla (C_, M_, etc), y sin los guiones bajos que contenga el nombre de la misma.

La nueva clase deberá implementar los métodos abstractos definidos en VCreateFrom, y a su vez podrá implementar algunos métodos protegidos que permiten ampliar la funcionalidad de la ventana. Para obtener mas detalles en la implementación de estos métodos se pueden estudiar alguna de las clases actualmente implementadas (mencionadas arriba).

En caso de simplemente querer ampliar o modificar la lógica de una de las clases existentes, también debemos crear una nueva clase en el paquete respetando las convenciones de nombres previamente indicadas. La diferencia con el caso de implementar un Crear Desde nuevo (no existente), es que esta clase podría extender de la clase existente en el sistema. Supongamos que queremos extender la funcionalidad del Crear Desde para remitos, deberíamos crear entonces la clase VCreateFromInOut que extiende de la clase VCreateFromShipment existente (en el Core no se respeta la convención de nombres mencionada, pero para los plugins es necesario si o sí para que el framework de plugins puede encontrar las clases implementadas).

Luego es necesario implementar los métodos requeridos para extender la funcionalidad del Crear Desde del Core. Siguiendo con el ejemplo, a continuación se muestra una ampliación al Crear Desde de remitos, que realiza algunos cambios en la grilla que muestra las líneas de un pedido seleccionado:

public class VCreateFromInOut extends VCreateFromShipment {
 
    public VCreateFromInOut(MTab mTab) {
        super(mTab);
    }
 
	@Override
	protected boolean dynInit() throws Exception {
		super.dynInit();
		// Modificaciones para remitos de salida
		if (isSOTrx()) {
			// Por defecto se seleccionan todas las líneas del pedido
			// y no se permite cambiar la selección. Se oculta el SelectAll.
			automatico.setSelected(true);
			automatico.setVisible(false);
			// Se oculta el botón del VLookup de pedidos y se le indica
			// que no cargue el info al dar Enter. (esto es necesario para
			// que el operador no pueda ver otros pedidos).
			orderField.getM_button().setVisible(false);
			orderField.setShowInfo(false);
		}
		return true;
	}
 
	@Override
	protected void orderChanged(int orderID) {
		super.orderChanged(orderID);
		// Por defecto selecciona todas las líneas (y no se puede modificar la selección)
		selectall();
	}
 
	@Override
	protected CreateFromTableModel createTableModelInstance() {
		CreateFromTableModel tableModel = super.createTableModelInstance();
		// No se permite la edición de la columna de selección debido a que
		// el remito de salida se debe crear con todas las líneas del pedido. 
		if (isSOTrx()) {
			tableModel.setColumnEditable(CreateFromTableModel.COL_IDX_SELECTION, false);
		}
		return tableModel;
	}
 
}

Es posible incorporar opciones adicionales al menú contextual que se presenta al realizar click derecho sobre el campo Entidad Comercial. Para ésto, es necesario realizar las siguientes ampliaciones:

Incorporar nuevo package lookup

Será necesario incorporar este nuevo package a fin de contener las clases correspondientes. Siguiendo el ejemplo, el mismo deberá llamarse: ar.com.disytel.plugin.lookup.

Creación de clase VLookupEntries

Dentro del package lookup, será necesario crear la clase VLookupEntries (respetando la convención de nombres), la cual deberá implementar la interfaz PluginLookupInterface. Esta interfaz define dos métodos:

A modo de ejmplo, se presenta el suguiente snippet de código:

package ar.com.disytel.plugin.lookup;
 
import java.util.Vector;
 
import javax.swing.JMenuItem;
 
import org.openXpertya.grid.ed.VLookup;
import org.openXpertya.plugin.common.PluginLookupInterface;
import org.openXpertya.util.Env;
import org.openXpertya.util.Msg;
 
public class VLookupEntries implements PluginLookupInterface {
 
	protected Vector<JMenuItem> bPartnerItems = new Vector<JMenuItem>();
	JMenuItem bPartnerItem1 = new JMenuItem("Entrada Adicional 1", Env.getImageIcon( "InfoBPartner16.gif" ));
	JMenuItem bPartnerItem2 = new JMenuItem("Entrada Adicional 2", Env.getImageIcon( "InfoBPartner16.gif" ));
 
	public Vector<JMenuItem> getBPartnerLookupEntries()
	{
            bPartnerItems.add(bPartnerItem1);
            bPartnerItems.add(bPartnerItem2);
    	    return bPartnerItems;
	}
 
	@Override
	public void doBPartnerLookupAction(JMenuItem clickedItem, boolean newRecord, VLookup aLookup) 
	{
		if (clickedItem.equals(bPartnerItem1))
                {
                        // acciones item 1
                }
		else if (clickedItem.equals(bPartnerItem2))
                {
                        // acciones item 2
                }
        }
}

Implementación de clases del package ar.com.disytel.plugin.reportProvider

A partir de la revision r2968, en caso de necesitar incorporar nuevos parámetros a un informe Jasper, pero sin necesidad de redefinir la clase por completo, es posible crear una clase que simplemente se encargue de incorporar al Jasper los parametros adicionales específicos del componente.

Es importante que la clase respete los siguientes:

Este método inyecta dos parámetros:

Por ejemplo, suponiendo que es necesario incorporar nuevos parámetros para la impresión Jasper de facturas, la clase en CORE es org.openXpertya.JasperReport.LaunchInvoice, con lo cual sera necesario crear la clase ar.com.disytel.plugin.reportProvider e implementar org.openXpertya.plugin.report.ReportProviderInterface.

package ar.com.disytel.plugin.reportProvider;
import org.openXpertya.JasperReport.MJasperReport;
import org.openXpertya.model.MInvoice;
import org.openXpertya.model.PO;
import org.openXpertya.plugin.report.ReportProviderInterface;
 
public class LaunchInvoice implements ReportProviderInterface {
 
	@Override
	public void addReportParametersToLaunch(MJasperReport report, PO po) {
		// Agregar los parámetros que necesitemos
		report.addParameter("FOO", getFoo(po));
		report.addParameter("BAR", getBar(po));
	}
}