Está en la página 1de 66

CAPÍTULO VIII

Desarrollo de Plug-in

Facilitador: Ing. Jorge Colmenarez


Desarrollo de Plug-in
1. Creando estructura del plugin con idempiere-plugin-scaffold.
Generador de plugins iDempiere:

Este proyecto crea un esqueleto de plugin iDempiere. Versión actual


de iDempiere 9.

Ejemplos:
Ejemplo del Plugin completo en idempiere-plugin-template
Ejemplo del Plugin de iDempiere
idempiere-plugin-template/com.ingeint.template
Ejemplo del Plugin de iDempiere Unit Test
idempiere-plugin-template/com.ingeint.template.test
Ejemplo del Plugin de iDempiere Target Platform
idempiere-plugin-template/com.ingeint.template.targetplatform

Pre-requisitos
Java 11, comandos java y javac
Establecer variable de entorno IDEMPIERE_REPOSITORY

Comandos de uso

Comando Descripción

./plugin-scaffold Crea un nuevo plugin

./plugin-scaffold clean Reinicie la configuración del


scaffold

Comando de uso en Windows .\plugin-scaffold.bat

Documentación del Plugin:

Componentes
El plugin tendrá tres componentes:
● El nuevo plugin de idempiere.
● Fragmento de prueba unitaria iDempiere.
● Plataforma de destino iDempiere.

Y un script para compilarlo. build.sh

Comandos
Compilar plugin y ejecutar pruebas:
./build

Utilice el parámetro para el ejemplo de modo de depuración: debug


./build debug

Uso para Windows.\build.bat

Plataforma de destino de complementos iDempiere:

Una plataforma de destino es necesaria para compilar un


complemento iDempiere. Esta es una plataforma de destino
inteligente. Versión actual de iDempiere 9

El scaffold le proporcionará una plataforma de destino y una forma


de compilarlo en su nuevo complemento.

Para obtener más información, visite


https://github.com/ingeint/idempiere-target-platform-plugin

iDempiere Plugin Deployer:

Esta herramienta le permite conectarse a la plataforma OSGI de


iDempiere e implementar un complemento, es útil para plataformas
de integración continua.

Para obtener más información, visite


https://github.com/ingeint/idempiere-plugin-deployer
2. CallOut.
La finalidad de esta parte es mostrar cómo desarrollar callouts en nuestro
plug-in. Para ello existen diferentes formas de implementar callouts. Aprenderás
las siguientes maneras:
● Crear un Callout usando IColumnCallout interface y extensión de puntos.
● Crear un Callout usando Java puro y el Diccionario de Aplicación.
● Crear un Callout usando IColumnCalloutFactory y una definición de
componente.

Flujo de Trabajo

Usando una Extensión


Desarrollar un Callout usando una extensión es una de las formas
más fáciles pero es un poco anticuado. Esto también puede causar
conflictos en versiones futuras de Idempiere, así que siempre es una buena
idea usar el enfoque factory/component si es posible. En primer lugar crea
tu plug-in siguiendo los prerequisitos de este tutorial. Después, crea un
nuevo paquete y una clase. Nosotros llamamos a nuestra clase
IColumnCalloutCallout en este caso. Implementa la clase de interfaz
IColumnCallout y agrega alguno registro para el método start():
Abre el MANIFEST.MF y ve a la pestaña extensión. Agrega la extensión
org.adempiere.base.IColumnCallout. Ahora haz click en agregar un nuevo
callout. Luego click en el botón navegador (browser) y selecciona la clase
creada anteriormente. Como nombre de la tabla (tableName) nosotros
elegimos "M_Product" y como nombre de columna (columnName) "Help".
Como prioridad (priority) escogimos 1:
Copia el nombre de la clase completo y selecciona de nuevo
org.adempiere.base.IColumnCallout. Pega y agrega ".help". Con esto se
puede definir mejor entre diferentes callouts si están dentro de la misma
clase.
Básicamente esto es todo. Inicia un cliente y abre la ventana Producto.
Introduzca o cambie algo en el campo de ayuda (help) y deje el campo.
Observe los registros en su consola.
Puedes usar una clase para diferentes Tablas/Columnas. Si deseas
hacer esto, sólo agrega otra extensión pero con diferente nombre de tabla y
columna (tableName/columnName). En el método start() de la clase
Callout. Comprueba con mTab.getTableName() y mField.getColumnName()
para decidir cual método callout quieres llamar. CalloutInfoWindow.java es
una buena referencia para ver como se puede hacer.

Usando Java
Esta forma es algo anticuado y más incómodo que los otros
enfoques, pero por motivos de exhaustividad, se les mostrará. En primer
lugar se crea otra clase llamada JavaCallout, la cual hereda de
"CalloutEngine" y se crea un nuevo método siguiendo la siguiente
estructura:

public String myMethodName(Properties ctx, int WindowNo, GridTab


mTab, GridField mField, Object value)

Se agrega algún registro para mostrar en consola y al final retornar en nulo


(null). Si la sentencia de retorno no es nulo, el CalloutEngine lo maneja como
un error:
Abrir el MANIFEST.MF y seleccionar la pestaña Runtime. Se agrega el
paquete donde tu clase JavaCallout esté localizada.

Hasta ahora no hay problema. Debido a que no se utiliza una extensión o un


componente, ¿cómo debería el CalloutEngine localizar nuestra clase? Bueno
para ello, cambie a la pestaña MANIFEST.MF y añada la siguiente línea:
"Eclipse-Registerbuddy: org.adempiere.base". Por Favor tome en cuenta que
esto no es una de las mejores prácticas! Guarde el Manifest e inicie el
cliente. Ingrese como SuperUser/System y abra la ventana Tabla y Columna
(Table and Column). Busque la tabla M_Product y cambie a la pestaña
Columna. Encuentre la columna Descripción e introduzca el nombre de la
clase seguida por un punto y el nombre del método en el campo Callout.
Restaure el cliente y asegúrese que el plug-in está activado. Ingrese y
abra la ventana Producto. Ingrese o cambie algo en el campo descripción y
cambie de campo. Ahora observe los registros en consola, debe ver algo
como esto:
Usando CalloutFactory

