Skip to content

4. Tarefa 1: Gestão básica de recibos de vencimento

4.1. Introdução

Para aplicar o que abordámos anteriormente, propomos agora um trabalho que envolve o desenvolvimento de um cliente Android para tablets, concebido para simular cálculos de folhas de pagamento para os funcionários de uma associação.

A aplicação terá uma arquitetura cliente/servidor:

Image

  • o servidor [1] é fornecido;
  • deve criar o cliente Android [2].

4.2. A base de dados

4.2.1. Definição

Os dados estáticos necessários para criar o recibo de vencimento serão armazenados numa base de dados a que nos referiremos doravante como « » (dbpam). Esta base de dados contém as seguintes tabelas:

Tabela EMPLOYEES: contém informações sobre os vários prestadores de cuidados infantis

Estrutura:

ID
chave primária
VERSÃO
número de versão – aumenta a cada modificação da linha
SS
Número de Segurança Social do funcionário – único
NOME
Apelido do funcionário
PRIMEIRO NOME
Nome
ENDEREÇO
a morada
CIDADE
a cidade dele/dela
CÓDIGO POSTAL
o seu código postal
INDEMNITY_ID
Chave estrangeira no campo [ID] da tabela [INDEMNITES]

O seu conteúdo pode ser o seguinte:

Image

Tabela COTISATIONS: contém as percentagens necessárias para calcular as contribuições para a segurança social

Estrutura:

ID
chave primária
VERSÃO
número de versão – aumenta a cada modificação da linha
CSGRDS
Percentagem: Contribuição Social Geral + Contribuição para o Reembolso da Dívida Social
CSGD
percentagem: contribuição social geral dedutível
SECU
percentagem: segurança social, viuvez, velhice
PENSÃO
percentagem: pensão complementar + seguro de desemprego

O seu conteúdo poderia ser o seguinte:

Image

As taxas de contribuição para a segurança social são independentes do trabalhador. A tabela anterior tem apenas uma linha.

Tabela ALLOWANCES: contém os elementos utilizados para calcular o salário a pagar.
ID
chave primária
VERSÃO
número de versão – aumenta a cada modificação da linha
ÍNDICE
Índice de processamento – único
TARIFA HORÁRIA
Preço líquido em euros por uma hora de serviço de plantão
MANUTENÇÃO DIÁRIA
Subsídio diário em euros por dia de cuidados
REFEIÇÃO POR DIA
Subsídio de refeição em euros por dia de assistência
SUBSÍDIO DE FÉRIAS
Subsídio de férias pagas. Trata-se de uma percentagem aplicada ao salário base.

O seu conteúdo pode ser o seguinte:

Image

Note-se que os subsídios podem variar de um prestador de cuidados infantis para outro. Estão associados a um prestador de cuidados infantis específico através da sua categoria salarial. Por exemplo, a Sra. Marie Jouveinal, que tem a categoria salarial 2 (tabela EMPLOYEES), tem um salário por hora de 2,1 euros (tabela INDEMNITES).

4.2.2. Geração

É fornecido o script de geração da base de dados [dbpam_hibernate.sql]:

  

Crie a base de dados [dbpam_hibernate] (este é o nome da base de dados utilizada pelo servidor web/jSON) e certifique-se de que o utilizador root (sem palavra-passe) consegue aceder à mesma. Pode fazê-lo da seguinte forma:

Inicie o MySQL e, em seguida, o [PhpMyAdmin]:

 
  • [1-2]: Importe o script [dbpam_hibernate.sql] e, em seguida, execute-o;

4.2.3. Modelagem Java da base de dados

Os elementos das tabelas [EMPLOYEES], [ALLOWANCES] e [CONTRIBUTIONS] são modelados pelas seguintes classes:

[ Employee]


package pam.entities;
 
import java.io.Serializable;
 
public class Employe implements Serializable {
 
  private static final long serialVersionUID = 1L;
  private Long id;
  private int version;
  private String SS;
  private String nom;
  private String prenom;
  private String adresse;
  private String ville;
  private String codePostal;
  private int idIndemnite;
  private Indemnite indemnite;
 
  public Employe() {
  }
 
  public Employe(String SS, String nom, String prenom, String adresse, String ville, String codePostal, Indemnite indemnite) {
    ...
  }
   // getters and setters
....
}
  • linhas 8–15: estes campos correspondem às colunas da tabela [EMPLOYEES];
  • linha 16: o campo [indemniteId] corresponde à coluna [INDEMNITE_ID], que é a chave estrangeira da tabela [EMPLOYEES];
  • linha 17: o subsídio do funcionário. Este campo nem sempre é preenchido:
    • não é preenchido ao solicitar a URL [/employees],
    • mas é preenchido ao solicitar o URL [/salary];

[ Indemnite]


package pam.entities;
 
import java.io.Serializable;
 
