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
MensajesFacturacion_es.properties de la carpeta
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.