openxava / documentación / Lección 25: Comportamiento y lógica de negocio
Curso:
1. Primeros pasos |
2. Modelo básico del dominio (1) |
3. Modelo básico del dominio (2) |
4. Refinar la interfaz de usuario |
5. Desarrollo ágil |
6. Herencia de superclases mapedas |
7. Herencia de entidades |
8. Herencia de vistas |
9. Propiedades Java |
10. Propiedades calculadas |
11. @DefaultValueCalculator en colecciones |
12. @Calculation y totales de colección |
13. @DefaultValueCalculator desde archivo |
14. Evolución del esquema manual |
15. Cálculo de valor por defecto multiusuario |
16. Sincronizar propiedades persistentes y calculadas |
17. Lógica desde la base de datos |
18. Validando con @EntityValidator |
19. Alternativas de validación |
20. Validación al borrar |
21. Anotación Bean Validation propia |
22. Llamada REST desde una validación |
23. Atributos en anotaciones |
24. Refinar el comportamiento predefinido |
25. Comportamiento y lógica de negocio |
26. Referencias y colecciones |
A. Arquitectura y filosofía |
B. Java Persistence API |
C. Anotaciones |
D. Pruebas automáticas
OpenXava no es simplemente un marco de trabajo para hacer mantenimientos (altas, bajas, modificaciones y consultas), más bien está concebido para desarrollar aplicaciones de gestión plenamente funcionales. Hasta ahora hemos aprendido como crear y refinar la aplicación para manejar los datos. Ahora vamos a posibilitar al usuario la ejecución de lógica de negocio específica.
En esta lección vamos a ver como escribir lógica de negocio en el modelo y llamar a esta lógica desde acciones personalizadas. Así podrás transformar tu aplicación de gestión de datos en una herramienta útil para el trabajo cotidiano de tu usuario.
Lógica de negocio desde el modo detalle
Empezaremos con el caso más simple: un botón en modo detalle para ejecutar cierta lógica. En este caso para crear la factura desde un pedido:
Aquí se muestra como esta nueva acción coge el pedido actual y crea una factura a partir de él. Simplemente copia todos los datos del pedido a la nueva factura, incluyendo las líneas de detalle. Se muestra un mensaje y la pestaña FACTURA del pedido visualizará la factura recién creada. Veamos como codificar este comportamiento.
Crear una acción para ejecutar lógica personalizada
Como ya sabes el primer paso para tener una acción personalizada en tu módulo es definir un controlador con esa acción. Por tanto, editemos
controladores.xml y añadamos un nuevo controlador. El siguiente código muestra el controlador
Pedido:
<controlador nombre="Pedido">
<hereda-de controlador="Facturacion"/> <!-- Para tener las acciones estándar -->
<accion nombre="crearFactura" modo="detail"
clase="com.tuempresa.facturacion.acciones.CrearFacturaDesdePedido"/>
<!-- modo="detail" : Sólo en modo detalle -->
</controlador>
Dado que hemos seguido la convención de dar al controlador el mismo nombre que a la entidad y el módulo, ya tenemos automáticamente esta nueva acción disponible para
Pedido. El controlador
Pedido desciende del controlador
Facturacion. Recuerda que creamos un controlador
Facturacion en la lección anterior. Es un refinamiento del controlador
Typical.
Ahora hemos de escribir el código Java para la acción. Puedes verlo aquí:
package com.tuempresa.facturacion.acciones; // En el paquete 'acciones'
import org.openxava.actions.*;
import org.openxava.jpa.*;
import com.tuempresa.facturacion.modelo.*;
public class CrearFacturaDesdePedido
extends ViewBaseAction { // Para usar getView()
public void execute() throws Exception {
Pedido pedido = XPersistence.getManager().find( // Usamos JPA para obtener la
Pedido.class, // entidad Pedido visualizada en la vista
getView().getValue("oid"));
pedido.crearFactura(); // El trabajo de verdad lo delegamos en la entidad
getView().refresh(); // Para ver la factura creada en la pestaña FACTURA
addMessage("factura_creada_desde_pedido", // Mensaje de confirmación
pedido.getFactura());
}
}
Realmente simple. Buscamos la entidad
Pedido, llamamos al método
crearFactura(), refrescamos la vista y mostramos un mensaje. Fíjate como la acción es un mero intermediario entre la vista (la interfaz de usuario) y el modelo (la lógica de negocio).
Recuerda añadir el texto del mensaje en el archivo
facturacion-messages_es.properties de la carpeta
src/main/resources/i18n:
factura_creada_desde_pedido=Factura {0} creada a partir del pedido actual
Sin embargo, el mensaje tal cual está no se muestra de forma agradable, porque enviamos como argumento un objeto
Factura. Necesitamos un
toString() para
Factura y
Pedido que sea útil para el usuario. Sobrescribiremos
toString() de
DocumentoComercial (el padre de
Factura y
Pedido) para conseguirlo. Puedes ver este método
toString():
abstract public class DocumentoComercial extends Eliminable { {
...
public String toString() {
return anyo + "/" + numero;
}
}
El año y el número son perfectos para identificar una factura o pedido desde el punto de vista del usuario.
Esto es todo para la acción. Veamos la pieza restante, el método
crearFactura() de la entidad
Pedido.
Escribiendo la lógica de negocio real en la entidad
La lógica de negocio para crear una nueva
Factura está en la entidad
Pedido, no en la acción. Esto es la forma natural de hacerlo. El principio esencial de la Orientación a Objetos es que los objetos no son solo datos, sino datos y lógica. El código más bello es aquel cuyos objetos contienen la lógica para manejar sus propios datos. Si tus entidades son meros contenedores de datos (simples envoltorios de las tablas de la base de datos) y tus acciones tienen toda la lógica para manipularlos, en ese caso tu código es una perversión del objetivo original de la Orientación a Objetos.
Aparte de las razones espirituales, poner la lógica para crear una
Factura dentro de
Pedido es un enfoque pragmático, porque de esta forma podemos usar esta lógica desde otras acciones, proceso masivos, servicios web, etc.
Veamos el código del método
crearFactura() de la clase
Pedido:
public class Pedido extends DocumentoComercial {
...
public void crearFactura() throws Exception { // throws Exception para tener
// un código más simple, de momento
Factura factura = new Factura(); // Instancia una factura
BeanUtils.copyProperties(factura, this); // y copia el estado del pedido actual
factura.setOid(null); // Para que JPA sepa que esta entidad todavía no existe
factura.setFecha(LocalDate.now()); // La fecha para la nueva factura es hoy
factura.setDetalles(new ArrayList<>(getDetalles())); // Clona la colección detalles
XPersistence.getManager().persist(factura);
this.factura = factura; // Siempre después de persist()
}
}
La lógica consiste en crear un nuevo objeto
Factura, copiar los datos desde el
Pedido actual a él y asignar la entidad resultante a la referencia
factura del
Pedido actual.
Hay tres sutiles detalles aquí. Primero, has de escribir
factura.setOid(null), si no la nueva
Factura tendría la misma identidad que el
Pedido original, además a JPA no le gusta persistir los objetos con el id autogenerado rellenado de antemano. Segundo, has de asignar la nueva
Factura al actual Pedido (
this.factura = factura) después de llamar a
persist(factura), si no obtendrás un error de JPA (algo así como "object references an unsaved transient instance". Tercero, hemos de envolver la colección
detalles con un
new ArrayList(), para que sea una colección nueva aunque con los mismos elementos, porque JPA no quiere la misma colección asignada a dos entidades.
Escribe menos código usando Apache Commons BeanUtils
Observa como hemos usado
BeanUtils.copyProperties() para copiar todas las propiedades del actual
Pedido a la nueva
Factura. Este método copia todas las propiedades con el mismo nombre de un objeto a otro, incluso si los objetos son de clases diferentes. Esta utilidad pertenece al proyecto de apache Commons BeanUtils. El jar para esta utilidad,
commons-beanutils.jar, ya está incluido en tu proyecto.
El siguiente código muestra como usando BeanUtils escribes menos:
BeanUtils.copyProperties(factura, this);
// Es lo mismo que
factura.setOid(getOid());
factura.setAnyo(getAnyo());
factura.setNumero(getNumero());
factura.setFecha(getFecha());
factura.setEliminado(isEliminado());
factura.setCliente(getCliente());
factura.setPorcentajeIVA(getPorcentajeIVA());
factura.setIva(getIva());
factura.setImporteTotal(getImporteTotal());
factura.setObservaciones(getObservaciones());
factura.setDetalles(getDetalles());
Sin embargo, la principal ventaja de usar BeanUtils no es ahorrar tiempo de tecleo, sino que obtienes un código más resistente a los cambios. Porque, si añades, quitas o renombras alguna propiedad de
DocumentoComercial (el padre de
Factura y
Pedido), si estás copiando las propiedades a mano tienes que cambiar el código, mientras que si estás usando
BeanUtils.copyProperties() el código funcionará siempre bien, sin tener que cambiarlo.
Excepciones de aplicación
Recuerda la frase: "La excepción que confirma la regla". Las reglas, la vida y el software están llenos de excepciones. Y nuestro método
crearFactura() no es una excepción. Hemos escrito código que funciona en los casos más comunes. Pero, ¿qué ocurre si el pedido no está listo para ser facturado o si hay algún problema para acceder a la base de datos? Obviamente, en este caso necesitamos tomar caminos diferentes.
Es decir, el simple
throws Exception que hemos escrito para el método
crearFactura() no es suficiente para un comportamiento refinado. Deberiamos crear nuestra propia excepción, hagámoslo:
package com.tuempresa.facturacion.modelo; // En el paquete 'modelo'
import org.openxava.util.*;
public class CrearFacturaException extends Exception { // No RuntimeException
public CrearFacturaException(String mensaje) {
// El XavaResources es para traducir el mensaje desde el id en i18n
super(XavaResources.getString(mensaje));
}
}
Ahora podemos usar nuestra
CrearFacturaException en lugar de
Exception en el método
crearFactura() de
Pedido:
public void crearFactura()
throws CrearFacturaException // Una excepción de aplicación (1)
{
if (this.factura != null) { // Si ya tiene una factura no podemos crearla
throw new CrearFacturaException(
"pedido_ya_tiene_factura"); // Admite un id de 18n como argumento
}
if (!isEntregado()) { // Si el pedido no está entregado no podemos crear la factura
throw new CrearFacturaException("pedido_no_entregado");
}
try {
Factura factura = new Factura();
BeanUtils.copyProperties(factura, this);
factura.setOid(null);
factura.setFecha(LocalDate.now());
factura.setDetalles(new ArrayList<>(getDetalles()));
XPersistence.getManager().persist(factura);
this.factura = factura;
}
catch (Exception ex) { // Cualquier excepción inesperada (2)
throw new SystemException( // Se lanza una excepción runtime (3)
"imposible_crear_factura", ex);
}
}
Ahora declaramos explícitamente las excepciones de aplicación que este método lanza (1). Una excepción de aplicación es una excepción chequeada que indica un comportamiento especial pero esperado del método. Una excepción de aplicación está relacionada con la lógica de negocio del método. Puedes crear una excepción de aplicación para cada posible caso. Por ejemplo, podrías crear una
PedidoYaTieneFacturaException y una
PedidoNoEntregadoException. Esto te permitiría tratar cada caso de forma diferente desde el código que usa el método. Aunque, esto no es necesario en nuestro caso, por tanto nosotros simplemente usamos nuestra
CrearFacturaException, una excepción de aplicación genérica para este método.
También hemos de enfrentarnos a problemas inesperados (2). Los problemas inesperados incluyen errores del sistema (acceso a base de datos, la red o problemas de hardware) o errores de programación (
NullPointerException, IndexOutOfBoundsException, etc). Cuando nos encontramos con cualquier problema inesperado lanzamos una
RuntimeException (3). En este caso hemos lanzado una
SystemException, una
RuntimeException incluida en OpenXava por comodidad, pero puedes lanzar la
RuntimeException que quieras.
No necesitas modificar el código de la acción. Si tu acción no atrapa las excepciones, OpenXava lo hace automáticamente. Muestra los mensajes de las excepciones de aplicación al usuario; y para las excepciones runtime, muestra un mensaje de error genérico y aborta la transacción.
Para rematar, añadimos el mensaje para la excepción en los archivos
i18n. Edita el archivo
facturacion-messages_es.properties de la carpeta
src/main/resources/i18n añadiendo las siguientes entradas:
pedido_ya_tiene_factura=El pedido ya tiene una factura
pedido_no_entregado=El pedido todavía no está entregado
imposible_crear_factura=Imposible crear factura
Hay cierto debate en la comunidad de desarrolladores sobre la manera correcta de usar las excepciones en Java. El enfoque usado en esta sección es la forma clásica de trabajar con excepciones en el mundo Java empresarial.
Validar desde la acción
Usualmente el mejor lugar para las validaciones es el modelo, es decir, las entidades. Sin embargo, a veces es necesario poner lógica de validación en las acciones. Por ejemplo, si quieres preguntar por el estado actual de la interfaz gráfica has de hacer la validación en la acción.
En nuestro caso si el usuario pulsa en CREAR FACTURA cuando está creando un nuevo pedido que todavía no ha grabado, fallará. Falla porque es imposible crear una factura desde un pedido inexistente. El usuario ha de grabar el pedido primero.
Modificamos el método
execute() de
CrearFacturaDesdePedido para validar que la factura visualizada actualmente esté grabada:
public void execute() throws Exception {
// Añade la siguiente condición
if (getView().getValue("oid") == null) {
// Si el oid es nulo el pedido actual no se ha grabado todavía (1)
addError("imposible_crear_factura_pedido_no_existe");
return;
}
...
}
La validación consiste en verificar que el
oid es nulo (1), en cuyo caso el usuario está introduciendo un pedido nuevo, pero todavía no lo ha grabado. En este caso se muestra un mensaje y se aborta la creación de la factura.
Aquí también tenemos un mensaje para añadir al archivo i18n. Edita el archivo
facturacion-messages_es.properties de la carpeta
src/main/resources/i18n añadiendo la siguiente entrada:
imposible_crear_factura_pedido_no_existe=Imposible crea factura: El pedido no existe todavía
Las validaciones le dicen al usuario que ha hecho algo mal. Esto es necesario, por supuesto, pero es mejor aún crear una aplicación que ayude al usuario a evitar hacer las cosas mal. Veamos una forma de hacerlo en la siguiente sección.
Evento OnChange para ocultar/mostrar una acción por código
Nuestro actual código es suficientemente robusto como para prevenir que equivocaciones del usuario estropeen los datos. Vamos a ir un paso más allá, impidiendo que el usuario se equivoque. Ocultaremos la acción para crear una nueva factura cuando el pedido no esté listo para ello.
OpenXava permite ocultar y mostrar acciones automáticamente. También permite ejecutar una acción cuando cierta propiedad sea cambiada por el usuario en la interfaz de usuario. Con estos dos ingredientes podemos mostrar el botón sólo cuando la acción esté lista para ser usada.
Recuerda que una factura puede ser generada desde un pedido si el pedido ha sido entregado y no tiene factura todavía. Por tanto, tenemos que vigilar los cambios en la referencia
factura y la propiedad
entregado de la entidad
Pedido. Lo primero será crear la acción que oculta o muestra la acción para crear una factura desde un pedido,
MostrarOcultarCrearFactura, con este código:
package com.tuempresa.facturacion.acciones; // En el paquete 'acciones'
import org.openxava.actions.*; // Necesario para usar OnChangePropertyAction,
public class MostrarOcultarCrearFactura
extends OnChangePropertyBaseAction { // Necesario para las acciones @OnChange (1)
public void execute() throws Exception {
if (estaPedidoCreado() && estaEntregado() && !tieneFactura()) { // (2)
addActions("Pedido.crearFactura");
}
else {
removeActions("Pedido.crearFactura");
}
}
private boolean estaPedidoCreado() {
return getView().getValue("oid") != null; // Leemos el valor de la vista
}
private boolean estaEntregado() {
Boolean entregado = (Boolean)
getView().getValue("entregado"); // Leemos el valor de la vista
return entregado == null?false:entregado;
}
private boolean tieneFactura() {
return getView().getValue("factura.oid") != null; // Leemos el valor de la vista
}
}
Después anotamos
factura y
entregado en
Pedido con
@OnChange para que cuando el usuario cambie el valor de
entregado o
factura en la pantalla, la acción
MostrarOcultarCrearFactura se ejecute:
public class Pedido extends DocumentoComercial {
...
@OnChange(MostrarOcultarCrearFactura.class) // Añade esto
Factura factura;
...
@OnChange(MostrarOcultarCrearFactura.class) // Añade esto
boolean entregado;
...
}
MostrarOcultarCrearFactura es una acción convencional con un método
execute(), aunque extiende de
OnChangePropertyBaseAction (1). Todas las acciones anotadas con
@OnChange tienen que implementar
IOnChangePropertyAction, aunque es más fácil extender de
OnChangePropertyBaseAction la cual lo implementa. Desde esta acción puedes usar
getNewValue() y
getChangedProperty(), aunque en este caso concreto no los necesitamos.
El método
execute() pregunta si el pedido visualizado está grabado, entregado y todavía no tiene una factura (2), en cuyo caso muestra la acción con
addActions("Pedido.crearFactura"), en caso contrario oculta la acción con
removeActions("Pedido.crearFactura"). Así, ocultamos o mostramos la acción
Pedido.crearFactura, mostrándola solo cuando proceda. Los métodos
add/removeActions() permiten especificar varias acciones a mostrar u ocultar separadas por comas.
Ahora cuando marcas o desmarcas la casilla
entregado o escoges una factura, el botón para la acción se muestra u oculta. También, cuando el usuario pulsa en
Nuevo para crear un nuevo pedido el botón para crear la factura se oculta. Sin embargo, al editar un pedido ya existente, el botón estará siempre presente, aunque el pedido no cumpla los requisitos. Esto es porque cuando un objeto se busca y visualiza las acciones
@OnChange no se ejecutan por defecto. Podemos cambiar esto con una pequeña modificación en
BuscarExcluyendoEliminados:
public class BuscarExcluyendoEliminados
// extends SearchByViewKeyAction {
extends SearchExecutingOnChangeAction { // Usa ésta como clase base
La acción de búsqueda por defecto, es decir,
SearchByViewKeyAction no ejecuta las acciones
@OnChange por defecto, por tanto cambiamos nuestra acción de buscar para que extienda de
SearchExecutingOnChangeAction.
SearchExecutingOnChangeAction se comporta exactamente igual que
SearchByViewKeyAction pero ejecutando los eventos
OnChange. De esta forma cuando el usuario escoge un pedido la acción
MostrarOcultarCrearFactura se ejecuta.
Nos queda un pequeño detalle para que todo esto sea perfecto: cuando el usuario pulsa en CREAR FACTURA después de que la factura se haya creado el botón se tiene que ocultar. El usuario no puede crear la factura otra vez. Podemos implementar esta funcionalidad con un ligero refinamiento de
CrearFacturaDesdePedido, así:
public void execute() throws Exception {
...
// Todo ha ido bien, por tanto ocultamos la acción
removeActions("Pedido.crearFactura");
}
Como puedes ver simplemente añadimos
removeActions("Pedido.crearFactura") al final del método
execute().
Mostrar y ocultar acciones no es un sustituto para la validación en el modelo. Las validaciones siguen siendo necesarias porque las entidades pueden ser usadas desde cualquier otra parte de la aplicación, no solo de los módulos de mantenimiento. Sin embargo, el truco de ocultar y mostrar acciones mejora la experiencia del usuario.
Lógica de negocio desde el modo lista
En la lección anterior aprendiste como crear acciones de lista. Las acciones de lista son una herramienta utilísima para dar al usuario la posibilidad de aplicar lógica a varios objetos a la vez. En nuestro caso, podemos añadir una acción en el modo lista para crear una nueva factura automáticamente a partir de varios pedidos seleccionados en la lista, de esta manera:
Aquí se muestra como esta acción de lista coge los pedidos seleccionados y crea una factura a partir de ellos. Simplemente copia los datos del pedido en la nueva factura, añadiendo las línea de detalle de todos los pedidos en una única factura. También se muestra un mensaje. Veamos como codificar este comportamiento.
Acción de lista con lógica propia
Como ya sabes, el primer paso para tener una acción propia en tu módulo es añadirla a un controlador. Por tanto, editemos
controladores.xml añadiendo una nueva acción al controlador
Pedido:
<controlador nombre="Pedido">
...
<!-- La nueva acción -->
<accion nombre="crearFacturaDesdePedidosSeleccionados"
modo="list"
clase="com.tuempresa.facturacion.acciones.CrearFacturaDesdePedidosSeleccionados"/>
<!-- modo="list": Sólo se muestra en modo lista -->
</controlador>
Solo con esto ya tienes una nueva acción disponible para
Pedido en modo lista.
Ahora hemos de escribir el código Java para la acción:
package com.tuempresa.facturacion.acciones;
import java.util.*;
import javax.ejb.*;
import org.openxava.actions.*;
import org.openxava.model.*;
import com.tuempresa.facturacion.modelo.*;
public class CrearFacturaDesdePedidosSeleccionados
extends TabBaseAction { // Tipico de acciones de lista. Permite usar getTab() (1)
public void execute() throws Exception {
Collection<Pedido> pedidos = getPedidosSeleccionados(); // (2)
Factura factura = Factura.crearDesdePedidos(pedidos); // (3)
addMessage("factura_creada_desde_pedidos", factura, pedidos); // (4)
}
private Collection<Pedido> getPedidosSeleccionados() // (5)
throws FinderException
{
Collection<Pedido> pedidos = new ArrayList<>();
for (Map key: getTab().getSelectedKeys()) { // (6)
Pedido pedido = (Pedido) MapFacade.findEntity("Pedido", key); // (7)
pedidos.add(pedido);
}
return pedidos;
}
}
Realmente sencillo. Obtenemos la lista de los pedidos marcados en la lista (2), llamamos al método estático
crearDesdePedidos() (3) de
Factura y mostramos un mensaje (4). En este caso también ponemos la lógica real en la clase del modelo, no en la acción. Dado que la lógica aplica a varios pedidos y crea una nueva factura, el lugar natural para ponerlo es en un método estático de la clase
Factura.
El método
getPedidosSeleccionados() (5) devuelve una colección con las entidades
Pedido marcadas por el usuario en la lista. Para hacerlo, el método usa
getTab() (6), disponible en
TabBaseAction (1), que devuelve un objeto
org.openxava.tab.Tab. El objeto
Tab te permite manejar los datos tabulares de la lista. En este caso usamos
getSelectedKeys() (6) que devuelve una colección con las claves de las filas seleccionadas. Dado que esas claves están en formato
Map usamos
MapFacade.findEntity() (7) para convertirlas en entidades
Pedido.
Acuérdate de añadir el texto del mensaje al fichero
facturacion-messages_es.properties en la carpeta
src/main/resources/i18n:
factura_creada_desde_pedidos=Factura {0} creada a partir de los pedidos: {1}
Eso es todo para la acción. Veamos la pieza que falta, el método
crearDesdePedidos() de la entidad
Factura.
Lógica de negocio en el modelo sobre varias entidades
La lógica de negocio para crear una nueva
Factura a partir de varias entidades
Pedido está en la capa del modelo, es decir, en las entidades, no en la acción. No podemos poner el método en la clase
Pedido, porque el proceso se hace a partir de varios pedidos, no de uno. No podemos usar un método de instancia en
Factura porque todavía no existe el objeto
Factura, de hecho lo que queremos es crearlo. Por lo tanto, vamos a crear un método de factoría estático en la clase
Factura para crear una nueva
Factura a partir de varios pedidos. Puedes ver este método aquí:
public class Factura extends DocumentoComercial {
...
public static Factura crearDesdePedidos(Collection<Pedido> pedidos)
throws CrearFacturaException
{
Factura factura = null;
for (Pedido pedido: pedidos) {
if (factura == null) { // El primero pedido
pedido.crearFactura(); // Reutilizamos la lógica para crear una
// factura desde un pedido
factura = pedido.getFactura(); // y usamos la factura creada
}
else { // Para el resto de los pedidos la factura ya está creada
pedido.setFactura(factura); // Asigna la factura
pedido.copiarDetallesAFactura(); // Un método de Pedido para copiar las lineas
}
}
if (factura == null) { // Si no hay pedidos
throw new CrearFacturaException("pedidos_no_especificados");
}
return factura;
}
}
Usamos el primer
Pedido para crear una nueva
Factura usando el método ya existente
crearFactura() de
Pedido. Entonces llamamos a
copiarDetallesAFactura() de
Pedido para copiar las líneas de los pedidos restantes a la nueva
Factura acumulando en ella el
iva e
importeTotal de los pedidos. Además, asignamos la nueva
Factura como la factura de los pedidos de la colección.
Si
factura es nulo al final del proceso, es porque la colección
pedidos está vacía. En este caso lanzamos una
CrearFacturaException, ya que la acción no atrapa las excepciones, OpenXava muestra el mensaje de la excepción al usuario. Esto está bien. Si el usuario no marca los pedido y pulsa en el botón para crear la factura, le aparecerá ese mensaje de error.