public class Indemnite implements Serializable {
 
    private static final long serialVersionUID = 1L;
    private Long id;
    private int version;
    private int indice;
    private double baseHeure;
    private double entretienJour;
    private double repasJour;
    private double indemnitesCp;
 
    public Indemnite() {
    }
 
    public Indemnite(int indice, double baseHeure, double entretienJour, double repasJour, double indemnitesCP) {
        ...
    }
 
     // getters and setters
   ....
}
  • linhas 8-14: os campos correspondem às colunas da tabela [INDEMNITES];

[Contribuição da ]


package pam.entities;
 
import java.io.Serializable;
 
public class Cotisation implements Serializable {
 
    private static final long serialVersionUID = 1L;
    private Long id;
    private int version;
    private double csgrds;
    private double csgd;
    private double secu;
    private double retraite;
 
    public Cotisation() {
    }
 
    public Cotisation(double csgrds, double csgd, double secu, double retraite) {
        ...
    }
    // getters and setters
   ...
}
  • linhas 8-13: os campos correspondem às colunas da tabela [COTISATIONS];

4.3. Servidor Web / Instalação do JSON

4.3.1. Instalação

O binário Java para o servidor web/JSON é fornecido:

 

Para iniciar o servidor web/JSON, proceda da seguinte forma:

  • Inicie o SGBD MySQL;
  • certifique-se de que a base de dados [dbpam_hibernate] existe;
  • Abra uma janela do prompt de comando;
  • Navegue até à pasta jar;
  • digite o comando:
java -jar pam-server-01-all-1.0.jar

Isto pressupõe que o executável [java.exe] se encontra no PATH do seu computador. Se não for esse o caso, digite o caminho completo para [java.exe], por exemplo:

D:\Programs\devjava\java\jdk1.8\bin\java -jar pam-server-01-all-1.0.jar

Os registos são apresentados:

.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.1.1.RELEASE)

2014-10-22 16:45:23.347  INFO 1868 --- [           main] pam.boot.BootWeb                         : Starting BootWeb on Gportpers3 with PID 1868 (D:\Temp\14-10-22\pam\server-pam.jar started by ST in D:\Temp\14-10-22\pam)
2014-10-22 16:45:23.414  INFO 1868 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@689ab9e2: startup date [Wed Oct 22 16:45:23 CEST 2014]; root of context hierarchy
...
...
2014-10-22 16:45:31.147  INFO 1868 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
2014-10-22 16:45:31.484  INFO 1868 --- [           main] o.h.h.i.ast.ASTQueryTranslatorFactory    : HHH000397: Using ASTQueryTranslatorFactory
2014-10-22 16:45:33.564  INFO 1868 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2014-10-22 16:45:33.804  INFO 1868 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/salaire/{SS}/{ht}/{jt}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public pam.restapi.FeuilleSalaireResponse pam.restapi.PamController.getFeuilleSalaire(java.lang.String,double,int)
2014-10-22 16:45:33.805  INFO 1868 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/employes],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public pam.restapi.EmployesResponse pam.restapi.PamController.getEmployes()
2014-10-22 16:45:33.807  INFO 1868 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2014-10-22 16:45:33.807  INFO 1868 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],methods=[],params=[],headers=[],consumes=[],produces=[text/html],custom=[]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest)
2014-10-22 16:45:33.839  INFO 1868 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2014-10-22 16:45:33.839  INFO 1868 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2014-10-22 16:45:34.384  INFO 1868 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2014-10-22 16:45:34.535  INFO 1868 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080/http
2014-10-22 16:45:34.538  INFO 1868 --- [           main] pam.boot.BootWeb                         : Started BootWeb in 11.916 seconds (JVM running for 12.725)
2014-10-22 16:45:39.329  INFO 1868 --- [       Thread-2] ationConfigEmbeddedWebApplicationContext : Closing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@689ab9e2: startup date [Wed Oct 22 16:45:23 CEST 2014]; root of context hierarchy
2014-10-22 16:45:39.331  INFO 1868 --- [       Thread-2] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown
2014-10-22 16:45:39.333  INFO 1868 --- [       Thread-2] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
  • linha 16: a URL [/salary/{SS}/{ht}/{jt}] é resolvida;
  • linha 17: a URL [/employees] foi descoberta;

4.3.2. URLs de serviço Web/JSON

O serviço Web / JSON é implementado pelo Spring MVC e expõe duas URLs:


@RequestMapping(value = "/employes", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
public EmployesResponse getEmployes() {
...
@RequestMapping(value = "/salaire/{SS}/{ht}/{jt}", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
public FeuilleSalaireResponse getFeuilleSalaire(@PathVariable("SS") String SS, @PathVariable("ht") double ht, @PathVariable("jt") int jt) {

O serviço web aceita as duas URLs seguintes:

  • linha 1: /employees: para recuperar a lista de funcionários;
  • Linha 4: /salary/SS/ht/jt: para recuperar o recibo de vencimento do funcionário n.º [SS] que trabalhou [ht] horas ao longo de [jt] dias;

Aqui estão algumas capturas de ecrã que ilustram isto.

Consultamos os funcionários:

Image

Fazemos uma cópia de segurança da base de dados, reiniciamos o servidor e consultamos os funcionários:

Image

Consultamos um salário:

Image

Solicitamos o salário de uma pessoa inexistente:

Image

4.3.3. As respostas JSON do serviço web/JSON

  

Os URLs do serviço web/jSON devolvem respostas do tipo [Response<T>]:


package client.android.dao.service;
 
import java.util.List;
 
public class Response<T> {
 
    // ----------------- properties
    // operation status
    private int status;
    // any status messages
    private List<String> messages;
    // the body of the reply
    private T body;
 
    // manufacturers
    public Response() {
 
    }
 
    public Response(int status, List<String> messages, T body) {
        this.status = status;
        this.messages = messages;
        this.body = body;
    }
 
    // getters and setters
...
}
  • A URL [/employees] devolve uma Response<List<Employee>>;
  • A URL [/salary] devolve um tipo Response<PayStub>;

A classe [PayrollSheet] é a seguinte:


package pam.entities;
 
import java.io.Serializable;
 
public class FeuilleSalaire implements Serializable {
 
    private static final long serialVersionUID = 1L;
    // private fields
    private Employe employe;
    private Cotisation cotisation;
    private ElementsSalaire elementsSalaire;
 
    // manufacturers
    public FeuilleSalaire() {
    }
 
    public FeuilleSalaire(Employe employe, Cotisation cotisation, ElementsSalaire elementsSalaire) {
        ...
    }
 
    // getters and setters
   ...
}
  • linha 9: a classe [Employee] foi apresentada na secção 4.2.3;
  • linha 10: a classe [Contribution] foi apresentada na secção 4.2.3;

A classe [SalaryElements] (linha 11) é a seguinte:


package pam.entities;
 
import java.io.Serializable;
 
public class ElementsSalaire implements Serializable {
 
    private static final long serialVersionUID = 1L;
    // private fields
    private double salaireBase;
    private double cotisationsSociales;
    private double indemnitesEntretien;
    private double indemnitesRepas;
    private double salaireNet;
 
    // manufacturers
    public ElementsSalaire() {
 
    }
 
    public ElementsSalaire(double salaireBase, double cotisationsSociales, double indemnitesEntretien, double indemnitesRepas, double salaireNet) {
        ...
    }
 
    // getters and setters
    ...
}

4.4. Testes do cliente Android

O ficheiro executável do cliente Android finalizado é fornecido abaixo:

  

Use o rato para arrastar o ficheiro [pam-client.apk] acima para um emulador de tablet [GenyMotion]. Este será então guardado e executado. Inicie também o servidor web/JSON, caso ainda não o tenha feito. O objetivo do cliente Android é recuperar as informações devolvidas pelo servidor web/JSON e formatá-las. As diferentes vistas do cliente Android são as seguintes:

Primeiro, deve ligar-se ao serviço web / JSON:

Image

  • em [1], introduza o URL do serviço web/JSON. Com o emulador, introduza um dos endereços IP do PC (mas não 127.0.0.1). Com um tablet, introduza o endereço Wi-Fi da máquina do servidor web/JSON e desative a firewall do servidor, caso exista, pois esta pode bloquear chamadas recebidas;
  • Em [2], inicie sessão;

Será então redirecionado para a página de simulação:

Image

  • Em [3], selecione um funcionário;
  • Em [4], introduza um número de horas;
  • Em [5], introduza o número de dias;
  • Em [6], execute a simulação;

A página de simulação resultante é a seguinte:

Image

  • em [7], os resultados da simulação;
  • em [8], guarde-a;

Image

  • em [9], a lista de simulações;
  • em [10], uma simulação é eliminada;

Image

  • em [11], não há mais simulações;
  • Em [12], volta ao formulário de simulação;

Image

  • em [13], volta ao formulário;
  • em [14], volta à página de configuração;

Image

  • em [15], volta ao formulário de início de sessão inicial.

4.5. Trabalho a realizar

O esqueleto do cliente Android apresentado anteriormente é fornecido a si. Foi construído a partir do projeto [client-android-skel] descrito na Secção 2.

  

O projeto está pronto a ser executado e já contém as vistas necessárias. Basta adicionar código para que a aplicação faça o que é suposto fazer. O procedimento é o seguinte:

  • execute a versão completa para compreender o trabalho a realizar;
  • execute a versão simplificada e estude o seu código. Segue os métodos de design utilizados nas páginas anteriores;
  • adicione o código que falta;