Este debe ser el enfoque preferido. Se crean 2 clases. Una para el


Factory y una para el callout actual. Nosotros la llamamos CalloutFactory y
CalloutFromFactory. Copie el código de la IColumnCalloutcallout al
CalloutFromFactory o observa cómo hacerlo. Se implementa
IColumnCallout interface y tiene sólo un registro en su método start().
Ahora, la parte interesante. En la clase CalloutFractory implemente
IColumnCalloutFactory interface y es necesario el método
getColumnCallouts(). Aquí se comprueba el nombre de la columna/ nombre
de tabla (columnname/tablename ) y decide, si desea proporcionar un
callout. Se comprueba para MProduct.Table_Name y
MProduct.COLUMNNAME_DocumentNote:
El siguiente paso es agregar una definición de componente(component
definition) en el plug-in. Click en New > New > Others y de la lista
seleccionar Component Definition.
Haga clic en siguiente y escoja el nombre. Asegúrese de que el nombre sea
único. También, haga clic en Examinar(browser) y seleccione la clase que
CalloutFactory:
Abra el component.xml (en nuestro caso calloutfactory.xml) y cambie a la
pestaña servicio (service tab). Añada
org.adempiere.base.IColumnCalloutFactory a la sección de Servicios
(Provides Services):
Compruebe la pestaña MANIFEST.MF de su archivo de manifiesto (manifest
file) y busque la línea que comienza con "Service-componente: ..." y
compruebe la ruta correcta para su xml e insertela aquí:
Inicie el cliente e inicie sesión. Abra la ventana del producto e introduzca o
cambie algo en el campo Nota de documentos. Deje el campo y comprobé
su registro en la consola. Debería ver algo como esto:
3. Sobrescribir clase modelo.

Su objetivo de este tutorial es mostrar cómo se utiliza el servicio


IModelFactory en el proyecto de complemento. La razón por la que desea
utilizar el servicio de fábrica de modelos es que puede proporcionar su
propio modelo para tablas personalizadas e iDempiere hará uso de ellas.
Además, si desea utilizar la clase Query, es necesario proporcionar sus
propios modelos. De lo contrario, una consulta devolverá un “GenericPO”
en lugar de la clase de modelo.

El flujo de trabajo
X_, I_, M? ¿Qué clase es para qué?
En iDempiere tenemos diferentes clases para nuestros modelos. La clase
toplevel se llama PO, que significa Objeto persistente. Cada modelo hereda
sus métodos. Si generas modelos o echas un vistazo a los existentes, verás
que la mayoría de las tablas tienen una X_ y I_ clase.

La clase I_ es una interfaz que tiene información sobre la tabla y sus


columnas. Tiene una constante para el nombre de la tabla, su ID en el
Diccionario de aplicaciones (que se carga dinámicamente a través de
MTable.getTable_ID()) y cada columna de la tabla. También tiene la plantilla
para todos los métodos getter y setter.

La clase X_ es el modelo básico de cada tabla. Aquí se extiende PO y se


implementa la clase I_. La clase X_ implementa todos los getters y es la
primera clase que podría aplicar lógica de negocios personalizada (pero no
ponga su lógica de negocios aquí, aprenderá por qué en un segundo). Si
echa un vistazo al código, puede ver que los captadores y configuradores
que se implementan aquí a menudo hacen algún tipo de validación de los
datos y utilizan los métodos de OP para persistir los datos.

La última clase es la clase M. Su esquema de nomenclatura es "M" +


"prefijo de tabla (si >=3 caracteres)" + "nombre de tabla sin espacios ni
guiones bajos". Entonces, si mi tabla se llama eve_this_is_my_table, la
crearía en el Diccionario de aplicaciones como "EVE_This_Is_My_Table"
(observe la escritura del caso del camello). Mi modelo se llamaría
"MEVEThisIsMyTable". Siéntase libre de no usar el prefijo de la tabla, pero
me gusta porque facilita la detección de modelos personalizados. Entonces,
¿cuál es el uso de esta clase M que no es implementada automáticamente
por el generador de modelos? Eso es fácil de explicar. Toda su lógica de
negocio personalizada entra en esta clase. ¿Por qué no usar la clase X_?
Debido a que su tabla puede cambiar con el tiempo y cada vez,
simplemente puede ejecutar el generador de modelos para recrear sus
clases de I_ y X_, pero perdería toda su lógica de negocio personalizada si
estuviera en la clase X. Por lo tanto, al crear una clase M que extiende la
clase X_, puede regenerar sus modelos sin perder ningún código o
funcionalidad.

¿Qué puedes hacer en la clase M? Algunos ejemplos son:

Sobrescribir los ganchos del modelo beforeSave(), afterSave(),


beforeDelete(), afterDelete()
Implemente la interfaz DocAction para que su tabla actúe como un
documento
Crear getters y setters especiales (como MOrder.getLines())

IModelFactory
Nota : si utiliza Eclipse Oxigen o una versión más reciente, puede
utilizar anotaciones en lugar de crear la definición de componente
manualmente.

Después de crear el complemento y establecer sus dependencias, haga clic


en Archivo>Nuevo>Otros... y seleccione "Definición de componentes" en la
sección Desarrollo de complementos.
Haga clic en siguiente. Seleccione el proyecto de plug-in y elija un nombre
de archivo y un nombre general. A menudo usamos
"defaultmodelfactory.xml" y "my.domain.model.factory" para el nombre /
nombre del archivo, pero todos los demás nombres también están bien.
Todavía no es necesario establecer el nombre de la clase porque no hemos
implementado uno. Haga clic en finalizar.

Lo siguiente que necesitaremos es:

● Un modelo
● Un ModelFactory (que es solo una clase que implementa
IModelFactory)

Observe que la fábrica de modelos y la clase de modelo deben


