cliente_pedido_factura_coincidir=El cliente de la factura y del pedido han de coincidir
Aquí comprobamos que el cliente de la factura es el mismo que el del pedido. Esto es suficiente para preservar la integridad de los datos, pero la validación sola es una opción bastante pobre desde el punto de vista del usuario.
Refinar la acción para buscar una referencia con una lista
Aunque la validación impide que el usuario pueda asignar una factura incorrecta a un pedido, lo tiene difícil a la hora de escoger una factura correcta. Porque cuando pulsa para buscar una factura, todas las facturas existentes se muestran. Vamos a mejorar esto para mostrar solo las facturas del cliente del pedido visualizado, de esta manera:
Para definir nuestra propia acción de búsqueda para la referencia a factura usaremos la anotación
@SearchAction. Aquí tienes la modificación necesaria en la clase
Pedido:
public class Pedido extends DocumentoComercial {
@ManyToOne
@ReferenceView("SinClienteNiPedidos")
@OnChange(MostrarOcultarCrearFactura.class)
@SearchAction("Pedido.buscarFactura") // Define nuestra acción para buscar facturas
Factura factura;
...
}
De esta forma tan simple definimos la acción a ejecutar cuando el usuario pulsa en el botón de la linterna para buscar una factura. El argumento usado para
@SearchAction,
Pedido.buscarFactura, es el nombre calificado de la acción, es decir la acción
buscarFactura del controlador
Pedido definido en el archivo
controladores.xml.
Ahora tenemos que editar
controladores.xml y añadir la definición de nuestra nueva acción:
<controlador nombre="Pedido">
...
<accion nombre="buscarFactura"
clase="com.tuempresa.facturacion.acciones.BuscarFacturaDesdePedido"
oculta="true" icono="magnify"/>
<!--
oculta="true" : Para que no se muestre en la barra de botones del módulo
icono="magnify" : La misma imagen que la de la acción estándar
-->
</controlador>
Nuestra acción hereda de
ReferenceSearchAction como se muestra en el siguiente código:
package com.tuempresa.facturacion.acciones; // En el paquete 'acciones'
import org.openxava.actions.*; // Para usar ReferenceSearchAction
public class BuscarFacturaDesdePedido
extends ReferenceSearchAction { // Lógica estándar para buscar una referencia
public void execute() throws Exception {
int numeroCliente =
getView().getValueInt("cliente.numero"); // Lee de la vista el número
// de cliente del pedido actual
super.execute(); // Ejecuta la lógica estándar, la cual muestra un diálogo
if (numeroCliente > 0) { // Si hay cliente los usamos para filtrar
getTab().setBaseCondition("${cliente.numero} = " + numeroCliente);
}
}
}
Observa como usamos
getTab().setBaseCondition() para establecer una condición en la lista para escoger la referencia. Es decir, desde una
ReferenceSearchAction puedes usar
getTab() para manipular la forma en que se comporta la lista.
Si no hay cliente no añadimos ninguna condición por tanto se mostrarían todas las facturas, esto ocurre cuando el usuario escoge la factura antes que el cliente.
Buscar la referencia tecleando en los campos
La lista para escoger una referencia ya funciona bien. Sin embargo, queremos dar al usuario la opción de escoger una factura sin usar la lista, simplemente tecleando el año y el número. Muy útil si el usuario conoce de antemano que factura quiere.
OpenXava provee esa funcionalidad por defecto. Si los campos
@Id son visualizados en la referencia serán usados para buscar, en caso contrario OpenXava usa el primer campo visualizado para buscar. Aunque en nuestro caso esto no es tan conveniente, porque el primer campo visualizado es el año, y buscar una factura sólo por el año no es muy preciso. La siguiente imagen muestra el comportamiento por defecto junto con una alternativa más conveniente:
Afortunadamente es fácil indicar que campos queremos usar para buscar desde la perspectiva del usuario. Esto se hace por medio de la anotación
@SearchKey. Edita la clase
DocumentoComercial (recuerda, el padre de
Pedido y
Factura) y añade esta anotación a las propiedades
anyo y
numero:
abstract public class DocumentoComercial extends Eliminable {
@SearchKey // Añade esta anotación aquí
@Column(length=4)
@DefaultValueCalculator(CurrentYearCalculator.class)
int anyo;
@SearchKey // Añade esta anotación aquí
@Column(length=6)
@ReadOnly
int numero;
...
}
De esta forma cuando el usuario busque un pedido o una factura desde una referencia tiene que teclear el año y el número, y la entidad correspondiente será recuperada de la base de datos y rellenará la interfaz de usuario. Ahora es fácil para el usuario escoger una factura desde un pedido sin usar la lista de búsqueda, simplemente tecleando el año y el número.
Refinar la acción para buscar cuando se teclea la clave
Ahora que obtener una factura tecleando el año y el número funciona queremos refinarlo para ayudar al usuario a hacer su trabajo de forma más eficiente. Por ejemplo, sería útil que si el usuario todavía no ha escogido al cliente para el pedido y escoge una factura, el cliente de esa factura sea asignado automáticamente al pedido actual. La siguiente imagen visualiza el comportamiento deseado:
Por otra parte, si el usuario ya ha seleccionado un cliente para el pedido, si no coincide con el de la factura, ésta será rechazada y se visualizará un mensaje de error, tal como se muestra aquí:
Para definir este comportamiento especial hemos de añadir una anotación en la referencia
factura de
Pedido.
@OnChangeSearch permite definir nuestra propia acción para hacer la búsqueda de la referencia cuando su clave cambia en la interfaz de usuario. Puedes ver la referencia modificada:
public class Pedido extends DocumentoComercial {
@ManyToOne
@ReferenceView("SinClienteNiPedidos")
@OnChange(MostrarOcultarCrearFactura.class)
@OnChangeSearch(BuscarAlCambiarFactura.class) // Añade esta anotación
@SearchAction("Pedido.buscarFactura")
Factura factura;
...
}
A partir de ahora cuando un usuario teclee un nuevo año y número para la factura,
BuscarAlCambiarFactura se ejecutará. En esta acción se han de leer los datos de la factura de la base de datos y actualizar la interfaz de usuario. A continuación el código de la acción:
package com.tuempresa.facturacion.acciones; // En el paquete 'acciones'
import java.util.*;
import org.openxava.actions.*; // Para usar OnChangeSearchAction
import org.openxava.model.*;
import org.openxava.view.*;
import com.tuempresa.facturacion.modelo.*;
public class BuscarAlCambiarFactura
extends OnChangeSearchAction { // Lógica estándar para buscar una referencia cuando
// los valores clave cambian en la interfaz de usuario (1)
public void execute() throws Exception {
super.execute(); // Ejecuta la lógica estándar (2)
Map clave = getView() // getView() aquí es la de la referencia, no la principal(3)
.getKeyValuesWithValue();
if (clave.isEmpty()) return; // Si la clave está vacía no se ejecuta más lógica
Factura factura = (Factura) // Buscamos la factura usando la clave tecleada (4)
MapFacade.findEntity(getView().getModelName(), clave);
View vistaCliente = getView().getRoot().getSubview("cliente"); // (5)
int numeroCliente = vistaCliente.getValueInt("numero");
if (numeroCliente == 0) { // Si no hay cliente lo llenamos (6)
vistaCliente.setValue("numero", factura.getCliente().getNumero());
vistaCliente.refresh();
}
else { // Si ya hay un cliente verificamos que coincida con el cliente de la factura (7)
if (numeroCliente != factura.getCliente().getNumero()) {
addError("cliente_factura_no_coincide",
factura.getCliente().getNumero(), factura, numeroCliente);
getView().clear();
}
}
}
}
Dado que la acción desciende de
OnChangeSearchAction (1) y usamos
super.execute() (2) se comporta de la forma estándar, es decir, cuando el usuario teclea el año y el número los datos de la factura se recuperan y rellenan la interfaz de usuario. Después, usamos
getView() (3) para obtener la clave de la factura visualizada y así encontrar su correspondiente entidad usando
MapFacade (4). Desde dentro de una
OnChangeSearchAction getView() devuelve la subvista de la referencia, y no la vista global. Por lo tanto, en este caso
getView() es la vista de la referencia a factura. Esto permite crear acciones
@OnChangeSearch más reutilizables. Has de escribir
getView().getRoot().getSubview("cliente") (5) para acceder a la vista del cliente.
Para implementar el comportamiento visualizado en la imagen anterior, la acción pregunta si no hay cliente (
numeroCliente == 0) (6). Si éste es el caso rellena los datos del cliente desde el cliente de la factura. En caso contrario implementa la lógica de la imagen de arriba verificando que el cliente del pedido actual coincide con el cliente de la factura recuperada.
Nos queda un pequeño detalle, el texto del mensaje. Añade la siguiente entrada al archivo
facturacion-messages_es.properties de la carpeta
src/main/resources/i18n:
cliente_factura_no_coincide=Cliente Nº {0} de la factura {1} no coincide con el cliente Nº {2} del pedido actual
Una cosa interesante de
@OnChangeSearch es que también se ejecuta si la factura se escoge desde la lista, porque en este caso el año y el número también cambian. Por ende, este es un lugar centralizado donde refinar la lógica para recuperar la referencia y rellenar la vista.
Refinar el comportamiento de las colecciones
Podemos refinar las colecciones de la misma forma que hemos hecho con las referencias. Esto es muy útil, porque nos permite mejorar el comportamiento actual del módulo
Factura. El usuario sólo puede añadir un pedido a una factura si la factura y el pedido pertenecen al mismo cliente. Además, el pedido tiene que estar entregado y no tener todavía factura.
Refinar la lista para añadir elementos a la colección
Actualmente cuando el usuario trata de añadir pedidos a la factura todos los pedidos están disponibles. Vamos a mejorar esto para mostrar solo los pedidos del cliente de la factura, entregados y todavía sin factura, tal como se muestra:
Usaremos la anotación
@AddAction para definir nuestra propia acción que muestre la lista para añadir pedidos. El siguiente código muestra la modificación necesaria en la clase
Factura:
public class Factura extends DocumentoComercial {
@OneToMany(mappedBy="factura")
@CollectionView("SinClienteNiFactura")
@AddAction("Factura.anyadirPedidos") // Define nuestra propia acción para añadir pedidos
Collection<Pedido> pedidos;
...
}
De esta forma tan sencilla definimos la acción a ejecutar cuando el usuario pulsa en el botón para añadir pedidos. El argumento usado para
@AddAction,
Factura.anyadirPedidos, es el nombre calificado de la acción, es decir la acción
añadirPedidos del controlador
Factura tal como se ha definido en el archivo
controladores.xml.
Ahora hemos de editar
controladores.xml para añadir el controlador
Factura (todavía no existe) con nuestra acción:
<controlador nombre="Factura">
<hereda-de controlador="Facturacion"/>
<accion nombre="anyadirPedidos"
clase="com.tuempresa.facturacion.acciones.IrAnyadirPedidosAFactura"
oculta="true" icono="table-row-plus-after"/>
<!--
oculta="true" : No se mostrará en la barra de botones del módulo
icono="table-row-plus-after" : La misma imagen que la acción estándar
-->
</controlador>
Este es el código de la acción:
package com.tuempresa.facturacion.acciones; // En el paquete 'acciones'
import org.openxava.actions.*; // Para usar GoAddElementsToCollectionAction
public class IrAnyadirPedidosAFactura
extends GoAddElementsToCollectionAction { // Lógica estándar para ir a la lista que
// permite añadir elementos a la colección
public void execute() throws Exception {
super.execute(); // Ejecuta la lógica estándar, la cual muestra un diálogo
int numeroCliente =
getPreviousView() // getPreviousView() es la vista principal (estamos en un diálogo)
.getValueInt("cliente.numero"); // Lee el número de cliente de la
// factura actual de la vista
getTab().setBaseCondition( // La condición de la lista de pedidos a añadir
"${cliente.numero} = " + numeroCliente +
" and ${entregado} = true and ${factura} is null"
);
}
}
Fíjate como usamos
getTab().setBaseCondition() para establecer la condición de la lista para escoger la entidades a añadir. Es decir, desde una
GoAddElementsToCollectionAction puedes usar
getTab() para manipular la forma en que la lista se comporta.
Refinar la acción que añade elementos a la colección
Una mejora interesante para la colección de pedidos sería que cuando el usuario añada pedidos a la factura actual, las líneas de detalle de estos pedidos se copien automáticamente a la factura.
No podemos usar
@AddAction para esto, porque es la acción que muestra la lista de elementos a añadir a la colección. Pero no es la acción que añade los elementos. En esta sección aprenderemos como definir la acción que realmente añade los elementos:
Por desgracia, no hay una anotación para definir directamente esta acción de añadir. Sin embargo, no es una tarea demasiado difícil, solo hemos de refinar la acción
@AddAction instruyéndola para mostrar nuestro propio controlador y en este controlador podemos poner las acciones que queramos. Dado que ya hemos definido nuestra
@AddAction en la sección anterior solo hemos de añadir un nuevo método a la ya existente
IrAnyadirPedidosAFactura. Añade el siguiente método
getNextController() a tu acción:
public class IrAnyadirPedidosAFactura ... {
...
public String getNextController() { // Añadimos este método
return "AnyadirPedidosAFactura"; // El controlador con las acciones disponibles
} // en la lista de pedidos a añadir
}
Por defecto las acciones en la lista de entidades a añadir (los botones AÑADIR y CANCELAR) son del controlador estándar de OpenXava
AddToCollection. Sobrescribir
getNextController() en nuestra acción nos permite definir nuestro propio controlador en su lugar. Añade en
controladores.xml la siguiente definición para nuestro controlador propio para añadir elementos:
<controlador nombre="AnyadirPedidosAFactura">
<hereda-de controlador="AddToCollection" /> <!-- Extiende del controlador estándar -->
<!-- Sobrescribe la acción para añadir -->
<accion nombre="add"
clase="com.tuempresa.facturacion.acciones.AnyadirPedidosAFactura" />
</controlador>
De esta forma la acción para añadir pedidos a la factura será
AnyadirPedidosAFactura. Recuerda que el objetivo de nuestra acción es añadir los pedidos a la factura de la manera convencional, pero también copiar las líneas de estos pedidos a la factura. Este es el código de la acción:
package com.tuempresa.facturacion.acciones; // En el paquete 'acciones'
import java.rmi.*;
import java.util.*;
import javax.ejb.*;
import org.openxava.actions.*; // Para usar AddElementsToCollectionAction
import org.openxava.model.*;
import org.openxava.util.*;
import org.openxava.validators.*;
import com.tuempresa.facturacion.modelo.*;
public class AnyadirPedidosAFactura
extends AddElementsToCollectionAction { // Lógica estándar para añadir
// elementos a la colección
public void execute() throws Exception {
super.execute(); // Usamos la lógica estándar "tal cual"
getView().refresh(); // Para visualizar datos frescos, incluyendo los importes
} // recalculados, que dependen de las líneas de detalle
protected void associateEntity(Map clave) // El método llamado para asociar
throws ValidationException, // cada entidad a la principal, en este caso para
XavaException, ObjectNotFoundException,// asociar cada pedido a la factura
FinderException, RemoteException
{
super.associateEntity(clave); // Ejecuta la lógica estándar (1)
Pedido pedido = (Pedido) MapFacade.findEntity("Pedido", clave); // (2)
pedido.copiarDetallesAFactura(); // Delega el trabajo principal en la entidad (3)
}
}
Sobrescribimos el método
execute() sólo para refrescar la vista después del proceso. Realmente, lo que nosotros queremos es refinar la lógica de asociar un pedido a la factura. La forma de hacer esto es sobrescribiendo el método
associateEntity(). La lógica aquí es simple, después de ejecutar la lógica estándar (1) buscamos la entidad
Pedido correspondiente y entonces llamamos al método
copiarDetallesAFactura() de ese
Pedido. Por suerte ya teníamos un método para copiar detalles desde una entidad
Pedido a la
Factura especificada, simplemente llamamos a este método.