DUSA - Sistema de Manejo y Control de Repartos
Documentación Técnica Versión 1.7
Historia de revisiones
Fecha Versión Descripción Autor
11/11/2015 1.0 Creación del documento Mauro Picó
12/11/2015 1.1 Modificaciones Rodrigo Eizmendi
14/11/2015 1.2 Modificaciones Matias Lugli 14/11/2015 1.2 Capa de persistencia Helen Olhausen 15/11/2015 1.3 Agregado de descripción de
operaciones.
Camila Moscatelli
15/11/2015 1.4 Agregado de descripción de operaciones.
Camila Rosso
15/11/2015 1.5 Agregado de descripción de operaciones.
Santiago Noguera
15/11/2015 1.6 Continuación capa de persistencia
Helen Olhausen
21/11/2015 1.7 Se revisa toda el documento buscando y arreglando errores.
Mauro Picó
Índice
Introducción………
……… 3 1.0-
Persistencia………
……… 5 2.0-
WebServices………
……… 14 3.0-
AsyncTasks………
……… 20 4.0- Business
………
……… 23 5.0- DUSA
………
……… 27
INTRODUCCIÓN
La idea de este documento es dar un marco de las clases y métodos más relevantes para facilitar el entendimiento y potencial modificación de la implementación realizada.
El proyecto fue realizado utilizando el IDE Android Studio 1.3.2, utilizando el jdk1.7.0_65 de Java.
El proyecto contiene la siguiente estructura, de la cual destacaremos los paquetes más relevantes para detallarlos en este documento. El grado de detalle con los que explicaremos cada componente, dependerá de la criticidad y complejidad de los mismos.
El esquema general de la distribución de la aplicación se muestra en la siguiente imagen.
De los recursos listados detallaremos en particular los siguientes paquetes: asyncTasks, Bussiness, dusa, Persistence, webservices.
asyncTasks: Contienen las llamadas asíncronas a procesos que utilicen conexiones remotas, debido a que Android no permite la ejecución de métodos que utilicen conexiones con internet en el main thread de la aplicación.
Business: Contiene la lógica de negocio.
Dusa: Contiene las clases Java que se asocian a los xml de UI.
Persistence: Contiene los accesos a la base de datos.
Webservices: Contiene la interfaz de conexión con el servidor DUSA, así como la lógica de los web methods.
1.0 Persistencia
Para poder gestionar nuestros datos a nivel de base de datos, Android, pone a nuestra disposición la base de datos relacional SQLite así como una serie de clases mediante las cuáles podremos crear el esquema de la base de datos, como los métodos para poder seleccionar registros, insertar, eliminar o actualizar.
Una vez conocidas estas clases, la gestión del SQLite es fácil, sin embargo, encierra una gran cantidad de código "repetitivo".
GreenDao es un ORM en el que se pretende conservar la simplicidad proporcionando una interfaz orientada a objetos para el manejo de la base de datos SQLite.
Es una API sencilla, que está optimizada para Android, permitiendo agilizar tareas comunes de Base de Datos y es utilizada en importantes aplicaciones de Android.
Por lo tanto GreenDao se eligió para la gestión de base de datos de la aplicación.
Se programó por un lado el modelo de la base de datos y se indicó en qué proyecto queríamos que nos generarán las clases dao (data access objects) y las entidades (java data objects), este proyecto es el proyecto dusa.
En resumen, GreenDAO, genera clases java, una clase entity y otra clase dao, por cada tabla que tengamos en nuestra base de datos y todas las columnas de estas serán
properties, además de todos los métodos necesarios para poder ejecutar cualquier acción CRUD (Create Read Update Delete).
1.1 Implementando GreenDAO
NOTA: A continuación se explica cómo comenzar a utilizar GreenDao en una aplicación Android desde cero. El modelo se creó previamente por el equipo técnico, ya listo para su uso.
Para usar GreenDao en una aplicación Android, es necesario en primer lugar crear las entidades y los objetos dao. Para ello, es necesario crear un segundo “proyecto generador”
(proyecto Java, no Android) ,aparte del proyecto Android `dusa`, donde se generen todas las clases necesarias para nuestro modelo.
Para ésto basta con crear un módulo `DusaDaoGenerator`, que se encargará de generar las entidades, los objetos DAOs y el acceso a la Base de Datos SQLite de Android.
Por lo tanto se realizó un proyecto Java paralelo al de Android, donde se implementaron las entidades y los objetos DAO de dichas entidades y se crearon los componentes de manera automática.
El archivo mas importante de éste modulo es el DusaDaoGenerator.java el cual es el archivo generador.
De modo que la estructura de proyecto quedó de la siguiente manera, por un lado el módulo DusaDaoGenerator y por otro el módulo Android.
Para poder usar la clase generadora DusaDaoGenerator.java primero se importaron las librerías necesarias para el uso de GreenDAO.
Como se indica en el github de greenDao
https://github.com/greenrobot/greenDAO#addgreendaotoyourproject
En el archivo build.gradle de proyecto Android ‘app’ y del generador ‘DusaDaoGenerator’ se agregaron en dependencies las líneas:
Gradle dependency para Android app:
compile ('de.greenrobot:greendao:2.0.0')
Gradle dependency para el Java generator project:
compile ('de.greenrobot:greendaogenerator:2.0.0')
Después de agregar cada uno de las líneas es necesario hacer un Sync de gradle.
1.2 Detalle de DusaDaoGenerator.java
DusaDaoGenerator.java es donde se crea el esquema de la base de datos, va a ser la clase encargada de crear las clases dao y las entidades.
Por lo tanto en DusaDaoGenerator, se define un método estático main() donde se crea el esquema mediante la inicialización del objeto Schema. Este objeto va a ser el encargado de almacenar todas nuestras entidades con sus características. El constructor tiene dos
parámetros, el número de versión y el paquete. Recordemos que cada cambio que realicemos en el modelo de la base de datos, tiene que verse reflejado, en el aumento del número de versión, para que Android, sepa que tiene que realizar un upgrade de la base de datos. El paquete que indicamos es donde va a crear las entidades y los daos.
Ejemplo:
publicstaticvoid main(String args[])throwsException {
Schema schema =newSchema(1, “com.dusa.pis.srcgen.model”);
Entity chofer = addChoferTo(schema); // agregar alguna entidad
newDaoGenerator().generateAll(schema,"../app/src/main/java/");
}
Posteriormente, a este Schema se le pueden agregar entidades.
Por ejemplo de la siguiente manera
privatestaticEntity addChoferTo(Schema schema) {
Entity chofer = schema.addEntity(CHOFER_TABLE_NAME);
chofer.addIdProperty();
chofer.addStringProperty("usuario").unique();
chofer.addStringProperty("password").notNull();
return chofer;
}
Cada entidad está mapeada a una tabla de la base de datos y tiene properties, que se mapean a las columnas de la tabla.
El nombre de la tabla lo asignamos mediante el método addEntity del Schema. Después se tiene que indicar todas las properties de la entidad. Aunque no es obligatorio, pero sí es una buena costumbre, se agrega un campo id, que SQLite identifica como clave y es de tipo Long y autogenerado. Para ello se dispone del método addIdProperty().
Para cada uno de los tipos de datos que admite SQLite también tenemos sus métodos como addLongProperty(“”), addStringProperty(“”), addBooleanProperty(“”), etc. Además
podemos indicar si un campo va a ser clave unique() o no debe ser nulo notNull(). Todo ésto se puede encontra en la documentacion de greenDao http://greendaoorm.com/ .
1.3 Relaciones en greenDao
Para la creación de relaciones, tenemos que obtener la property que va a tener la foreignKey de la relación. Las relaciones se deben crear bidirecionales.
Por ejemplo un chofer tiene varios repartos y un reparto tiene un único chofer
// foreign key en reparto con idChofer
addToManyToOneRelationship(chofer, reparto,"repartos","idChofer");
privatestaticvoid addToManyToOneRelationship(Entity source,Entity destination, String relationshipName,String sourceFKName)
Es un método de utilidad que se creó en DusaDaoGenerator mediante el cual se genera una relación entre las entidades que se pasan como parámetros, siendo las relaciones entre estas las siguientes:
● `source` has Many `destination`
● `destination` has One `source`
● `sourceFKName`: es el nombre de la columna que tiene la foreignKey en la tabla
`destination`
Obteniéndose la relación, la tabla destination tiene una columna con la una foreign key de la tabla source, teniendo ahora un método de utilidad creado por greendao donde se puede llamar a source.get"relationshipName"() que nos retorna una lista de tipo `destination`.
GreenDAO no soporta directamente las relaciones N:M. Para manejar este tipo de
relaciones se debe crear una nueva entidad que represente una tabla de join, que contiene la clave de la tabla primaria con la clave de la tabla foránea. En nuestro caso se creó la tabla de join para el repartoFarmacia, en DusaDaoGenerator se creó el método de utilidad que realiza esto addJoinTableRepartoFarmacia(schema, reparto, farmacia)
Una vez que se creó toda la estructura de la base de datos se ejecuta la clase Java Para correr DusaDaoGenerator.java:
● Ir a la sección de ‘Gradle task’
● Elegir DusaDaoGenerator
● Seleccionar run.
Esto comienza a generar las clases dao y las clases entity. De esta manera todo el modelo de datos de la aplicación se crea.
Observaciones importantes:
Cuando se modifique el archivo DusaDaoGenerator.java, ya sea para agregar una nueva entidad al esquema o para agregar una propiedad a alguna de las ya existente, se debe ejecutar la task `run` dentro de Tasks de
Gradle>application>run, para que vuelva a regenerar cada una de las entidades, como se mostró arriba.
Las clases generadas por GreenDao no deberían modificarse, ésto se debe hacer desde el archivo generador `DusaDaoGenerator.java`
1.4 Uso del generador desde proyecto Android
Se creó la clase DBUtil.java que tiene metodos de útilidad donde se crea la instancia de la base de datos y se obtienen los “daos” de cada entidad.
Consultas
Una de las acciones más habituales cuando trabajamos con bases de datos son las consultas. GreenDAO tiene dos métodos para generar estas consultas SQL que cubren cualquier necesidad de consulta que se tenga.
Con queryRaw(String where, String ... selectionArg) pasaremos directamente la consulta SQL entera. No es la opción recomendada, pues vamos a tener que trabajar directamente
con código SQL que o bien hemos probado en un analizador sintáctico o podemos cometer errores. Sin embargo, nos permite cubrir opciones que no va a cubrir el método queryBuilder() como joins entre tablas.
queryBuilder nos devuelve un objeto QueryBuilder que es el que permite crear las consultas sin necesidad de utilizar sql.
Mediante el método where indicamos el campo de consulta. Añadiremos tantos métodos where como campos formen parte de nuestra consulta.
Una vez creado el modelo de la aplicación, se procedió a crear la interfaz de persistencia, con la cual la capa lógica de la aplicación se comunicará con la capa de persistencia.
1.5 Interfaz de persistencia - IPersistance
La interfaz de persistencia IPersistance.java provee todos los métodos especificados en la arquitectura utilizando, el modelo de datos anteriormente generado por el archivo generador DusaDaoGenerator.java de GreenDao. Permitiendo así la comunicación de la capa lógica con la capa de persistencia. De esta forma la aplicación se mantiene modularizada, no acoplada y escalable.
El modelo de la base datos se puede ver en el documento de arquitectura Documento de modelo de diseño v1.4.
A continuación se brinda una documentación de los métodos más relevantes de
IPersistance.java siendo Persistance.java la clase que brinda la implementacion de cada uno de ellos. Para la implementación de éstos, se utilizaron métodos de utilidad que proporciona greenDao para el acceso a la base de datos.
Métodos Create/Insert/Update
EstadoBusiness setEstadoBusiness(Long idReparto)
EstadoBusiness es la entidad que conserva el estado de la aplicación en
determinado momento. Este método crea, setea y retorna el estado de la capa lógica en un determinado momento que se basa únicamente en el idReparto.
Reparto crearReparto(DataProximoReparto reparto,String choferUsername)
Crea e inserta en la base de datos un reparto a partir del dataType
DataProximoReparto y lo asigna al chofer con nombre de usuario choferUsername
RepartoFarmacia crearFarmaciaReparto(DataFciaReparto farmaciaReparto,Long idReparto, Integer posicionOrden)
Crea e inserta en la base de datos la entidad FarmaciaReparto (tabla de join anteriormente explicada para un reparto y una farmacia) utilizando el dataType
DataFciaReparto que representa la farmacia y el reparto idReparto. posicionOrden representa la posición en que se va a visitar la farmacia para el orden del reparto
Chofer crearChofer(String usuario,Stringpass)
Crea e inserta en la base de datos la entidad Chofer con el nombre de usuario usuario y la contraseña pass
Farmacia crearFarmacia(Integer nroCliente,String nombre)
Crea, inserta o actualiza la Farmacia con numero de cliente nroCliente y nombre
Notificacion crearNotificacion(Integer nroCliente,Long idReparto,String notificacionData)
Crea y asigna una notificación para la pareja repartoFarmacia con nroCliente e
idReparto y los datos notificacionData
Métodos Getters
publicDate getEstadoBusinessFechaBackup()
EstadoBusiness a su vez conserva la fecha del último backup que se hizo del archivo sqlite de la base de datos. Con éste método se obtiene dicha fecha
publicArrayList<DataFarmacia> getFarmaciasReparto(Long idReparto)
Retorna la lista de Farmacias del reparto con id idReparto, representadas por DataFarmacia (dataType que representa una farmacia)
publicList<Reparto> getRepartosChofer(String user)
Retorna la lista de repartos del chofer con username user
List<String> getCodigosNoEntregados(Integer nroCliente,Long idReparto)
Retorna la List<String> de código de barras que no se escanearon en la visita a una determinada farmacia con número de cliente nroCliente para el reparto con idReparto.
Retorna las notificaciones que tienen 'codBarraNoEntregado:'
Integer getProximaFarmacia(Long idReparto)
Retorna el número de cliente Integer dela próxima farmacia a visitar para el reparto con idReparto de la ruta establecida
String getCodigoBarrasReparto(Long idReparto)
Retorna el código de barras que identifica al reparto con idReparto
List<DataNotificacionesPendientesFarmacia>
getNotificacionesPendientes(Long idReparto)
Retorna las notificaciones pendientes que quedan para enviar del reparto con
idReparto. Éste método se utiliza en caso de pérdida de conexión para el envío posterior de datos.
List<DataOrden> getOrden(Long idReparto)
Retorna el orden de farmacias a visitar para el reparto con idReparto.
List<DataSitioVisitadoPendiente> getSitiosVisitadosPendientes(Long idReparto)
Retorna la lista de sitios que se visitaron para el reparto con id idReparto pero por perdida de conexión, no se pudo enviar al servidor los datos.
String getEstadoRepartoActual(Long idReparto)
Retorna estado del reparto con id idReparto del enumerado EnumEstado { ABIERTO, CERRADO, INICIADO, TERMINADO, COMPLETO, INGRESANDOMONTO }
publicboolean getPendiente(Long idReparto)
Retorna true si al menos una de las siguientes variables en el reparto con idReparto son true
1-isNotifiacionesPendiente 2-isSitioVisitadoPendiente 3-isOrdenPendiente
Otros métodos de utilidad
boolean isFarmaciaVisitada(Long idReparto,Integer nroCliente)
Retorna true si la farmacia con nroCliente para el reparto con idReparto ya fue visitada.
Get de la variable is Visitada en la tabla orden para la faramacia nroCliente y el reparto con id idReparto
Boolean isRepartoTerminado()
Retorna true si el reparto con idReparto que se encuentra apuntado por el EstadoBusiness es un reparto con estado “COMPLETO”
Boolean isUserLoggedIn()
Retorna true si existe un chofer en la base de datos, este chofer en todo momento es unico, y es el usuario logueado
void cerrarSesion()
Hace un delete del chofer que se encuentra como único usuario de la aplicación
Ejemplos
A continuación brindamos un ejemplo de cómo acceder, obtener un atributo, y modificar un elemento (en este caso un reparto) de nuestra base de datos a partir del identificador
"idReparto" utilizando métodos de GreenDao:
1 Obtenemos el objeto reparto.
Reparto reparto =DBUtil.getRepartoDao().queryBuilder()
.where(RepartoDao.Properties.Id.eq(idReparto)).unique();
2 Obtenemos una propiedad ( columna ) del objeto reparto, en este caso el monto total cobrado.
reparto.getMontoTotalCobrado();
3 Modificamos dicha propiedad.
reparto.setMontoTotalCobrado(monto);
reparto.update();
Con esto resumimos la documentación de la capa de persistencia de la aplicación. Se recomienda siempre tomar como referencia la documentación de greenDao
http://greendaoorm.com/, y la documentación del código de la aplicación.
2.0 Paquete Webservices:
Conceptos Generales
Los servicios fueron realizados utilizando la librería “retrofit” asistido mediante las librerías de “okHttp” para la configuración de los parámetros de conexión, “gson” de Google para el manejo de los JSON y “GlassFish” para las notaciones correspondientes.
La siguiente imágen muestra las dependencias en el build.gradle de la app.
El paquete de servicios está dividido en los siguientes subpaquetes de acuerdo a la imagen:
El paquete de “RemoteTypes” contiene aquellas clases “POJO” (Plain Old Java Object), estos son los objetos que con el uso de Retrofit y gson, son populadas mediante la traza de las respuestas JSON de los servicios.
Así, el paquete “ClasesServices” contiene clases que serán devueltas a la capa lógica conteniendo la información de llamado a servicios. Estas clases se crean y se construyen utilizando los datos de las clases descritas en el párrafo anterior, “RemoteTypes”. Si bien, en algunos casos el contenido y estructura de las clases pertenecientes a “RemoteTypes” y
“ClasesServices” es prácticamente idéntico, esto se realiza para mantener aún más la brecha de separación entre la interfaz de conexión y la lógica.
En particular, la clase que es llamada desde la lógica es la clase “WebServices” (singleton) que implementa los métodos de su interfaz “IWebServices”. La clase “Conector”, contiene la interfaz de conexión. Es decir, en última instancia, cuando se consulte un servicio se hará mediante la clase “Conector”, es en esta, donde se definen los métodos, atributos y las propiedades, generales de los métodos (como puede ser “GET” o “POST”) o particulares de los atributos de los métodos (como puede ser si son parámetros “QUERY”).
La siguiente imagen muestra parte de la definición de esta clase:
@GET("/estadoReparto")
ResultGetEstadoReparto estadoReparto(@Query("fecha") String fecha,@Query("reparto") int reparto,@Query("nroSalida") int nroSalida);
@POST("/iniciarReparto")
MensajeError iniciarReparto(@Query("fecha") String fecha, @Query("reparto") int reparto,@Query("nroSalida") int nroSalida, @Body Object dummuy);
@GET y @POST determina el tipo request, @Query determina el tipo de parámetro,
@Body es el body requerido por POST.
Dentro de la clase “WebServices” se mantiene, luego de iniciada la sesión, un objeto de la clase “Conector”. Este conector contiene las propiedades de conexión (así como la URL y el tipo de autenticación), este conector es creado por un método, llamado CreateService
Los métodos definidos en la clase “IWebService” son trazados uno a uno con los métodos del conector, esto para simplificar la distribución de la lógica.
El atributo de la clase “WebService”, llamado BASE_URL define la url base a la cual se realizará la conexión. Por otro lado, el atributo apiServicios mantendrá la conexión realizada el “Conector” creado que se describió en los párrafos anteriores. Éste, será utilizado por todos los restantes métodos para llamar a los métodos remotos.
Dentro de cada método definido en la clase “WebServices”, además de realizar las
consultas a los respectivos métodos remotos (mediante el conector), se realiza la gestión de los posibles errores. Sobre esto, atendemos particularmente los errores que pueden ser ocurridos durante el proceso de inicio de sesión en el método iniciarSesion, debiendo distinguir principalmente aquellos errores ocasionados por problemas de autenticación. Esto se realiza atendiendo al catch de la excepción, verificando el mensaje del error, y
devolviendo los tipos de mensaje indicados a la lógica.
Simplemente a modo de ejemplo, se deja en la siguiente porción de código, el manejo de error del método iniciarSesion.
if (e.getLocalizedMessage()!=null && e.getLocalizedMessage().equals("401 Unauthorized"))
{
return new MensajeError(1,"Nombre Usuario o password no son validos");
}
return new MensajeError(404,"No hay conectividad con los servicios");
Verifica los tiempos de mensajes, distinguiendo aquél que sea generado debido a un error de autenticación.
El manejo es similar en el resto de los métodos, atendiendo únicamente a los errores del estilo 404, debido a la no conectividad con los servicios remotos, ya sea por la pérdida de conexión a internet del dispositivo de la aplicación, o por una baja en los servicios remotos.
Aquellas respuestas, que están compuestas por varios objetos dentro del objeto de llamado a los servicios, (como es el ejemplo del método obtenerProximoReparto
que devuelve un objeto de la clase ResultGetProximoReparto que a su vez está formado por un objeto del tipo mensaje y un objeto del tipo DataProximoReparto), lo primero que se verifica es el estado de la respuesta, en caso de que el código sea distinto a
“1” (código que implica la ocurrencia de un error), se armarán los objetos de respuesta, que serán devueltos a la capa lógica. En caso contrario, se armará y enviará el mensaje de error únicamente, siendo responsabilidad de la lógica distinguir entre posibles escenarios.
Desde la clase bussines, se acceden a los métodos provistos por la clase “WebServices”, los mismos se detallan a continuación:
Create Service
Mirando más en detalle este método vemos que contiene la parametrización del timeout de la conexión, mediante las líneas:
final OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setReadTimeout(10, TimeUnit.SECONDS);
okHttpClient.setConnectTimeout(10, TimeUnit.SECONDS);
Con ese cliente http, se realiza la construcción del adaptador rest de “Retrofit”:
RestAdapter.Builder builder = new RestAdapter.Builder() .setEndpoint(BASE_URL)
.setClient(new OkClient(okHttpClient));
Para la configuración del Basic Authentication se utiliza un interceptor de retrofit de la siguiente manera:
final String credentials = user + ":" + password;
builder.setRequestInterceptor(new RequestInterceptor() { @Override
public void intercept(RequestFacade request) {
String string = "Basic " + Base64.encodeToString(credentials.getBytes(), Base64.NO_WRAP);
request.addHeader("Accept","application/json");
request.addHeader("Authorization", string);
} });
Donde se configura el string de “usuario:contraseña”, se especifica que se utilizará “Basic”
como tipo de autenticación, se concatenan las credenciales en Base 64, se agregan a la cabeceras lo anterior definido, así como el tipo de respuesta a aceptar (application/json).
Las siguientes líneas dan la construcción el conector que finalmente el método devolverá y quedará seteado en el atributo apiServicios de la clase WebServices:
RestAdapter restAdapter = builder.build();
Conector apiService =
restAdapter.create(Conector.class);
Iniciar Sesión
public MensajeError iniciarSesion(String usuario,String password)
Este método realiza la creación del conector y deja el mismo como atributo de la clase de la siguiente manera:
this.apiServicios = CreateService(usuario,password);
Siendo apiServicios un atributo del tipo Conector.
El llamado al método remoto es mediante la siguiente linea:
com.dusa.pis.webservices.RemoteTypes.MensajeError error =
this.apiServicios.iniciarSesion(usuario, password);
Esto es análogo en el resto de los métodos de la clase webservices, una vez que fue ejecutado el iniciarSesión, el resto de los métodos ya estarán en condiciones de acceder a los servicios remotos.
Obtener Próximo Reparto
public ResultGetProximoReparto obtenerProximoReparto(String usuario)
La complejidad de este método radica en la gran cantidad de atributos y clases de la clase ResultGetProximoReparto. Así, el llamado a servicios remotos se utiliza de la misma manera que el método anterior:
com.dusa.pis.webservices.RemoteTypes.ResultGetProximoReparto reparto = this.apiServicios.obtenerProximoReparto(usuario);
Obtener Datos Farmacia Reparto:
public ResultGetFciaReparto obtenerDatosFciaReparto(int nroCliente, String fecha, int reparto, int nroSalida)
Nuevamente la complejidad de este método, radica en la construcción de las estructuras de datos a ser devueltas a la capa lógica.
Carece de sentido seguir especificando las llamadas concretas a los servicios remotos, las mismas se encuentran en el código y además, son de alta similitud a las expuestas en párrafos anteriores.
Reordenar lista de farmacias no visitadas
public MensajeError reordenarListaFarmaciasNoVisitadas(String fecha, int reparto, int nroSalida, List<Integer> listaReordenada){
Este es el primer método POST que describimos, basta mirar en el código del Conector para distinguir el atributo objeto
@POST("/reordenarListaFarmaciasNoVisitadas") MensajeError reordenarListaFarmaciasNoVisitadas(
@Query("fecha") String fecha,
@Query("reparto") int reparto,
@Query("nroSalida") int nroSalida,
@Query("listaReordenada") List<Integer> listaReordenada,
@Body Object objeto );
El mismo está etiquetado con la notación “@Body”, que implica que objeto contendrá el cuerpo del llamado post. En nuestro caso, pasamos como parámetros de la llamada los datos necesarios. Así que para respetar el uso de la librería, antes de la llamada a los métodos remotos (nuevamente en el método “obtenerListaFarmaciasNoVisitadas” de la clase WebServices”), se crea un “Objeto Dummy” genérico de la siguiente manera, y el mismo se envía en el POST del método:
Object objeto = new Object();
com.dusa.pis.webservices.RemoteTypes.MensajeError mensaje = this.apiServicios.reordenarListaFarmaciasNoVisitadas(
fecha, reparto, nroSalida, listaReordenada, objeto
);
Agregar sitio visitado
public MensajeError agregarSitioVisitado(String descripcion, String fechaHora, int reparto, int nroSalida)
Este método es del tipo POST, para más información sobre el comportamiento del llamado, referirse a la especificación del método “Reordenar lista de farmacias no visitadas”
Procesar farmacia
public MensajeError procesarFarmacia(String fecha, int reparto, int nroSalida, int nroCliente,List <String> listaNotificaciones, String horaProcesada)
Este método es del tipo POST, para más información sobre el comportamiento del llamado, referirse a la especificación del método “Reordenar lista de farmacias no visitadas”
Is reparto terminado
public ResultIsRepartoTerminado isRepartoTerminado(String fecha, int reparto, int nroSalida)
En una primera instancia de implementación que utilizó este método, la misma estaba pensada para recibir como respuesta parámetros booleanos que indicaran si el reparto estaba en estado “Terminado”. A medida que se avanzó con el proyecto se vió la necesidad de cambiar el método web por uno que devolviera el estado en el cual se encuentra el reparto, lo que llevó a la modificación de este método y creación del método
“estadoReparto” en el Conector. Así mismo, el nombre IsRepartoTerminado del método de la clase “WebServices” se dejó de igual manera, debido a que ya se encontraban
implementaciones de lógica, que usaban este método.
Es por esa razón que el llamado a los servicios de este método se realiza según la siguiente linea:
com.dusa.pis.webservices.RemoteTypes.ResultGetEstadoReparto er = this.apiServicios.estadoReparto(fecha, reparto, nroSalida);
Terminar Reparto
public MensajeError terminarReparto(String fecha, int reparto, int nroSalida,BigDecimal totEfectivo)
Este método es del tipo POST, para más información sobre el comportamiento del llamado, referirse a la especificación del método “Reordenar lista de farmacias no visitadas”
Iniciar reparto
public MensajeError iniciarReparto(String fecha,int reparto, int nroSalida)
Este método es del tipo POST, para más información sobre el comportamiento del llamado, referirse a la especificación del método “Reordenar lista de farmacias no visitadas”
3.0 Paquete asyncTasks
Contiene las clases:
Las clases anteriormente listadas extienden la clase AsyncTask, la necesidad de usar estas clases está dada por el hecho de que Android no permite la ejecución de métodos que utilicen conexiones con internet en el main thread de la aplicación. Se decidió usar esta y no Threads directamente ya que facilita el trabajo y permite una mayor optimización de los recursos.
Al extender la clase es necesario sobreescribir e implementar ciertos métodos, estos son en el caso de esta aplicación: doInBackground() y onPostExecute(). Además se implementa un método constructor donde se inicializan los atributos de la clase necesarios para realizar la tarea asincrónica.
En todos los casos en el método doInBackground() se realizan las llamadas a los métodos pertenecientes al paquete Business que a su vez llaman a los métodos contenidos en el paquete Webservices donde finalmente se realiza la llamada al webservice
correspondiente.
Cuando se termina de procesar el llamado al webservice el flujo continúa en el método onPostExecute() donde se impactan los resultados en la UI mediante algún método definido en la Activity correspondiente o en el caso de no ser necesario reflejar los resultados en la UI se realizan los cambios necesarios en la lógica.
El siguiente diagrama explica el flujo completo de la clase AsyncTask:
A continuación se explica brevemente cada una de las clases y en los casos pertinentes se detallan los métodos doInBackground y onPostExecute.
3.1 ActualizarRepartoTask
doInBackground: Aquí se llama al método actualizarReparto() presente en la Business, el mismo obtiene el reparto nuevamente llamando a uno de los webservices y posteriormente hace los cambios pertinentes si hay alguno presente .
onPostExecute: En este método se analiza el mensaje obtenido de la Business al actualizar el reparto y se despliega un mensaje correspondiente en la UI.
3.2 AgregarNotificacionesTask
doInBackground: se envían las notificaciones pertenecientes a una farmacia a través del método procesarFarmacia() presente en la Business.
onPostExecute: al terminarse los procesamientos en el background se llama al método desplegarVistaCorrespondiente() de la Activity AgregarNotificaciones. En el mismo se despliega la información de la próxima farmacia en la activity InfoFarmacia o en caso de no existir otra pendiente se llama a la Activity IngresarMonto.
3.3 AgregarSitioVisitadoTask
Como indica el nombre de esta clase, se agrega un sitio visitado notificando a DUSA mediante uno de los WS. Al terminarse el procesamiento en el background se redirige a la activity RepartoCerrado.
3.3 CerrarRepartoTask
En el background se chequea que el reparto no tenga nada pendiente y luego se invocan los ws necesarios para corroborar que el reparto puede ser cerrado para posteriormente indicar por uno de los ws que el reparto está terminado.
3.4 IniciarRutaTask
Se realizan los chequeos pertinentes para asegurar que el reparto puede ser iniciado y en función de esto se despliega en la UI un mensaje adecuado.
3.5 IniciarSesionOnBackground
Esta tarea es usada cuando un usuario con una sesión iniciada en el dispositivo abre la aplicación luego de haberla cerrado. Realiza un login mediante el ws correspondiente con las credenciales del usuario almacenadas localmente en la BD del dispositivo. Luego de realizar el login redirige la activity que corresponda según el estado del reparto cuando el usuario cerró la aplicación.
3.6 IniciarSesionTask
doInBackground: Utilizando el ws de inciar sesion corrobora la password y contraseña ingresadas en la UI por el usuario.
onPostExecute: Analizando el mensaje obtenido del ws anterior muestra un mensaje de error correspondiente o en caso de éxito redirige a la activity ObtenerReparto.
3.7 NoVisitarFarmaciaTask
doInBackground: Se procesa la farmacia en cuestión y se notifica a DUSA mediante un ws.
onPostExecute: Se despliega en la UI la próxima farmacia de existir una o en caso contrario la vista de ingresar monto.
3.8 ObtenerRepartoTask
doInBackground: se obtiene mediante ws el próximo reparto del usuario logueado y también la información de cada farmacia en el reparto persistiendo los datos en la DB local. Este método se explica más detalladamente en la sección correspondiente al paquete Business.
onPostExecute: se despliega el mensaje adecuado en la UI si ocurre algún error o si el usuario no tiene un reparto asignado. En caso contrario se llama al método
showProperList() de la activity ObtenerReparto el cual desplegará la vista correspondiente dependiendo del estado del reparto.
3.9 ReordenarFarmaciasTask
Se actualiza en el background el orden en que se visitaran las farmacias notificando a DUSA mediante un ws el nuevo orden y persistiendo estos cambios en la DB local. Luego en onPostExecute se refrescan las listas de farmacias con el nuevo orden asignado.
4.0 PAQUETE BUSINESS:
En Business se implementa la lógica de los casos de uso que componen la
aplicación, con funciones utilizadas por las activities y async Tasks descritas anteriormente.
Para el acceso a tales funcionalidades desde el resto de los paquetes de la aplicación se cuenta con la interfaz IBusiness.
4.1 ObtenerReparto
Esta operación contiene la lógica respecto al caso de uso Obtener Reparto. En primer lugar obtiene la información del reparto. Si la información recibida es correcta, y el reparto está abierto o cerrado, se crea un nuevo reparto en la base con la interfaz de persistencia. Acto seguido, se pide la información de cada una de las farmacias y se almacenan estas en la base de datos. Luego se setean en la clase Business los atributos relativos al reparto, como ser, fecha, número de salida, el id del reparto en la base de datos y el id del reparto en el sistema de DUSA, entre otros.
En caso de que se pierda la conexión mientras se está obteniendo la información de las farmacias, se hace un rollback eliminando todo lo relativo al reparto para luego retornar que no hay conexión.
4.2 IniciarSesion
Recibe las credenciales del usuario, nombre y contraseña. Con estos datos, consulta la interfaz de web services para autenticarse ante el sistema de DUSA. En caso de éxito, se almacenan ambos datos en la base, y en la clase Business se guarda el nombre del
usuario.
En caso de no existir conexión a internet, no se inicia la sesión y se retorna un mensaje de error acorde. En caso de no autenticar correctamente al pretendido usuario, se retorna un mensaje de error acorde.
4.3 ActualizarReparto
En alto nivel, esta operación obtiene nuevamente el reparto de DUSA. Si la hora de último cambio es distinta de la que el sistema tiene almacenado, se procede a actualizar el reparto.
Esto implica, impactar el nuevo reparto en la base de datos, obtener la lista de órdenes del reparto anterior (el usuario pudo haber hecho modificaciones de orden, y debemos conservarlas). Luego se realiza un merge entre la lista de órdenes del reparto que existía en el sistema y la lista de farmacias a visitar obtenida en el reparto actualizado.
Para este merge, creamos una lista resultante y en primera instancia recorremos la lista ordenada que había en la base. Para cada farmacia de esta lista, se pregunta si esta sigue en la lista actualizada de farmacias a visitar. En caso de estar (la farmacia sigue entre
las farmacias que deben ser visitadas) se la agrega a la nueva lista, con el mismo número de orden que tenía.
Luego, se recorre la lista nueva de farmacias, y se pregunta si cada una de ellas está en la lista resultante. En caso de no estar, es porque es una farmacia que se agregó al reparto, por lo que se la agrega al final de la lista.
Acto seguido, se procede a persistir la lista mergeada de farmacias. Para esto recorremos la lista y para cada farmacia que esté en ella, pedimos la información a DUSA y la impactamos sobre la base de datos.
En caso de que alguno de estos pedidos de información falle, típicamente por no tener conexión, se realiza un rollback, dejando la base y los atributos de la clase business tal y como estaban antes de invocarse la operación, y se comunica el no éxito de la operación.
En caso de que la operación sea exitosa, se elimina el reparto antiguo y se retorna el éxito de la operación.
Esta operación se apoya en dos procedimientos auxiliares, uno para persistir la lista de farmacias merengadas y otra para hacer el merge entre estas listas.
Se retorna un objeto de la clase “MensajeError” que contiene el resultado de la operación, junto con un DataProximoReparto que está nulo en los casos de no éxito de la operación, y contiene los datos del reparto en caso de que el reparto haya sido actualizado.
4.4 IniciarRuta
Si el estado del reparto actual es cerrado, se procede a verificar que el usuario haya escaneado la salida en planta. El chequeo en planta se hace consultando por el estado del reparto en D.U.S.A, si éste es INICIADO, es porque el usuario ha marcado la salida en planta, de lo contrario no lo ha hecho.
Si el usuario escaneo la salida en planta, se invoca a la operación descrita anteriormente (actualizarReparto()). En caso de que no exista conexión, se retorna un mensaje de error acorde. Lo mismo sucede en caso de que no se haya escaneado en planta.
Para los casos en que no hubo problemas de conexión y se ha escaneado en planta, se retorna un mensaje informando si el reparto cambió o no, junto a la información del reparto.
4.5 procesarFarmacia
Esta función se encarga de la lógica general del caso de uso AgregarNotificaciones.
Para ésto, dado que es en este punto del flujo en el que efectivamente se da por concluida la visita a una farmacia, se persisten definitivamente en la base de datos tanto los códigos de paquetes y documentos no entregados como las notificaciones asociadas a la visita.
Para concretar lo mencionado anteriormente se utilizan dos procedimiento auxiliares, guardarCodigosNoEntregados() y guardarNotificaciones(), que son los que finalmente llaman a las funciones correspondientes de la interfaz de persistencia para que los datos sean guardados en la base.
Adicionalmente es en la operación en cuestión que se realiza el chequeo de la visita a la primer farmacia, resultando en un cambio de estado a INICIADO de ser positivo.
Para finalizar el procesamiento se envían los datos recabados de la visita al servidor de D.U.S.A a través del WS procesarFarmacia(fechaRecorrido, idR, nroSalida,
idInfoFarmacia, notificaciones, hora), de no existir conexión al momento de enviar los datos, se guardan en la base como pendiente de envió todas las notificaciones asociadas a la farmacia que se estaba intentando procesar, para su posterior envío al recuperar la conexión.
Esta función retorna la próxima farmacia según la lista ordenada por el usuario, de no existir próxima farmacia, porque la que se está procesando la última, se retorna null y se cambia el estado del reparto a INGRESANDOMONTO.
4.6 noVisitarFarmacia
Esta operación engloba la lógica del caso de uso NoVisitarFarmacia. Siendo su implementación sumamente similar a la operación procesarFarmacia con la diferenciación de que al guardar en la base los datos asociados a la no visita de una farmacia guarda como notificaciones el string “NO SE VISITO” y no persiste los códigos de paquetes y documentos no escaneados al perder sentido tal acción en este contexto.
4.7 cerrarReparto
Esta función lleva a cabo los pasos necesarios para culminar un reparto y realizar un backup del estado de la base de datos en ese momento, implementado así el caso de uso CerrarReparto y cumpliendo con el requerimiento de mantener un respaldo de los datos.
Particularmente lo primero que se ejecuta al invocarse esta operación es el llamado al procedimiento auxiliar backupBaseIfNeeded(), el cual chequea si ha pasado una semana desde el último respaldo de la base de datos y de ser así realiza uno nuevo.
Luego, si no hay datos pendientes de envío, se procede a consultar al servidor de D.U.S.A (a través de la operación provista por la interfaz de WS
isRepartoTerminado(fechaRecorrido,idR,nroSalida)) si el chofer escaneo el código de barras marcando su llegada en planta. Si en el sistema existen datos pendientes de envío la operación retorna que no hay conexión, bajo el entendido de que de haber algo pendiente se envía automáticamente al recuperar la conectividad.
Si el chofer realizó el escaneo en planta la operación retorna OK, cambia el estado del reparto a COMPLETO, e invoca a la operación