implementarse en clases diferentes. No funcionará si intenta implementar
IModelFactory en uno de sus modelos. La buena noticia es que solo
necesita una fábrica de modelos para todos sus modelos personalizados.
Para nuestro tutorial, usaremos una subclase personalizada de MOrder.
Cree la clase MOrder_New que hereda de MOrder e implemente los
constructores predeterminados:

La segunda clase solo implementa IModelFactory y se denomina


MyModelFactory. En getClass() comprobamos si el nombre de la tabla es
igual a nuestro nombre de tabla de modelos personalizados y devolvemos
la clase de modelo si lo es. En los dos métodos getPO(), utilizamos
nuestros constructores de modelos personalizados para crear nuevas
instancias.
Abra mymodelfactory.xml y haga clic en el botón Examinar para seleccionar
la clase MyModelFactory. Agregue también una propiedad entera llamada
"service.ranking" con un valor positivo (>0).

En la pestaña Servicios, agregue IModelFactory a la sección "Servicios


proporcionados".

Básicamente, eso es todo lo que necesitas hacer. Para ver que realmente
hicimos algo, abra de nuevo la clase de modelo personalizado y agregue un
inicio de sesión de consola beforeSave()
Asegúrese de que el complemento está activo en las configuraciones de
ejecución.

Inicie el cliente y asegúrese de que el complemento está activo

Ahora inicie sesión y cree un nuevo pedido de ventas complete todos los
campos obligatorios y haga clic en guardar. Eche un vistazo al registro de la
consola, debería ver el registro de la clase de modelo personalizado:
4. Proceso.
El objetivo es mostrar cómo desarrollaría un nuevo proceso dentro de un
complemento. También descubriremos información útil sobre cómo utilizar
los procesos de iDempiere correctamente.

Lo que aprenderás:

● Cree y ejecute un nuevo proceso que se encuentra en su proyecto


de complemento personalizado.
● Obtenga los parámetros del proceso desde dentro de su proceso.
● Registrar información útil mientras se ejecuta el proceso.
● Cómo manejar los errores correctamente durante el proceso se está
ejecutando y después de que haya finalizado.

Flujo de Trabajo
Uso de puntos de extensión

Después de crear el complemento, lo primero que desea hacer es crear una


nueva clase para su proceso. En nuestro caso, simplemente lo llamamos
MyProcess. Esta clase hereda de SvrProcess e implementa los métodos
necesarios.

Abra su MANIFIESTO. MF y cambie a la pestaña Extensiones. Agregue


una extensión org.adempiere.base.Process. Haga clic con el botón derecho
en él y agregue un nuevo proceso. Haga clic en el botón Examinar y
seleccione MyProcess. Copie el nombre completo de la clase y péguelo en
el campo org.adempiere.base.Nombre e identificador del proceso:
El plugin XML debería tener un aspecto similar al siguiente:

<?xml version="1.0" encoding="UTF-8"?>


<?eclipse version="3.4"?>
<plugin>

<extension

id="org.notima.idempiere.process.CreateWarehouseLocators"

name="org.notima.idempiere.process.CreateWarehouseLocators"
point="org.adempiere.base.Process">
<process

class="org.notima.idempiere.process.CreateWarehouseLocators">
</process>
</extension>

</plugin>

¡NOTA! Eclipse no agrega automáticamente la etiqueta "proceso" y sin ella no se


encontrará la clase de su proceso.

Inicie sesión como Sistema. Abra la ventana Informe y proceso, cree una nueva
entrada. Lo llamamos MyProcess. Como nombre de clase, inserte el nombre de
clase completo (en nuestro caso org.evenos.process.MyProcess):
Cambie a la pestaña Parámetro y agregue algunos parámetros. Para fines de
prueba, agregaremos los siguientes parámetros: -M_Product_ID - Table Direct -
DBColumn "M_Product_ID" -SomeString - String - DBColumn "SomeString"
-SomeInteger - Integer - DBColumn "SomeInteger"

Abra la ventana Menú y cree una nueva entrada. Asígnele el nombre MyProcess y
elija Process como acción. En el campo Proceso, seleccione el proceso creado
anteriormente:
Cierre la sesión y vuelva a entrar. Debería ver su Proceso en el menú. Si ha hecho
todo bien, puede iniciarlo y devolverá "** null" como mensaje. Si se produce algún
error, asegúrese de seguir nuestros tutoriales anteriores. Compruebe si el
complemento está iniciado y activo y si ha elegido los nombres de clase correctos.

Uso de una fábrica de procesos

El uso de una fábrica de procesos es la forma más nueva y mejor de


proporcionar procesos para iDempiere. En lugar de utilizar puntos de extensión en
el manifiesto, se utiliza la definición de componente que utiliza el servicio
IProcessFactory. Para hacer esto, primero crea una nueva clase para su fábrica.
Llámalo, por ejemplo, MyProcessFactory.java. En esta clase, implemente la interfaz
IProcessFactory y sus métodos. En newProcessInstance(String), crea una
instrucción if en la que compruebe el nombre de la clase y, si encaja, devuelva una
nueva instancia del proceso. A continuación, crea una nueva definición de
componente haciendo clic en Archivo>Nuevo>Otros> Definición de componente.

Dale un nombre único y haz clic en finalizar. Una vez creado, asegúrese de que se
crea la entrada correspondiente en su MANIFIESTO. Archivo MF. Me gusta poner
todas mis definiciones de componentes en una carpeta llamada OSGI-INF y solo
agregar esta carpeta a mi manifiesto:

Export-Package: ...
Service-Component: OSGI-INF/*.xml
Bundle-ClassPath: ...
Bundle-Activator: ...

Después, abra la definición del componente y asegúrese de que el nombre sea


único en idempiere. Es una buena idea usar su nombre de dominio inverso seguido
del nombre de la clase o complemento. A continuación, seleccione la clase que
implementa la interfaz de fábrica del proceso y agregue una nueva propiedad
llamada service.ranking, que es un entero. Dale un valor de 100 más o menos. Esto
es necesario, por lo que se carga antes de la fábrica de procesos predeterminada
de idempiere. Vaya a la pestaña de servicios y agregue IProcessFactory a la
sección de servicios proporcionados. Guárdalo y listo.

<?xml version="1.0" encoding="UTF-8"?>


<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
name="org.evenos.pluginname.process.factory">
<implementation class="org.evenos.packagename.MyProcessFactory"/>
<property name="service.ranking" type="Integer" value="100"/>
<service>
<provide interface="org.adempiere.base.IProcessFactory"/>
</service>
</scr:component>

Parámetros de lectura
Regrese a Eclipse y abra MyProcess, es hora de agregar más funcionalidad aquí.
Primero queremos leer los parámetros para este proceso. Comience agregando
las variables de instancia. Luego vaya a prepare() y agregue el código para leer en
los parámetros. Ahora debería verse así (excepto el error tipográfico en
M_Product_ID ;):

Registrar información desde el proceso


Hay diferentes formas de registro que se utilizan comúnmente en iDempiere. En
un proceso, tiene dos, respectivamente tres opciones que queremos mostrarle.
Con la primera opción, debe estar muy familiarizado. Es el registro a través de un
CLogger. Cada proceso que se hereda de SvrProcess tiene una variable de
instancia llamada "log". Puede usar este registrador para registrar información
como en cualquier otro lugar donde se use este tipo de registrador. Los registros
aparecerán en la consola y los registros de iDempiere si están configurados. Si
desactiva la funcionalidad de registro de iDempieres, no verá estos registros. Ya
hicimos una llamada al registrador predeterminado en nuestro método prepare().

La siguiente técnica de registro es llamar a uno de los métodos addLog(). Estos


registros aparecerán en la ventana Auditoría de procesos en iDempiere. Imagine
que su proceso hace algún tipo de procesamiento por lotes. Tal vez pueda salir
algo mal. En lugar de cancelar todo el proceso si se produce un error, simplemente
puede agregar una entrada de registro especial a la auditoría de procesos. Los
métodos addLog() pueden ser simples y simplemente registrar una cadena o
incluso pueden vincularse a una tabla / registro para que pueda saltar
directamente a la entrada correspondiente desde dentro de la auditoría de
procesos. Vamos a añadir algunos registros. Uno que registra la cadena de
entrada y otro que nos lleva al producto que podemos seleccionar como
parámetro:

addLog(getProcessInfo().getAD_Process_ID(),
new Timestamp(System.currentTimeMillis()),
new BigDecimal(getProcessInfo().getAD_PInstance_ID()),
"The input string was: " + someString);
addLog(getProcessInfo().getAD_Process_ID(),
new Timestamp(System.currentTimeMillis()),
new BigDecimal(getProcessInfo().getAD_PInstance_ID()),
"The input integer was: " + someInteger,
MProduct.Table_ID,
m_product_id);

Hay más que puede y debe registrar. ¿Ha notado que doIt() puede lanzar
excepciones y devolver un String? La instrucción return se muestra al usuario.
¿Recuerdas el mensaje "** null" cuando iniciamos el proceso? Puedes decidir lo
que quieres devolver. Si desea lanzar un error, puede hacerlo. El proceso en
ejecución actual se detendrá y mostrará la descripción del error localizado en lugar
de la declaración de retorno. Si marca la ventana Auditoría de procesos, puede ver
si un proceso se canceló debido a un error por su valor de resultado. 0 significa
que se produjo una excepción, 1 significa que devolvió su propio valor.
Agreguemos una instrucción para que sea posible lanzar errores:
Inicie el cliente iDempiere y pruebe el nuevo proceso. Inicie sesión para que tenga
algunos productos disponibles. Inicie el proceso con los siguientes parámetros:

El resultado debería verse así:

Ahora abra la ventana Auditoría de procesos y busque su proceso. Debería verse


así:
En la pestaña Registro de la ventana Auditoría de procesos, debería ver nuestros
tres registros. Seleccione el último y cambie al diseño de una sola fila. Haga clic en
el botón ID de registro. Le llevará directamente al Producto seleccionado.
Lo último que puede hacer ahora es comenzar el proceso nuevamente, pero esta
vez, elija un valor entero > 10. Compare las dos ejecuciones en la ventana
Auditoría de procesos:

Interactuar con el usuario


Algunas veces, necesita interactuar con el usuario durante la ejecución de un
proceso para tomar, por ejemplo, decisiones. ¿Por qué no darle al proceso estas
decisiones como parámetros antes de la ejecución? Porque es posible que no
sepa las preguntas que tendría que responder. Permítanme darles este ejemplo:
en Europa, tenemos SEPA (Área Única de Pago Europea), que es un método de
pago de dinero unido en toda la UE. Ahora, si desea enviar un pago a través de la
banca en línea, enviará sus números de cuenta bancaria y demás al servidor
bancario. Luego, el servidor genera algunos números que debe ingresar en un
pequeño dispositivo que luego genera el número de transacción para este pago.
Este número será enviado de nuevo al banco. Verá, durante el proceso de envío
de un pago, debe interactuar con el usuario.

iDempiere le ofrece una manera fácil de interactuar con el usuario durante la


ejecución de un proceso. Todo lo que tiene que hacer es llamar a la variable de
instancia "processUI", llamar a uno de los métodos ask () y devolver la llamada.
Así es como se vería el método youe doIt() con un panel de solicitud simple para
una entrada booleana:

@Override
protected String doIt() throws Exception {
this.processUI.ask("Yes or No?", new Callback<Boolean>() {
public void onCallback(Boolean result) {
addLog("You selected " + (result ? "Yes" : "No"));
}
});
return null;
}

5. Clases Modelos.
El primer paso es crear un nuevo modelo de clases en forma de dos tablas
de base de datos con una relación padre-hijo entre ellas.

Tabla Principal:
Cree la tabla principal eve_main de la siguiente manera:

CREATE TABLE eve_main(


-- Mandatory columns
eve_main_id numeric(10,0) PRIMARY KEY,
eve_main_uu character varying(36) DEFAULT NULL::character
varying UNIQUE,
ad_client_id numeric(10,0) NOT NULL,
ad_org_id numeric(10,0) NOT NULL,
isactive character(1) NOT NULL DEFAULT 'Y'::bpchar,
created timestamp without time zone NOT NULL DEFAULT now(),
createdby numeric(10,0) NOT NULL,
updated timestamp without time zone NOT NULL DEFAULT now(),
updatedby numeric(10,0) NOT NULL,

-- User-defined columns
value character varying(40) NOT NULL,
name character varying(255) NOT NULL,
description character varying(255) NOT NULL,
help character varying(2000),
m_product_id numeric(10,0),
UNIQUE (ad_client_id, value)
);

La convención de nomenclatura de tablas de iDempiere consiste en prefijar


el nombre de la tabla con tres o más letras específicas de su proyecto,
seguidas de un guión bajo y el nombre de la tabla en camelCase. En este
ejemplo, el nombre de la tabla tiene el prefijo eve_, para el nombre de la
empresa para la que trabaja Jan, Evenos.

La tabla debe comenzar con un conjunto de columnas obligatorias que


iDempiere utiliza para administrar los registros y registrar la actividad con
fines de auditoría. En la tabla anterior se encuentran:

● eve_main_id: la clave principal de la tabla, nombrada agregando el sufijo


_id al nombre de la tabla;
● eve_main_uu: un identificador único, nombrado agregando el sufijo _uu
al nombre de la tabla;
● ad_client_id: el identificador del cliente o inquilino;
● ad_org_id: el identificador de la unidad organizativa dentro del cliente;
● isactive: los registros se pueden desactivar para permitir eliminaciones
lógicas;
● created: la fecha en que se creó este registro;
● createdby: el usuario que creó el registro;
● updated: la fecha en que se actualizó este registro;
● updatedby: el usuario que actualizó el registro.

Las columnas definidas por el usuario recomendadas incluyen:

● valor: la clave o el código de búsqueda;


● nombre: el nombre para mostrar;
● descripción: descripción larga;
● ayuda: ayuda o comentarios.
La tabla principal incluye un campo m_product_id que se encuentra en la tabla
iDempiere estándar M_Product, y tiene una restricción que garantiza que el
contenido de las columnas cliente y valor sean únicos en combinación.

Tenga en cuenta que las columnas con la restricción 'NOT NULL' se convertirán en
campos obligatorios en la ventana.

Tabla infantil:
Cree la tabla secundaria eve_sub siguiente:

CREATE TABLE eve_sub(


-- Mandatory columns
eve_sub_id numeric(10,0) PRIMARY KEY,
eve_sub_uu character varying(36) DEFAULT NULL::character varying
UNIQUE,
ad_client_id numeric(10,0) NOT NULL,
ad_org_id numeric(10,0) NOT NULL,
isactive character(1) NOT NULL DEFAULT 'Y'::bpchar,
created timestamp without time zone NOT NULL DEFAULT now(),
createdby numeric(10,0) NOT NULL,
updated timestamp without time zone NOT NULL DEFAULT now(),
updatedby numeric(10,0) NOT NULL,

-- User-defined columns
eve_main_id numeric(10,0) NOT NULL references eve_main(eve_main_id),
value character varying(40) NOT NULL,
name character varying(255) NOT NULL,
description character varying(255) NOT NULL,
help character varying(2000),
UNIQUE (ad_client_id, value)
);

Esto es como la tabla principal, excepto que tiene una relación de clave externa
con el padre.

Tabla y columnas
Iniciar sesión con el rol de System, después de iniciar sesión, seleccione el botón
crear tabla y columna a la derecha de la entrada Tabla y columna en la ventana
Favoritos. Cree la tabla introduciendo la tabla de base de datos principal en el
campo Nombre de tabla de base de datos. Guarde el registro de tabla y, a
continuación, haga clic en el engranaje Procesos para crear columnas a partir de la
tabla de base de datos, el proceso le pedirá que confirme que desea crear las
columnas, cuando el proceso se haya completado, enumerará las columnas que se
crearon, ahora tiene la nueva tabla y columnas para trabajar.

Ahora repita el ejercicio para crear una entrada de tabla para la tabla de base de
datos secundaria eve_sub y genere las columnas a partir de la base de
datos.Debemos vincular la tabla hija a su padre, utilizando la columna
EVE_Main_ID, así que selecciónela y ábrala, luego marque el atributo de columna
Enlace padre y guarde el cambio en la columna. Tenga en cuenta que podríamos
haber hecho lo mismo con la tabla principal EVE_Main anterior para vincularla a la
tabla M_Product también.

Ventana y pestañas
Hemos creado las entradas para las dos tablas, por lo que ahora podemos crear la
ventana y la pestaña para el EVE_Main padre y una subpestaña para el EVE_Sub
secundario.

Crear ventana
Haga clic en el botón para crear una nueva ventana (a la derecha de la entrada
Ventana, pestaña y campo en Favoritos) e ingrese el nombre.

Desmarque el atributo Transacción de ventas porque no es relevante y guarde el


registro.Haga clic en la pestaña Pestaña debajo de la ventana que creó
anteriormente para abrir una nueva pantalla para la pestaña. Introduzca el nombre
Pestaña Principal y seleccione el Nombre de la tabla EVE_Main_Evenos Principal
en el menú desplegable. Compruebe el atributo Single Row Layout y, a
continuación, guarde el registro.Desplácese hacia abajo hasta el botón grande
Crear campos en la pantalla de pestañas y selecciónelo para generar los campos
de la tabla de la base de datos. Seleccione Aceptar en la pantalla de
confirmación.Haga clic en el engranaje de la barra de herramientas para abrir un
editor gráfico para editar el diseño de los campos. El Editor de pestañas se abrirá
en una nueva ventana donde puede agregar y eliminar campos, arrastrarlos
(agarrando el título del campo), cambiar el tamaño (haciendo doble clic con y sin
mantener presionada la tecla CTRL), etc.

Ahora debemos crear la subpestaña para la tabla secundaria, así que haga clic en
el icono Nuevo registro e ingrese los detalles para la subpestaña. Tenga en cuenta
que el nivel de tabulación debe establecerse en 1 porque es una subpestaña (el
nivel predeterminado es 0). Desplácese hacia abajo y haga clic en el icono Crear
campos para generar los campos para la subpestaña. Debe crear una entrada de
menú para usar la nueva ventana, así que haga clic en el botón para crear un nuevo
menú (a la derecha de la entrada de menú en Favoritos). Escriba el nombre,
establezca la Acción en Ventana y use el menú desplegable en Ventana para
encontrar la ventana que creó anteriormente y guarde el registro.

Plug-In

En este punto, el nuevo modelo solo está disponible en el entorno de desarrollo y


se implementa mediante mecanismos predeterminados que no contienen lógica
de negocio. Necesitamos construir un plug-in para contener el nuevo modelo, junto
con cualquier lógica a medida que pueda ser necesaria, para que pueda
implementarse con el sistema base.

Crear un proyecto de plug-in, si aún no lo ha hecho, cree un subdirectorio en su


espacio de trabajo para complementos a medida para mantener limpia la base de
código (en este ejemplo, el subdirectorio se denomina zerlina-plugins).

Archivo > nuevo > otro complemento de > > próxima >

Ahora navegue a la carpeta src del nuevo proyecto y cree un nuevo paquete.

Haga clic con el botón derecho en src > Nuevo paquete de >

Genere los modelos con el generador de modelos.

Ejecutar > Ejecutar configuraciones... > > ejecutar el > de generador


Después de unos segundos, se abrirá el generador de clases de modelo.

● utilice el '...' en la carpeta de origen para buscar la carpeta src del proyecto
en el subdirectorio de complementos;
● El nombre del paquete es 'org.evenos.model';
● El nombre de la tabla es 'EVE_%' para generar modelos para ambas tablas;
● El tipo de entidad es 'U' para el usuario mantenido.
● Actualice el proyecto para ver las clases de modelo recién generadas.

Clases 'M'

Cree dos clases 'M' para mantener la lógica de negocios para cada una de las dos
tablas.

Haga clic con el botón derecho en org.evenos.model > Nuevo > Nombre de clase >:
MEVEMain > Finish

Agregue dos constructores y un ID de versión de serie generado.

package org.evenos.model;

import java.sql.ResultSet;

import java.util.Properties;

public class MEVEMain extends X_EVE_Main{

/**

*/
private static final long serialVersionUID =
-4652910060540398746L;

public MEVEMain(Properties ctx, ResultSet rs, String trxName) {

super(ctx, rs, trxName);

// TODO Auto-generated constructor stub

public MEVEMain(Properties ctx, int EVE_Main_ID, String trxName)


{

super(ctx, EVE_Main_ID, trxName);

// TODO Auto-generated constructor stub

Repetir para MEVESub:

package org.evenos.model;

import java.sql.ResultSet;

import java.util.Properties;

public class MEVESub extends X_EVE_Sub{

/**
*

*/

private static final long serialVersionUID =


-2076611601274220946L;

public MEVESub(Properties ctx, int EVE_Sub_ID, String trxName) {

super(ctx, EVE_Sub_ID, trxName);

// TODO Auto-generated constructor stub

public MEVESub(Properties ctx, ResultSet rs, String trxName) {

super(ctx, rs, trxName);

// TODO Auto-generated constructor stub

Para tener una idea de lo que la lógica de negocios es posible en estas clases, mire
las clases 'M' en el paquete 'org.compiere.model' en el proyecto
'org.adempiere.base', que es el núcleo de iDempiere.

Dependencias

El funcionamiento del nuevo plug-in depende de otros dos plug-ins, así que utilice
el botón Add... para añadir 'org.adempiere.base' y 'org.adempiere.plugin.utils' al
manifiesto del plug-in.

Fábrica de modelos

El complemento requiere una fábrica de modelos personalizada para crear el


nuevo modelo de clase en lugar de depender de la fábrica de modelos
predeterminada. Cree un nuevo paquete con una nueva clase . A continuación
mostraremos diferentes enfoques para crear esta fábrica.
org.evenos.factoriesMyModelFactory

package org.evenos.factories;

import java.sql.ResultSet;

import org.adempiere.base.IModelFactory;

import org.compiere.model.PO;

import org.compiere.util.Env;

import org.evenos.model.MEVEMain;

import org.evenos.model.MEVESub;

public class MyModelFactory implements IModelFactory{

@Override

public Class<?> getClass(String tableName) {

if(tableName.equalsIgnoreCase(MEVESub.Table_Name))

return MEVESub.class;

if(tableName.equalsIgnoreCase(MEVEMain.Table_Name))

return MEVEMain.class;
return null;

@Override

public PO getPO(String tableName, int Record_ID, String trxName)


{

if(tableName.equalsIgnoreCase(MEVESub.Table_Name))

return new MEVESub(Env.getCtx(), Record_ID,


trxName);

if(tableName.equalsIgnoreCase(MEVEMain.Table_Name))

return new MEVEMain(Env.getCtx(), Record_ID,


trxName);

return null;

@Override

public PO getPO(String tableName, ResultSet rs, String trxName)


{

if(tableName.equalsIgnoreCase(MEVESub.Table_Name))

return new MEVESub(Env.getCtx(), rs, trxName);

if(tableName.equalsIgnoreCase(MEVEMain.Table_Name))

return new MEVEMain(Env.getCtx(), rs, trxName);


return null;

Definición de componentes

La fábrica de modelos personalizada debe darse a conocer al marco OSGi para


que se invoque con preferencia a la fábrica de modelos predeterminada. Cree una
nueva carpeta 'OSGI-INF' en el proyecto y, a continuación, cree un archivo de
definición de componentes.

Haga clic con el botón derecho en OSGI-INF > Nuevo > Otro... > Plug-in
Development > Component Definición > Nombre: ModelFactory.xml > Finish

Escriba lo siguiente:

Nota:
● El nombre es 'org.evenos.newwindow.model.factory'
● La clase es 'org.evenos.factories.MyModelFactory' (use el botón
Examinar para encontrarla)
● Haga clic en Agregar propiedad para crear la propiedad 'service.ranking'
con el tipo Integer y el valor 100, lo que garantizará que OSGi use la
fábrica de modelos personalizadas con preferencia a la versión
predeterminada
Ahora agregue IModelFactory a la lista de servicios proporcionados en la pestaña
Servicios de la Definición de componente:

Haga clic en la pestaña 'Servicios' > Complemento de servicios proporcionados >


IModelFactory y guarde el archivo de definición.

Por último, actualice el manifiesto para que la definición de componente cargue


todos los servicios definidos en el directorio OSGI-INF cambiando la última línea de
la siguiente manera:

Manifest-Version: 1.0

Bundle-ManifestVersion: 2

Bundle-Name: Newwindow

Bundle-SymbolicName: org.evenos.newwindow

Bundle-Version: 1.0.0.qualifier

Bundle-RequiredExecutionEnvironment: JavaSE-1.6

Require-Bundle: org.adempiere.base;bundle-version="4.1.0",

org.adempiere.plugin.utils;bundle-version="4.1.0"

Service-Component: OSGI-INF/*

El complemento ya está completo, por lo que puede compilar el producto e


implementarlo para su uso.

6. Eventos.
Cómo desarrollar un EventHandler en su propio complemento para que
pueda compartir su complemento con otros con toda la información
necesaria para que funciones.

Para registrar un nuevo controlador de eventos, debe conocer el nombre


del tema del evento modelo, crear un nueva clase java y registrar esa clase
como un componente OSGI. Los temas de eventos del modelo publicados
por iDempiere se definen en la interfaz IEventTopics (tenga en cuenta que
el paquete y otro paquete de extensiones pueden publicar temas de
eventos adicionales). Según lo requerido por el marco de administración de
eventos de OSGI, la clase de controlador de eventos debe implementar la
interfaz OSGi EventHandler. Sin embargo, para facilitar la codificación del
controlador de eventos, iDempiere incluye la clase AbstractEventHandler
para que la clase del controlador de eventos se extienda. Una de las
ventajas de que su clase de controlador de eventos extienda
AbstractEventHandler es que puede detener el evento simplemente
lanzando RuntimeException en el método doEventHandler.

Si su clase extiende AbstractEventHandler, debe realizar el paso 2 a


continuación en su clase:

● Invalide el método initialize() para registrar el evento de tabla que le


interesa a la clase.
● Invalide el método doHandleEvent(Event event) para realizar su
trabajo cuando se produzca el evento registrado.
Crear definición de componente
Nota : si utiliza Eclipse Oxigen o una versión más reciente, puede utilizar
anotaciones en lugar de crear la definición de componente manualmente.

El controlador de eventos recibió el servicio IEventManager a través del marco de


servicio dinámico OSGi. Para ello, debe declarar la clase de controlador de
eventos como un componente OSGi.

Primero, crea una nueva carpeta en tu paquete para almacenar el archivo de


definición de componentes OSGi (un archivo xml). La convención estándar para
esto es renombrar esa carpeta como "OSGI-INF". Haga clic derecho en la carpeta
OSGI-INF, seleccione nueva Definición de componentes en el menú contextual.
El editor de definiciones de componentes tiene 3 pestañas, Información general,
Servicios y Origen. La ficha Origen es el origen xml sin procesar. En la pestaña
Información general, debe definir el nombre del componente y rellenar el campo
de clase con el nombre de la clase del controlador de eventos. El nombre del
componente debe ser único dentro de todo el sistema, la recomendación es usar
todas las minúsculas calificadas por el nombre del paquete java de la clase de
componente.
En la pestaña servicios, debe agregar el servicio IEventManager como una de las
entradas de la lista Servicios a los que se hace referencia.
En el cuadro de diálogo ReferencedService, se hace referencia al servicio utilizando
el nombre completo de la interfaz java del servicio:
org.adempiere.base.event.IEventManager ( el método bindEventManager y
unbindEventManager es de la clase AbstractEventHandler ).
El editor de componentes de Eclipse guardará la definición de componente como
un archivo xml al que puede asignar un nombre arbitrario ( componente de
ejemplo.xml ) En tiempo de ejecución, el marco de servicio dinámico OSGi
inyectará automáticamente el servicio IEventManager en el controlador de eventos
cuando se haya iniciado el servicio IEventManager.

Además, el editor de componentes de Eclipse debe agregar automáticamente la


referencia de definición de componente al MANIFEST de su complemento. Archivo
MF. Tenga en cuenta que si tiene muchos archivos de definición de componentes,
puede usar *.xml en lugar de incluir todos los nombres allí.
Finalmente, debe activar la opción "Activar este complemento cuando se cargue la
clase".

7. Formas (Ventana Personalizada).


El objetivo de esta parte es mostrar cómo utilizar el servicio IFormFactory
en el proyecto de complemento.
Hay dos formas de crear su propio formulario y luego registrarlo usando
IFormFactory:
● Método 1: Crear una clase componente que extienda la clase
ADForm registrarla.
● Método 2: Cree una clase Java que implemente la interfaz
IFormController para definir su formulario personalizado y luego
registrarlo.

En este plug-in hará un Formulario usando la interfaz IFormController y


proporcionaré mi nuevo Formulario en el Servicio OSGI para iDempiere
pueda llamarlo (Método 2).

* Crea tu propio plug-in:

+ Desmarque "Usar ubicación predeterminada" y vaya a su directorio de


complementos.

+ Marque "un marco OSGI", elija Equinox.


+ Desmarque "Generar un activador...", más adelante usaremos el
AdempiereActivator predeterminado proporcionado en iDempiere.
* Configurar el plug-in META-INF/MANIFEST. MF:

+ Abrir MANIFIESTO. MF dentro de META-INF, navegue a la pestaña Dependencias


y agregue los complementos requeridos:

org.adempiere.base
org.adempiere.plugin.utils

org.adempiere.ui.zk

org.zkoss.zk.library

+ Vaya a la pestaña Descripción general y marque "Activar este


complemento cuando se cargue una de sus clases" y "Este complemento es
un singleton".

+ Vaya a org.adempiere.plugin.utils.AdempiereActivator en el campo


Activador.
* Crea tu clase Form:

En este tutorial creé WMyFormController.java que implementa


IFormController y EventListener para controlar alguna función en el
formulario y colocarla en el paquete com.hdsoft.webui.apps.form.
* Crea tu clase Form Factory:

En este paso, creamos una clase que implementa IFormFactory, puede nombrarla
de acuerdo con sus preferencias. Por ejemplo, lo llamé HdSoftFormFactory y lo
coloqué dentro del paquete com.hdsoft.webui.factory. Esta clase está destinada a
ser utilizada como el cargador de formulario predeterminado de su complemento,
al que se llamará desde el servicio OSGI cuando haya una solicitud para crear un
formulario desde el cliente. En esta etapa aún no se ha definido interactuar con el
servicio OSGI. Simplemente agréguele un poco de código para que luego funcione
correctamente.

* Cree una definición de componente para su clase Form Factory:

Cree una carpeta en su complemento y asígnele el nombre OSGI-INF, luego


agregue la definición del componente en la carpeta, vaya a su clase de fábrica de
formularios creada en el paso anterior. Se requiere que el nombre del componente
sea único dentro del ámbito de iDempiere, de lo contrario, el servicio OSGI no
podrá cargarlo cuando se inicie el servidor.
* Configurar la definición de componentes:

+ Navegue a la pestaña Servicios y agregue IFormFactory a la sección Servicios


proporcionados.
+ Vaya a la pestaña Información general y agregue la propiedad denominada
service.ranking con el tipo Integer y un valor positivo (1, por ejemplo) a la sección
Propiedades.

La propiedad service.ranking ayuda a OSGI a determinar la prioridad para buscar


clases que proporcionan servicios en complementos. Por ejemplo, si ejecuta 2
complementos diferentes con 2 clases de fábrica separadas, OSGI ejecutará
continuamente métodos en las clases que proporcionan servicios de alta a baja
prioridad (de service.ranking 1 a 2 y superior...) hasta que uno de los métodos de
búsqueda devuelve un objeto legal (no nulo), el proceso de búsqueda se detendrá.
Por lo tanto, asegúrese de utilizar un valor service.ranking diferente para controlar
el flujo de búsqueda de OSGI para el mismo tipo de servicio de fábrica que desea.
Ahora, cuando iniciamos iDempiere Server en Eclipse, abrimos Ejecutar
configuraciones y recordamos verificar su complemento para comenzar con el
servidor, también puede configurarlo como inicio automático = verdadero.

* Configura tu formulario en iDempiere:

+ Inicie iDempiere con su nuevo complemento. Inicie sesión como rol de


administrador del sistema.

+ Crear nuevo formulario

+ Crear nuevo menú

* Ejecute su nuevo formulario personalizado:

+ Iniciar sesión como administrador.

+ Abra el nuevo formulario personalizado desde el menú (en caso de que no


aparezca, abra el menú Rol y agregue su acceso de rol al nuevo formulario
personalizado)
8. Sobrescribir clase contable.
El objetivo de este tutorial es mostrar cómo se utiliza el servicio IDocFactory
en el proyecto de plug-in. La razón por la que desea utilizar el servicio de
fábrica de documentos es que puede proporcionar su propio método para
contabilizar los documentos principales (facturas, envíos, pagos, etc.) o
para sus propios tipos de documentos. Para nuestro ejemplo, cambiaremos
la forma en que se contabilizan las facturas.

Después de crear el complemento y establecer sus dependencias, haga clic


en Archivo>Nuevo>Otros y seleccione "Definición de componentes" en la
sección Desarrollo de complementos.

Haga clic en siguiente. Seleccione su proyecto de plug-in (carpeta OSGI) y


elija un nombre de archivo y un nombre general. En nuestro ejemplo,
usamos "DocFactory.xml" y "org.idempierelbr.tax.DocFactory" para el
nombre de archivo / nombre, pero todos los demás nombres también están
bien. Todavía no es necesario establecer el nombre de la clase porque no
hemos implementado uno. Haga clic en finalizar.

Lo siguiente que necesitaremos es:

● Un Doc_Invoice (una clase que extiende Doc)


● Un DocFactory (una clase que implementa IDocFactory)
Solo necesitas un DocFactory para todas tus clases de Doc_*. Para
simplificar, acabamos de copiar la clase original de Doc_Invoice.java
ubicada en org.adempiere.base/src/org.compiere.acct/
La segunda clase solo implementa IDocFactory y se llama DocFactory. Hay
dos métodos para implementar. Ambas imágenes a continuación muestran
el código completo.
Abra docFactory.xml y haga clic en el botón Examinar para seleccionar la
clase DocFactory. Agregue una propiedad entera denominada
"service.ranking" con un valor positivo (>0). Agregue también una propiedad
string llamada "gaap" con el valor "*". Obs: el valor "*" significa que se
utilizará su Doc_Invoice, sin importar qué GAAP seleccione en la ventana
Esquema de contabilidad (Menu->Aplicaciones de rendimiento->Reglas
contables->Esquema de contabilidad). También puede iniciar sesión como
Sistema, localizar la referencia "C_AcctSchema GAAP", agregar una nueva
Validación de Lista (Ej: "GAAP Brasileño") y usar su clave de búsqueda en
lugar de "*" de DocFactory.xml. En este caso, la clase Doc_Invoice se usará
sólo cuando su organización esté utilizando los nuevos GAAP creados.
En la pestaña Servicios, agregue org.adempiere.base.IDocFactory a la
sección "Servicios proporcionados".

Básicamente, eso es todo lo que necesitas hacer. Intente modificar su clase


de Doc_Invoice y cree (y publique) una nueva factura. Sus reglas se
utilizarán en lugar del mecanismo central.

También podría gustarte