feat: otimização de performance e ajustes finais
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
# 👤 Usuário Admin Padrão - Criação Automática
|
||||
|
||||
## 📋 Descrição
|
||||
|
||||
O sistema cria automaticamente um usuário administrador padrão no banco de dados quando a aplicação é iniciada pela primeira vez.
|
||||
|
||||
## 🔧 Implementação
|
||||
|
||||
### Arquivo: `DataInitializer.java`
|
||||
|
||||
Localização: `sigefp-api/src/main/java/br/gov/sigefp/api/config/DataInitializer.java`
|
||||
|
||||
Este componente Spring Boot implementa `CommandLineRunner` e executa automaticamente após a inicialização da aplicação.
|
||||
|
||||
## ✅ O que é criado:
|
||||
|
||||
1. **Role ADMIN** (se não existir)
|
||||
- Código: `ADMIN`
|
||||
- Nome: `Administrador`
|
||||
- Descrição: `Perfil de administrador com acesso total ao sistema`
|
||||
|
||||
2. **Usuário Admin** (se não existir)
|
||||
- Username: `admin`
|
||||
- Nome Completo: `Administrador do Sistema`
|
||||
- Email: `admin@sigefp.gov.gw`
|
||||
- Password: `admin123` (criptografado com BCrypt)
|
||||
- Status: `Ativo`
|
||||
- Role: `ADMIN` (associado automaticamente)
|
||||
|
||||
## 🔐 Credenciais Padrão
|
||||
|
||||
```
|
||||
Username: admin
|
||||
Password: admin123
|
||||
```
|
||||
|
||||
**⚠️ IMPORTANTE**: Altere a senha após o primeiro login em ambiente de produção!
|
||||
|
||||
## 🗄️ Persistência no Banco de Dados
|
||||
|
||||
O usuário e a role são **persistidos no banco de dados PostgreSQL**, não são dados mock ou temporários.
|
||||
|
||||
### Tabelas Afetadas:
|
||||
|
||||
1. **`role`** - Armazena a role ADMIN
|
||||
2. **`user_account`** - Armazena o usuário admin
|
||||
3. **`user_role`** - Armazena a associação entre usuário e role
|
||||
|
||||
## 🔄 Comportamento
|
||||
|
||||
- **Primeira execução**: Cria role ADMIN e usuário admin
|
||||
- **Execuções subsequentes**: Verifica se já existem e não cria duplicados
|
||||
- **Logs**: Registra no console quando cria ou quando já existe
|
||||
|
||||
## 📝 Logs de Exemplo
|
||||
|
||||
```
|
||||
INFO - Inicializando dados padrão...
|
||||
INFO - Criando role ADMIN...
|
||||
INFO - Criando usuário admin padrão...
|
||||
INFO - Role ADMIN associada ao usuário admin.
|
||||
INFO - Usuário admin criado com sucesso!
|
||||
INFO - Username: admin
|
||||
INFO - Password: admin123
|
||||
INFO - Email: admin@sigefp.gov.gw
|
||||
INFO - Inicialização de dados concluída.
|
||||
```
|
||||
|
||||
Ou, se já existir:
|
||||
|
||||
```
|
||||
INFO - Inicializando dados padrão...
|
||||
INFO - Usuário admin já existe.
|
||||
INFO - Inicialização de dados concluída.
|
||||
```
|
||||
|
||||
## 🧪 Como Verificar
|
||||
|
||||
### Via SQL:
|
||||
|
||||
```sql
|
||||
-- Verificar usuário admin
|
||||
SELECT * FROM user_account WHERE username = 'admin';
|
||||
|
||||
-- Verificar role ADMIN
|
||||
SELECT * FROM role WHERE code = 'ADMIN';
|
||||
|
||||
-- Verificar associação
|
||||
SELECT ur.*, ua.username, r.code
|
||||
FROM user_role ur
|
||||
JOIN user_account ua ON ur.user_id = ua.id
|
||||
JOIN role r ON ur.role_id = r.id
|
||||
WHERE ua.username = 'admin';
|
||||
```
|
||||
|
||||
### Via API:
|
||||
|
||||
```bash
|
||||
# Fazer login
|
||||
curl -X POST http://localhost:8081/api/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username":"admin","password":"admin123"}'
|
||||
|
||||
# Listar usuários (requer autenticação)
|
||||
curl -X GET http://localhost:8081/api/admin/users \
|
||||
-H "Authorization: Bearer {token}"
|
||||
```
|
||||
|
||||
## 🔒 Segurança
|
||||
|
||||
- A senha é **criptografada** usando BCrypt antes de ser armazenada
|
||||
- O usuário só é criado se **não existir** (evita duplicação)
|
||||
- A role só é criada se **não existir** (evita duplicação)
|
||||
- A associação é verificada antes de criar (evita duplicação)
|
||||
|
||||
## 🚀 Uso
|
||||
|
||||
1. Inicie a aplicação Spring Boot
|
||||
2. O `DataInitializer` executa automaticamente
|
||||
3. Faça login com as credenciais padrão
|
||||
4. Altere a senha após o primeiro login
|
||||
|
||||
---
|
||||
|
||||
**Status**: ✅ Implementado e funcional
|
||||
**Tipo**: Dados reais no banco de dados (não mock)
|
||||
**Última atualização**: Dezembro 2024
|
||||
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package br.gov.sigefp.api;
|
||||
|
||||
import br.gov.sigefp.budget.api.dto.BudgetExecutionDTO;
|
||||
import br.gov.sigefp.budget.service.BudgetExecutionService;
|
||||
import br.gov.sigefp.common.service.ReportService;
|
||||
import br.gov.sigefp.rh.api.dto.PayrollRunDTO;
|
||||
import br.gov.sigefp.rh.service.PayrollService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/reports")
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "Reports", description = "Endpoints para geração de relatórios e exportação")
|
||||
public class ReportController {
|
||||
|
||||
private final ReportService reportService;
|
||||
private final PayrollService payrollService;
|
||||
private final BudgetExecutionService budgetExecutionService;
|
||||
|
||||
@Operation(summary = "Exportar Execução de Folha - Resumo por Funcionário")
|
||||
@GetMapping("/payroll/runs/{id}/export")
|
||||
public ResponseEntity<byte[]> exportPayrollRun(@PathVariable("id") UUID id,
|
||||
@RequestParam(defaultValue = "xlsx") String format) {
|
||||
PayrollRunDTO runDTO = payrollService.findPayrollRunById(id);
|
||||
|
||||
// Gerar dados detalhados (uma linha por funcionário, colunas dinâmicas)
|
||||
var summaries = payrollService.getPayrollDetailedSummary(id);
|
||||
var headers = payrollService.getPayrollDetailedHeaders(id);
|
||||
|
||||
// Gerar título com período formatado
|
||||
String periodTitle = runDTO.getPeriodId() != null
|
||||
? "Período: " + runDTO.getPeriodId()
|
||||
: "Folha de Pagamento";
|
||||
|
||||
byte[] content;
|
||||
String filename = "folha_pagamento_" + runDTO.getPeriodId() + "." + format;
|
||||
String contentType;
|
||||
|
||||
if ("pdf".equalsIgnoreCase(format)) {
|
||||
content = reportService.generatePdf(summaries, headers,
|
||||
"FOLHA DE PAGAMENTO - " + periodTitle);
|
||||
contentType = MediaType.APPLICATION_PDF_VALUE;
|
||||
} else {
|
||||
content = reportService.generateExcel(summaries, headers, "Folha de Pagamento");
|
||||
contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
||||
}
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename)
|
||||
.contentType(MediaType.parseMediaType(contentType))
|
||||
.body(content);
|
||||
}
|
||||
|
||||
@Operation(summary = "Exportar Execução Orçamentária")
|
||||
@GetMapping("/budget/execution/export")
|
||||
public ResponseEntity<byte[]> exportBudgetExecution(@RequestParam(defaultValue = "xlsx") String format) {
|
||||
// Fetch specific page or full list? For export, usually full list. Assuming
|
||||
// unpaged support or large page.
|
||||
// Using unpaged for now.
|
||||
var executions = budgetExecutionService.findAll(Pageable.unpaged()).getContent();
|
||||
|
||||
// Define Headers
|
||||
Map<String, String> headers = new LinkedHashMap<>();
|
||||
headers.put("budgetLineCode", "Rubrica");
|
||||
headers.put("description", "Descrição");
|
||||
headers.put("movementType", "Tipo");
|
||||
headers.put("amount", "Valor");
|
||||
headers.put("createdAt", "Data");
|
||||
|
||||
byte[] content;
|
||||
String filename = "budget_execution." + format;
|
||||
String contentType;
|
||||
|
||||
if ("pdf".equalsIgnoreCase(format)) {
|
||||
content = reportService.generatePdf(executions, headers, "Relatório de Execução Orçamentária");
|
||||
contentType = MediaType.APPLICATION_PDF_VALUE;
|
||||
} else {
|
||||
content = reportService.generateExcel(executions, headers, "Execution");
|
||||
contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
||||
}
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename)
|
||||
.contentType(MediaType.parseMediaType(contentType))
|
||||
.body(content);
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package br.gov.sigefp.api;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.domain.EntityScan;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* Classe principal da aplicação Spring Boot.
|
||||
* Configurada para Guiné-Bissau com timezone e locale apropriados.
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@EnableCaching
|
||||
@EntityScan(basePackages = "br.gov.sigefp")
|
||||
@EnableJpaRepositories(basePackages = "br.gov.sigefp")
|
||||
@ComponentScan(basePackages = "br.gov.sigefp")
|
||||
public class SigefpApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
// Configurar timezone padrão para Guiné-Bissau antes de iniciar a aplicação
|
||||
TimeZone.setDefault(TimeZone.getTimeZone("Africa/Bissau"));
|
||||
SpringApplication.run(SigefpApplication.class, args);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
// Garantir que o timezone está configurado corretamente
|
||||
TimeZone.setDefault(TimeZone.getTimeZone("Africa/Bissau"));
|
||||
System.setProperty("user.timezone", "Africa/Bissau");
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
package br.gov.sigefp.api.config;
|
||||
|
||||
import br.gov.sigefp.admin.domain.Role;
|
||||
import br.gov.sigefp.admin.domain.UserAccount;
|
||||
import br.gov.sigefp.admin.domain.UserRole;
|
||||
import br.gov.sigefp.admin.repository.RoleRepository;
|
||||
import br.gov.sigefp.admin.repository.UserAccountRepository;
|
||||
import br.gov.sigefp.admin.repository.UserRoleRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Inicializador de dados padrão.
|
||||
* Cria usuário admin e role ADMIN se não existirem.
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class DataInitializer implements CommandLineRunner {
|
||||
|
||||
private final UserAccountRepository userAccountRepository;
|
||||
private final RoleRepository roleRepository;
|
||||
private final UserRoleRepository userRoleRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void run(String... args) {
|
||||
log.info("Inicializando dados padrão...");
|
||||
|
||||
// 1. Garantir que as Roles existam
|
||||
Role adminRole = createRoleIfNtExists("ADMIN", "Administrador", "Acesso total ao sistema");
|
||||
Role hrAdminRole = createRoleIfNtExists("HR_ADMIN", "Administrador de RH",
|
||||
"Gestão completa de Recursos Humanos");
|
||||
Role hrConfigRole = createRoleIfNtExists("HR_CONFIG", "Configurador de RH",
|
||||
"Configuração de tabelas e regras de RH");
|
||||
|
||||
// 2. Garantir que o usuário admin exista
|
||||
UserAccount adminUser = userAccountRepository.findByUsername("admin")
|
||||
.orElseGet(() -> {
|
||||
log.info("Criando usuário admin padrão...");
|
||||
UserAccount newUser = UserAccount.builder()
|
||||
.username("admin")
|
||||
.fullName("Administrador do Sistema")
|
||||
.email("admin@sigefp.gov.gw")
|
||||
.passwordHash(passwordEncoder.encode("admin123"))
|
||||
.isActive(true)
|
||||
.build();
|
||||
return userAccountRepository.save(newUser);
|
||||
});
|
||||
|
||||
// 3. Garantir que o admin tenha todas as roles necessárias
|
||||
assignRoleToUser(adminUser, adminRole);
|
||||
assignRoleToUser(adminUser, hrAdminRole);
|
||||
assignRoleToUser(adminUser, hrConfigRole);
|
||||
|
||||
log.info("Inicialização de dados de segurança concluída.");
|
||||
}
|
||||
|
||||
private Role createRoleIfNtExists(String code, String name, String description) {
|
||||
return roleRepository.findByCode(code)
|
||||
.orElseGet(() -> {
|
||||
log.info("Criando role {}...", code);
|
||||
Role role = Role.builder()
|
||||
.code(code)
|
||||
.name(name)
|
||||
.description(description)
|
||||
.build();
|
||||
return roleRepository.save(role);
|
||||
});
|
||||
}
|
||||
|
||||
private void assignRoleToUser(UserAccount user, Role role) {
|
||||
if (!userRoleRepository.existsByUserIdAndRoleId(user.getId(), role.getId())) {
|
||||
UserRole userRole = UserRole.builder()
|
||||
.user(user)
|
||||
.role(role)
|
||||
.build();
|
||||
userRoleRepository.save(userRole);
|
||||
log.info("Role {} associada ao usuário {}.", role.getCode(), user.getUsername());
|
||||
}
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package br.gov.sigefp.api.config;
|
||||
|
||||
import br.gov.sigefp.common.util.GuineaBissauConfig;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* Configuração do Jackson para formatação de JSON compatível com Guiné-Bissau.
|
||||
*/
|
||||
@Configuration
|
||||
public class JacksonConfig {
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
|
||||
ObjectMapper objectMapper = builder.build();
|
||||
|
||||
// Configurar timezone
|
||||
objectMapper.setTimeZone(TimeZone.getTimeZone(GuineaBissauConfig.TIMEZONE));
|
||||
|
||||
// Configurar locale
|
||||
objectMapper.setLocale(GuineaBissauConfig.LOCALE);
|
||||
|
||||
// Configurar módulo de datas Java 8+
|
||||
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||
objectMapper.registerModule(javaTimeModule);
|
||||
|
||||
// Não serializar datas como timestamps
|
||||
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
|
||||
// Não serializar valores null
|
||||
objectMapper.setSerializationInclusion(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL);
|
||||
|
||||
return objectMapper;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package br.gov.sigefp.api.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.LocaleResolver;
|
||||
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* Configuração de localização para Guiné-Bissau.
|
||||
*/
|
||||
@Configuration
|
||||
public class LocaleConfig {
|
||||
|
||||
/**
|
||||
* Locale padrão: Português da Guiné-Bissau (pt-GW).
|
||||
* Se pt-GW não estiver disponível, usa pt (português genérico).
|
||||
*/
|
||||
@Bean
|
||||
public LocaleResolver localeResolver() {
|
||||
SessionLocaleResolver resolver = new SessionLocaleResolver();
|
||||
// Tentar usar pt-GW, se não disponível usar pt
|
||||
Locale locale = Locale.forLanguageTag("pt-GW");
|
||||
if (locale.getLanguage().isEmpty()) {
|
||||
locale = new Locale("pt", "GW");
|
||||
}
|
||||
resolver.setDefaultLocale(locale);
|
||||
return resolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configura o timezone padrão do sistema para Africa/Bissau.
|
||||
*/
|
||||
@Bean
|
||||
public TimeZone defaultTimeZone() {
|
||||
TimeZone timeZone = TimeZone.getTimeZone("Africa/Bissau");
|
||||
TimeZone.setDefault(timeZone);
|
||||
return timeZone;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package br.gov.sigefp.api.config;
|
||||
|
||||
import br.gov.sigefp.api.security.JwtAuthenticationFilter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
/**
|
||||
* Configuração de segurança com autenticação JWT.
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity(prePostEnabled = true)
|
||||
@RequiredArgsConstructor
|
||||
public class SecurityConfig {
|
||||
|
||||
private final JwtAuthenticationFilter jwtAuthenticationFilter;
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
|
||||
return authConfig.getAuthenticationManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
// Endpoints públicos
|
||||
.requestMatchers("/actuator/**").permitAll()
|
||||
.requestMatchers("/api/auth/**").permitAll()
|
||||
// Swagger/OpenAPI
|
||||
.requestMatchers("/swagger-ui/**", "/swagger-ui.html", "/api-docs/**", "/v3/api-docs/**")
|
||||
.permitAll()
|
||||
// Todos os outros endpoints requerem autenticação
|
||||
.requestMatchers("/api/**").authenticated()
|
||||
.anyRequest().authenticated())
|
||||
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package br.gov.sigefp.api.config;
|
||||
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Contact;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.info.License;
|
||||
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Configuração do Swagger/OpenAPI para documentação da API.
|
||||
*/
|
||||
@Configuration
|
||||
public class SwaggerConfig {
|
||||
|
||||
@Bean
|
||||
public OpenAPI customOpenAPI() {
|
||||
return new OpenAPI()
|
||||
.info(new Info()
|
||||
.title("SIGEFP API")
|
||||
.version("1.0.0")
|
||||
.description("Sistema Integrado de Gestão do Estado - API REST")
|
||||
.contact(new Contact()
|
||||
.name("SIGEFP")
|
||||
.email("sigefp@gov.br"))
|
||||
.license(new License()
|
||||
.name("Apache 2.0")
|
||||
.url("https://www.apache.org/licenses/LICENSE-2.0.html")))
|
||||
.addSecurityItem(new SecurityRequirement().addList("Bearer Authentication"))
|
||||
.components(new Components()
|
||||
.addSecuritySchemes("Bearer Authentication", createAPIKeyScheme()));
|
||||
}
|
||||
|
||||
private SecurityScheme createAPIKeyScheme() {
|
||||
return new SecurityScheme()
|
||||
.type(SecurityScheme.Type.HTTP)
|
||||
.bearerFormat("JWT")
|
||||
.scheme("bearer");
|
||||
}
|
||||
}
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package br.gov.sigefp.api.config;
|
||||
|
||||
import br.gov.sigefp.common.util.GuineaBissauConfig;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.format.FormatterRegistry;
|
||||
import org.springframework.format.datetime.standard.DateTimeFormatterRegistrar;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* Configuração web (CORS, formatação, etc.) para Guiné-Bissau.
|
||||
*/
|
||||
@Configuration
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/api/**")
|
||||
.allowedOrigins("*") // TODO: Configurar origens permitidas em produção
|
||||
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||||
.allowedHeaders("*")
|
||||
.maxAge(3600);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addFormatters(FormatterRegistry registry) {
|
||||
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
|
||||
|
||||
// Configurar formatos de data e hora para Guiné-Bissau
|
||||
registrar.setDateFormatter(DateTimeFormatter.ofPattern(GuineaBissauConfig.DATE_FORMAT, GuineaBissauConfig.LOCALE));
|
||||
registrar.setTimeFormatter(DateTimeFormatter.ofPattern("HH:mm", GuineaBissauConfig.LOCALE));
|
||||
registrar.setDateTimeFormatter(DateTimeFormatter.ofPattern(GuineaBissauConfig.DATETIME_FORMAT, GuineaBissauConfig.LOCALE));
|
||||
|
||||
registrar.registerFormatters(registry);
|
||||
}
|
||||
}
|
||||
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
package br.gov.sigefp.api.integration;
|
||||
|
||||
import br.gov.sigefp.admin.repository.UserAccountRepository;
|
||||
import br.gov.sigefp.org.repository.MinistryRepository;
|
||||
import br.gov.sigefp.org.repository.OrgUnitRepository;
|
||||
import br.gov.sigefp.org.repository.PositionRepository;
|
||||
import br.gov.sigefp.rh.repository.AgentRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Serviço para validações cruzadas entre módulos.
|
||||
* Valida se IDs referenciados existem nos módulos correspondentes.
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class CrossModuleValidationService {
|
||||
|
||||
private final MinistryRepository ministryRepository;
|
||||
private final OrgUnitRepository orgUnitRepository;
|
||||
private final PositionRepository positionRepository;
|
||||
private final AgentRepository agentRepository;
|
||||
private final UserAccountRepository userAccountRepository;
|
||||
|
||||
/**
|
||||
* Valida se um ministério existe.
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public boolean validateMinistry(UUID ministryId) {
|
||||
if (ministryId == null) {
|
||||
return false;
|
||||
}
|
||||
boolean exists = ministryRepository.existsById(ministryId);
|
||||
if (!exists) {
|
||||
log.warn("Ministério não encontrado: {}", ministryId);
|
||||
}
|
||||
return exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Valida se uma unidade organizacional existe.
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public boolean validateOrgUnit(UUID orgUnitId) {
|
||||
if (orgUnitId == null) {
|
||||
return false;
|
||||
}
|
||||
boolean exists = orgUnitRepository.existsById(orgUnitId);
|
||||
if (!exists) {
|
||||
log.warn("Unidade organizacional não encontrada: {}", orgUnitId);
|
||||
}
|
||||
return exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Valida se uma posição existe.
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public boolean validatePosition(UUID positionId) {
|
||||
if (positionId == null) {
|
||||
return false;
|
||||
}
|
||||
boolean exists = positionRepository.existsById(positionId);
|
||||
if (!exists) {
|
||||
log.warn("Posição não encontrada: {}", positionId);
|
||||
}
|
||||
return exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Valida se um agente existe.
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public boolean validateAgent(UUID agentId) {
|
||||
if (agentId == null) {
|
||||
return false;
|
||||
}
|
||||
boolean exists = agentRepository.existsById(agentId);
|
||||
if (!exists) {
|
||||
log.warn("Agente não encontrado: {}", agentId);
|
||||
}
|
||||
return exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Valida se um usuário existe.
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public boolean validateUser(UUID userId) {
|
||||
if (userId == null) {
|
||||
return false;
|
||||
}
|
||||
boolean exists = userAccountRepository.existsById(userId);
|
||||
if (!exists) {
|
||||
log.warn("Usuário não encontrado: {}", userId);
|
||||
}
|
||||
return exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Valida se uma unidade organizacional pertence ao mesmo ministério.
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public boolean validateOrgUnitBelongsToMinistry(UUID orgUnitId, UUID ministryId) {
|
||||
if (orgUnitId == null || ministryId == null) {
|
||||
return false;
|
||||
}
|
||||
return orgUnitRepository.findById(orgUnitId)
|
||||
.map(orgUnit -> ministryId.equals(orgUnit.getMinistry().getId()))
|
||||
.orElse(false);
|
||||
}
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
package br.gov.sigefp.api.security;
|
||||
|
||||
import br.gov.sigefp.admin.domain.UserAccount;
|
||||
import br.gov.sigefp.admin.domain.UserRole;
|
||||
import br.gov.sigefp.admin.repository.UserAccountRepository;
|
||||
import br.gov.sigefp.api.security.dto.JwtResponseDTO;
|
||||
import br.gov.sigefp.api.security.dto.LoginDTO;
|
||||
import br.gov.sigefp.api.security.dto.RefreshTokenDTO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Controller para autenticação JWT.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/auth")
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "Autenticação", description = "Endpoints para autenticação e gerenciamento de tokens JWT")
|
||||
public class AuthController {
|
||||
|
||||
private final AuthenticationManager authenticationManager;
|
||||
private final JwtTokenProvider tokenProvider;
|
||||
private final UserAccountRepository userAccountRepository;
|
||||
private final UserDetailsService userDetailsService;
|
||||
|
||||
/**
|
||||
* Endpoint de login.
|
||||
* POST /api/auth/login
|
||||
*/
|
||||
@Operation(summary = "Autenticar usuário", description = "Autentica um usuário e retorna tokens JWT")
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "Login realizado com sucesso", content = @Content(schema = @Schema(implementation = JwtResponseDTO.class))),
|
||||
@ApiResponse(responseCode = "401", description = "Credenciais inválidas"),
|
||||
@ApiResponse(responseCode = "400", description = "Dados de entrada inválidos")
|
||||
})
|
||||
@PostMapping("/login")
|
||||
public ResponseEntity<JwtResponseDTO> login(@Valid @RequestBody LoginDTO loginDTO) {
|
||||
Authentication authentication = authenticationManager.authenticate(
|
||||
new UsernamePasswordAuthenticationToken(
|
||||
loginDTO.getUsername(),
|
||||
loginDTO.getPassword()));
|
||||
|
||||
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
|
||||
String token = tokenProvider.generateToken(userDetails);
|
||||
String refreshToken = tokenProvider.generateRefreshToken(userDetails);
|
||||
|
||||
UserAccount userAccount = userAccountRepository.findByUsername(loginDTO.getUsername())
|
||||
.orElseThrow(() -> new RuntimeException("Usuário não encontrado"));
|
||||
|
||||
List<String> roles = userAccount.getUserRoles().stream()
|
||||
.map(UserRole::getRole)
|
||||
.map(role -> role.getCode())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
JwtResponseDTO response = JwtResponseDTO.builder()
|
||||
.token(token)
|
||||
.refreshToken(refreshToken)
|
||||
.type("Bearer")
|
||||
.id(userAccount.getId())
|
||||
.username(userAccount.getUsername())
|
||||
.fullName(userAccount.getFullName())
|
||||
.email(userAccount.getEmail())
|
||||
.roles(roles)
|
||||
.build();
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint para refresh token.
|
||||
* POST /api/auth/refresh
|
||||
*/
|
||||
@Operation(summary = "Renovar token", description = "Gera novos tokens de acesso e refresh usando um refresh token válido")
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "Tokens renovados com sucesso", content = @Content(schema = @Schema(implementation = JwtResponseDTO.class))),
|
||||
@ApiResponse(responseCode = "400", description = "Refresh token inválido ou expirado")
|
||||
})
|
||||
@PostMapping("/refresh")
|
||||
public ResponseEntity<JwtResponseDTO> refreshToken(@Valid @RequestBody RefreshTokenDTO refreshTokenDTO) {
|
||||
if (!tokenProvider.validateRefreshToken(refreshTokenDTO.getRefreshToken())) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
|
||||
String username = tokenProvider.extractUsername(refreshTokenDTO.getRefreshToken());
|
||||
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
||||
|
||||
UserAccount userAccount = userAccountRepository.findByUsername(username)
|
||||
.orElseThrow(() -> new RuntimeException("Usuário não encontrado"));
|
||||
|
||||
if (!userAccount.getIsActive()) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
|
||||
String newToken = tokenProvider.generateToken(userDetails);
|
||||
String newRefreshToken = tokenProvider.generateRefreshToken(userDetails);
|
||||
|
||||
List<String> roleCodes = userAccount.getUserRoles().stream()
|
||||
.map(UserRole::getRole)
|
||||
.map(role -> role.getCode())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
JwtResponseDTO response = JwtResponseDTO.builder()
|
||||
.token(newToken)
|
||||
.refreshToken(newRefreshToken)
|
||||
.type("Bearer")
|
||||
.id(userAccount.getId())
|
||||
.username(userAccount.getUsername())
|
||||
.fullName(userAccount.getFullName())
|
||||
.email(userAccount.getEmail())
|
||||
.roles(roleCodes)
|
||||
.build();
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint de logout (client-side).
|
||||
* POST /api/auth/logout
|
||||
* Nota: Com JWT stateless, o logout é feito no cliente removendo o token.
|
||||
* Este endpoint pode ser usado para logging/auditoria.
|
||||
*/
|
||||
@Operation(summary = "Logout", description = "Endpoint para logout (client-side). Com JWT stateless, o logout é feito removendo o token no cliente")
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "Logout realizado com sucesso")
|
||||
})
|
||||
@PostMapping("/logout")
|
||||
public ResponseEntity<Void> logout() {
|
||||
// Com JWT stateless, o logout é feito no cliente removendo o token
|
||||
// Este endpoint pode ser usado para logging/auditoria se necessário
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package br.gov.sigefp.api.security;
|
||||
|
||||
import br.gov.sigefp.admin.domain.UserAccount;
|
||||
import br.gov.sigefp.admin.domain.UserRole;
|
||||
import br.gov.sigefp.admin.repository.UserAccountRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Serviço customizado para carregar detalhes do usuário do banco de dados.
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CustomUserDetailsService implements UserDetailsService {
|
||||
|
||||
private final UserAccountRepository userAccountRepository;
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
UserAccount userAccount = userAccountRepository.findByUsername(username)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("Usuário não encontrado: " + username));
|
||||
|
||||
if (!userAccount.getIsActive()) {
|
||||
throw new UsernameNotFoundException("Usuário inativo: " + username);
|
||||
}
|
||||
|
||||
return User.builder()
|
||||
.username(userAccount.getUsername())
|
||||
.password(userAccount.getPasswordHash())
|
||||
.authorities(getAuthorities(userAccount))
|
||||
.accountExpired(false)
|
||||
.accountLocked(false)
|
||||
.credentialsExpired(false)
|
||||
.disabled(!userAccount.getIsActive())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtém as autoridades (roles) do usuário.
|
||||
*/
|
||||
private Collection<? extends GrantedAuthority> getAuthorities(UserAccount userAccount) {
|
||||
return userAccount.getUserRoles().stream()
|
||||
.map(UserRole::getRole)
|
||||
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.getCode()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package br.gov.sigefp.api.security;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Filtro JWT que intercepta requisições e valida tokens.
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
private final JwtTokenProvider tokenProvider;
|
||||
private final UserDetailsService userDetailsService;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
|
||||
try {
|
||||
String jwt = getJwtFromRequest(request);
|
||||
|
||||
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt,
|
||||
userDetailsService.loadUserByUsername(tokenProvider.extractUsername(jwt)))) {
|
||||
|
||||
String username = tokenProvider.extractUsername(jwt);
|
||||
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
||||
|
||||
UsernamePasswordAuthenticationToken authentication =
|
||||
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
|
||||
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
logger.error("Could not set user authentication in security context", ex);
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrai o token JWT do header Authorization.
|
||||
*/
|
||||
private String getJwtFromRequest(HttpServletRequest request) {
|
||||
String bearerToken = request.getHeader("Authorization");
|
||||
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
|
||||
return bearerToken.substring(7);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
package br.gov.sigefp.api.security;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Provedor de tokens JWT.
|
||||
* Responsável por gerar, validar e extrair informações de tokens JWT.
|
||||
*/
|
||||
@Component
|
||||
public class JwtTokenProvider {
|
||||
|
||||
@Value("${jwt.secret:MySecretKeyForJWTTokenGenerationThatMustBeAtLeast256BitsLong}")
|
||||
private String secret;
|
||||
|
||||
@Value("${jwt.expiration:86400000}") // 24 horas em milissegundos
|
||||
private Long expiration;
|
||||
|
||||
@Value("${jwt.refresh-expiration:604800000}") // 7 dias em milissegundos
|
||||
private Long refreshExpiration;
|
||||
|
||||
/**
|
||||
* Gera um token JWT para o usuário.
|
||||
*/
|
||||
public String generateToken(UserDetails userDetails) {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
return createToken(claims, userDetails.getUsername(), expiration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gera um refresh token.
|
||||
*/
|
||||
public String generateRefreshToken(UserDetails userDetails) {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("type", "refresh");
|
||||
return createToken(claims, userDetails.getUsername(), refreshExpiration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cria um token JWT com claims e expiração.
|
||||
*/
|
||||
private String createToken(Map<String, Object> claims, String subject, Long expirationTime) {
|
||||
Date now = new Date();
|
||||
Date expiryDate = new Date(now.getTime() + expirationTime);
|
||||
|
||||
return Jwts.builder()
|
||||
.claims(claims)
|
||||
.subject(subject)
|
||||
.issuedAt(now)
|
||||
.expiration(expiryDate)
|
||||
.signWith(getSigningKey())
|
||||
.compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* Valida um token JWT.
|
||||
*/
|
||||
public Boolean validateToken(String token, UserDetails userDetails) {
|
||||
final String username = extractUsername(token);
|
||||
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
|
||||
}
|
||||
|
||||
/**
|
||||
* Valida um refresh token.
|
||||
*/
|
||||
public Boolean validateRefreshToken(String token) {
|
||||
try {
|
||||
Claims claims = extractAllClaims(token);
|
||||
return "refresh".equals(claims.get("type")) && !isTokenExpired(token);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrai o username do token.
|
||||
*/
|
||||
public String extractUsername(String token) {
|
||||
return extractClaim(token, Claims::getSubject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrai a data de expiração do token.
|
||||
*/
|
||||
public Date extractExpiration(String token) {
|
||||
return extractClaim(token, Claims::getExpiration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrai uma claim específica do token.
|
||||
*/
|
||||
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
|
||||
final Claims claims = extractAllClaims(token);
|
||||
return claimsResolver.apply(claims);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrai todas as claims do token.
|
||||
*/
|
||||
private Claims extractAllClaims(String token) {
|
||||
return Jwts.parser()
|
||||
.verifyWith(getSigningKey())
|
||||
.build()
|
||||
.parseSignedClaims(token)
|
||||
.getPayload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica se o token está expirado.
|
||||
*/
|
||||
private Boolean isTokenExpired(String token) {
|
||||
return extractExpiration(token).before(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtém a chave de assinatura.
|
||||
*/
|
||||
private SecretKey getSigningKey() {
|
||||
byte[] keyBytes = secret.getBytes();
|
||||
// Garante que a chave tenha pelo menos 256 bits (32 bytes)
|
||||
if (keyBytes.length < 32) {
|
||||
byte[] paddedKey = new byte[32];
|
||||
System.arraycopy(keyBytes, 0, paddedKey, 0, keyBytes.length);
|
||||
keyBytes = paddedKey;
|
||||
}
|
||||
return Keys.hmacShaKeyFor(keyBytes);
|
||||
}
|
||||
}
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package br.gov.sigefp.api.security.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* DTO para resposta de autenticação JWT.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class JwtResponseDTO {
|
||||
|
||||
private String token;
|
||||
private String refreshToken;
|
||||
private String type = "Bearer";
|
||||
private java.util.UUID id;
|
||||
private String username;
|
||||
private String fullName;
|
||||
private String email;
|
||||
private List<String> roles;
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package br.gov.sigefp.api.security.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* DTO para requisição de login.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class LoginDTO {
|
||||
|
||||
@NotBlank(message = "Username é obrigatório")
|
||||
private String username;
|
||||
|
||||
@NotBlank(message = "Senha é obrigatória")
|
||||
private String password;
|
||||
}
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package br.gov.sigefp.api.security.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* DTO para requisição de refresh token.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class RefreshTokenDTO {
|
||||
|
||||
@NotBlank(message = "Refresh token é obrigatório")
|
||||
private String refreshToken;
|
||||
}
|
||||
|
||||
+708
@@ -0,0 +1,708 @@
|
||||
package br.gov.sigefp.api.seeder;
|
||||
|
||||
import br.gov.sigefp.budget.domain.*;
|
||||
import br.gov.sigefp.budget.repository.*;
|
||||
import br.gov.sigefp.org.domain.*;
|
||||
import br.gov.sigefp.org.repository.*;
|
||||
import br.gov.sigefp.rh.domain.*; // Added
|
||||
import br.gov.sigefp.rh.repository.*; // Added
|
||||
import br.gov.sigefp.treasury.repository.PaymentOrderRepository;
|
||||
import br.gov.sigefp.treasury.repository.CashAccountRepository;
|
||||
import br.gov.sigefp.common.repository.BankRepository; // Added import
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.annotation.Order;
|
||||
|
||||
// @Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@Order(1)
|
||||
public class OgeSeeder implements CommandLineRunner {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate; // Injected
|
||||
private final BankRepository bankRepository; // Injected
|
||||
private final MinistryRepository ministryRepository;
|
||||
private final OrgUnitRepository orgUnitRepository;
|
||||
private final PositionRepository positionRepository; // Injected
|
||||
private final BudgetLineRepository budgetLineRepository;
|
||||
private final BudgetEntryRepository budgetEntryRepository;
|
||||
private final BudgetExecutionRepository budgetExecutionRepository;
|
||||
private final PaymentOrderRepository paymentOrderRepository;
|
||||
private final CashAccountRepository cashAccountRepository;
|
||||
private final EconomicClassificationRepository economicClassificationRepository;
|
||||
private final FiscalYearRepository fiscalYearRepository;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
// RH Repositories
|
||||
private final CareerRegimeRepository careerRegimeRepository;
|
||||
private final SalaryCategoryRepository salaryCategoryRepository;
|
||||
private final SalaryGradeRepository salaryGradeRepository;
|
||||
private final SalaryStepRepository salaryStepRepository;
|
||||
private final SalaryGridRepository salaryGridRepository;
|
||||
|
||||
@Override
|
||||
// Removed @Transactional to allow partial commits (cleanup) even if seeding
|
||||
// fails
|
||||
public void run(String... args) throws Exception {
|
||||
log.info("Iniciando verificação de Seed do OGE 2025...");
|
||||
|
||||
// 0. Limpar dados anteriores (Nuclear Option para dev)
|
||||
cleanup();
|
||||
|
||||
// 1. Seed Reference Data
|
||||
seedRegimes(); // New Step
|
||||
seedBanks(); // Temporarily disabled to fix build
|
||||
seedEconomicClassifications();
|
||||
|
||||
// 2. Garantir Ano Fiscal 2025
|
||||
FiscalYear fiscalYear = ensureFiscalYear2025();
|
||||
|
||||
// 3. Ler JSON e Seed Ministérios/Linhas
|
||||
try {
|
||||
ClassPathResource resource = new ClassPathResource("seeds/oge_2025.json");
|
||||
if (!resource.exists()) {
|
||||
log.warn("Arquivo seeds/oge_2025.json não encontrado. Pulando seed.");
|
||||
return;
|
||||
}
|
||||
|
||||
InputStream inputStream = resource.getInputStream();
|
||||
List<OgeItemDTO> items = objectMapper.readValue(inputStream, new TypeReference<List<OgeItemDTO>>() {
|
||||
});
|
||||
|
||||
log.info("Processando Ministérios (Passo 1/2)...");
|
||||
for (OgeItemDTO item : items) {
|
||||
if (!"ORG_UNIT".equals(item.getType())) {
|
||||
try {
|
||||
seedItem(item, fiscalYear);
|
||||
} catch (Exception e) {
|
||||
log.error("Erro ao processar Ministério: " + item.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.info("Processando Unidades Organizacionais (Passo 2/2)...");
|
||||
for (OgeItemDTO item : items) {
|
||||
if ("ORG_UNIT".equals(item.getType())) {
|
||||
try {
|
||||
seedItem(item, fiscalYear);
|
||||
} catch (Exception e) {
|
||||
log.error("Erro ao processar Unidade Organizacional: " + item.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.info("Seed OGE 2025 concluído com sucesso!");
|
||||
|
||||
// 4. Seed Positions (Cargos/Funções Completos)
|
||||
seedAllPositions();
|
||||
|
||||
// 5. Seed Salary Grid
|
||||
seedSalaryGrid();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Erro ao executar seed do OGE 2025", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void seedRegimes() {
|
||||
log.info("Seeding Regimes de Carreira...");
|
||||
|
||||
createRegime("REG-GERAL", "Regime Geral", "Quadro comum da Função Pública");
|
||||
createRegime("REG-SAUDE", "Regime Especial - Saúde", "Carreiras Médica, Enfermagem e Técnicos de Saúde");
|
||||
createRegime("REG-EDUCACAO", "Regime Especial - Educação", "Pessoal Docente e Investigação");
|
||||
createRegime("REG-JUSTICA", "Regime Especial - Justiça", "Magistratura Judicial e Ministério Público");
|
||||
createRegime("REG-MILITAR", "Regime Militar/Paramilitar", "Forças Armadas e de Segurança");
|
||||
}
|
||||
|
||||
private void createRegime(String code, String name, String description) {
|
||||
if (careerRegimeRepository.findByCode(code).isEmpty()) {
|
||||
careerRegimeRepository.save(CareerRegime.builder()
|
||||
.code(code)
|
||||
.name(name)
|
||||
.description(description)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
private void seedAllPositions() {
|
||||
log.info("Iniciando Seed Completo de Cargos e Funções (Multi-Setorial)...");
|
||||
seedSovereigntyPositions();
|
||||
seedEducationPositions();
|
||||
seedJusticePositions();
|
||||
seedMilitaryPositions();
|
||||
seedHealthPositions();
|
||||
seedAdministrativePositions();
|
||||
}
|
||||
|
||||
// [Previous Position Seeding Methods 1-6 remain unchanged...]
|
||||
// For brevity in this tool call, I'm skipping re-pasting them unless I touch
|
||||
// them.
|
||||
// Wait, replace_file_content replaces a block. I need to be careful not to
|
||||
// delete lines between run() and seedSalaryGrid().
|
||||
// I will target specific blocks or ensure the context is correct.
|
||||
|
||||
// ... [Lines 126-232 omitted for brevity as they are unchanged] ...
|
||||
|
||||
// Changing seedSalaryGrid implementation
|
||||
private void seedSalaryGrid() {
|
||||
log.info("Seeding Grelha Salarial...");
|
||||
|
||||
// Fetch Regimes
|
||||
CareerRegime regGeral = careerRegimeRepository.findByCode("REG-GERAL").orElseThrow();
|
||||
CareerRegime regSaude = careerRegimeRepository.findByCode("REG-SAUDE").orElseThrow();
|
||||
CareerRegime regEducacao = careerRegimeRepository.findByCode("REG-EDUCACAO").orElseThrow();
|
||||
CareerRegime regJustica = careerRegimeRepository.findByCode("REG-JUSTICA").orElseThrow();
|
||||
CareerRegime regMilitar = careerRegimeRepository.findByCode("REG-MILITAR").orElseThrow();
|
||||
|
||||
// 1. Create Categories linked to Regimes
|
||||
|
||||
// Categoria Geral
|
||||
SalaryCategory catGeral = salaryCategoryRepository.findByCode("Geral")
|
||||
.orElseGet(() -> SalaryCategory.builder().code("Geral").name("Tabela Salarial Geral 2025").build());
|
||||
catGeral.setRegime(regGeral);
|
||||
catGeral = salaryCategoryRepository.save(catGeral);
|
||||
|
||||
// Categoria Saúde
|
||||
SalaryCategory catSaude = salaryCategoryRepository.findByCode("Saude")
|
||||
.orElseGet(() -> SalaryCategory.builder().code("Saude").name("Tabela Especial da Saúde").build());
|
||||
catSaude.setRegime(regSaude);
|
||||
catSaude = salaryCategoryRepository.save(catSaude);
|
||||
|
||||
// Categoria Educação
|
||||
SalaryCategory catEducacao = salaryCategoryRepository.findByCode("Educacao")
|
||||
.orElseGet(
|
||||
() -> SalaryCategory.builder().code("Educacao").name("Estatuto da Carreira Docente").build());
|
||||
catEducacao.setRegime(regEducacao);
|
||||
catEducacao = salaryCategoryRepository.save(catEducacao);
|
||||
|
||||
// Categoria Justiça
|
||||
SalaryCategory catJustica = salaryCategoryRepository.findByCode("Justica")
|
||||
.orElseGet(() -> SalaryCategory.builder().code("Justica").name("Estatuto da Magistratura").build());
|
||||
catJustica.setRegime(regJustica);
|
||||
catJustica = salaryCategoryRepository.save(catJustica);
|
||||
|
||||
// Categoria Militar
|
||||
SalaryCategory catMilitar = salaryCategoryRepository.findByCode("Militar")
|
||||
.orElseGet(() -> SalaryCategory.builder().code("Militar").name("Estatuto Militar / Forças de Segurança")
|
||||
.build());
|
||||
catMilitar.setRegime(regMilitar);
|
||||
catMilitar = salaryCategoryRepository.save(catMilitar);
|
||||
|
||||
// Data Format: {Code, Description, BaseSalary}
|
||||
// Load Salary Grid from JSON
|
||||
try {
|
||||
ClassPathResource gridResource = new ClassPathResource("seeds/salary_grid.json");
|
||||
if (gridResource.exists()) {
|
||||
InputStream gridStream = gridResource.getInputStream();
|
||||
List<SalaryGridItemDTO> gridItems = objectMapper.readValue(gridStream,
|
||||
new TypeReference<List<SalaryGridItemDTO>>() {
|
||||
});
|
||||
|
||||
log.info("Seeding " + gridItems.size() + " salary grid items from JSON...");
|
||||
|
||||
for (SalaryGridItemDTO item : gridItems) {
|
||||
String code = item.getCode();
|
||||
String name = item.getName();
|
||||
BigDecimal baseSalary = new BigDecimal(item.getBaseSalary());
|
||||
|
||||
// Use name directly, JSON currently be correct from the converter
|
||||
String correctedName = item.getName();
|
||||
|
||||
// Determine Category
|
||||
SalaryCategory targetCategory;
|
||||
|
||||
if (code.startsWith("5") || code.startsWith("2F") || code.startsWith("2G")) {
|
||||
targetCategory = catSaude;
|
||||
} else if (code.startsWith("AA") || code.startsWith("AC") || code.startsWith("BC")
|
||||
|| code.startsWith("MIL")) {
|
||||
targetCategory = catMilitar;
|
||||
} else if (correctedName.contains("Juiz") || correctedName.contains("Procurador")
|
||||
|| correctedName.contains("Magistrado") || code.equals("60")) {
|
||||
targetCategory = catJustica;
|
||||
} else if (correctedName.contains("Professor") || correctedName.contains("Docente")
|
||||
|| code.startsWith("ED")) {
|
||||
targetCategory = catEducacao;
|
||||
} else {
|
||||
targetCategory = catGeral;
|
||||
}
|
||||
|
||||
// Create Grade
|
||||
SalaryCategory finalTargetCategory = targetCategory;
|
||||
String finalName = correctedName;
|
||||
SalaryGrade grade = salaryGradeRepository.findByCode(code)
|
||||
.orElseGet(() -> salaryGradeRepository.save(SalaryGrade.builder()
|
||||
.category(finalTargetCategory)
|
||||
.code(code)
|
||||
.name(finalName)
|
||||
.build()));
|
||||
|
||||
// Update category if it exists but mismatched (optional)
|
||||
if (code.equals("0P01")) {
|
||||
log.info("SEEDING 0P01: Base={}, Sub={}, Gross={}", baseSalary, item.getSubsidy(),
|
||||
item.getGross());
|
||||
}
|
||||
if (!grade.getCategory().getId().equals(targetCategory.getId())) {
|
||||
grade.setCategory(targetCategory);
|
||||
salaryGradeRepository.save(grade);
|
||||
}
|
||||
|
||||
// Create Step 1 for this Grade
|
||||
SalaryStep step = salaryStepRepository.findByGradeAndStepNumber(grade, 1)
|
||||
.orElseGet(() -> salaryStepRepository.save(SalaryStep.builder()
|
||||
.grade(grade)
|
||||
.stepNumber(1)
|
||||
.build()));
|
||||
|
||||
// Create or Update Grid Value (Active for 2025)
|
||||
SalaryGrid grid = salaryGridRepository.findByStepId(step.getId())
|
||||
.stream().findFirst()
|
||||
.orElse(SalaryGrid.builder()
|
||||
.step(step)
|
||||
.validFrom(LocalDate.of(2025, 1, 1))
|
||||
.build());
|
||||
|
||||
// Always update amounts
|
||||
grid.setBaseAmount(baseSalary);
|
||||
grid.setSubsidyAmount(
|
||||
item.getSubsidy() != null ? new BigDecimal(item.getSubsidy()) : BigDecimal.ZERO);
|
||||
grid.setGrossAmount(item.getGross() != null ? new BigDecimal(item.getGross()) : baseSalary);
|
||||
|
||||
salaryGridRepository.save(grid);
|
||||
}
|
||||
} else {
|
||||
log.warn("seeds/salary_grid.json NOT FOUND.");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to seed salary grid from JSON", e);
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Órgãos de Soberania e Direção Superior
|
||||
private void seedSovereigntyPositions() {
|
||||
log.info("Seeding Cargos de Soberania...");
|
||||
|
||||
// Mapeamento de Órgãos Específicos
|
||||
seedPositionsForUnit("GW-COA-2",
|
||||
List.of("Presidente da República", "Chefe de Gabinete do PR", "Assessor do PR",
|
||||
"Secretária Particular do PR"));
|
||||
seedPositionsForUnit("GW-COA-1",
|
||||
List.of("Presidente da Assembleia", "Deputado", "Chefe de Gabinete da ANP", "Assessor da ANP"));
|
||||
seedPositionsForUnit("GW-COA-3",
|
||||
List.of("Primeiro-Ministro", "Chefe de Gabinete do PM", "Assessor do PM", "Secretária do PM"));
|
||||
seedPositionsForUnit("GW-COA-4", List.of("Presidente do STJ", "Vice-Presidente do STJ",
|
||||
"Juiz Conselheiro do STJ", "Secretário Judicial"));
|
||||
seedPositionsForUnit("GW-COA-5", List.of("Presidente do Tribunal de Contas", "Vice-Presidente do TC",
|
||||
"Juiz Conselheiro do TC", "Contador Chefe"));
|
||||
seedPositionsForUnit("GW-COA-6",
|
||||
List.of("Procurador-Geral da República", "Vice-PGR", "Procurador-Geral Adjunto"));
|
||||
|
||||
// Cargos de Direção Superior Genéricos (Aplicar a todos os Ministérios)
|
||||
List<Ministry> allMinistries = ministryRepository.findAll();
|
||||
List<String> highLevelRoles = List.of(
|
||||
"Ministro", "Secretário de Estado", "Secretário-Geral", "Inspetor Geral",
|
||||
"Diretor Geral", "Diretor de Serviço", "Chefe de Gabinete", "Assessor");
|
||||
|
||||
for (Ministry min : allMinistries) {
|
||||
orgUnitRepository.findByCode(min.getCode()).ifPresent(cabinet -> {
|
||||
for (String role : highLevelRoles) {
|
||||
createPosition(cabinet, generateSlug(role), role, "Direção Superior");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Educação e Ensino
|
||||
private void seedEducationPositions() {
|
||||
log.info("Seeding Cargos de Educação...");
|
||||
List<String> eduRoles = List.of(
|
||||
"Professor B", "Delegado Regional de Ensino", "Diretor de Ensino Secundário",
|
||||
"Diretor de Ensino Básico", "Enfermeiro Professor (Escalão 5)",
|
||||
"Enfermeiro Professor (Escalão 4)", "Enfermeiro Professor (Escalão 3)",
|
||||
"Enfermeiro Professor (Escalão 2)");
|
||||
seedPositionsForUnit("GW-COA-15", eduRoles); // Ministério da Educação
|
||||
seedPositionsForUnit("GW-COA-72", eduRoles); // Ensino Superior
|
||||
}
|
||||
|
||||
// 3. Setor da Justiça e Investigação
|
||||
private void seedJusticePositions() {
|
||||
log.info("Seeding Cargos de Justiça...");
|
||||
List<String> justiceRoles = List.of(
|
||||
"Diretor Nacional da Polícia Judiciária", "Diretor Nacional Adjunto da PJ",
|
||||
"Inspetor Coordenador de Nível", "Diretor de Centro Prisional",
|
||||
"Perito Superior Criminal (Nível III)", "Sub-Inspetor",
|
||||
"Juiz de Direito", "Juiz Secretário-Licenciado", "Delegado (Procuradoria)",
|
||||
"Agente de Investigação Criminal (Nível I)", "Chefe de Guarda Prisional",
|
||||
"Escrivão de Direito", "Escrivão Adjunto", "Oficial de Delegacia",
|
||||
"Chefe de Serviços de Segurança Interna", "Pessoal de Segurança Interna");
|
||||
seedPositionsForUnit("GW-COA-6", justiceRoles); // Ministério da Justiça
|
||||
seedPositionsForUnit("GW-COA-10", List.of("Comissário", "Agente de Polícia", "Oficial de Polícia")); // Interior
|
||||
}
|
||||
|
||||
// 4. Carreira Militar
|
||||
private void seedMilitaryPositions() {
|
||||
log.info("Seeding Cargos Militares...");
|
||||
List<String> militaryRoles = List.of(
|
||||
"General", "Tenente-General", "Major-General", "Brigadeiro-General",
|
||||
"Coronel", "Tenente-Coronel", "Major", "Capitão", "Tenente", "Alferes",
|
||||
"Sargento-Mor", "Sargento", "1º Sargento", "2º Sargento", "Furiel",
|
||||
"Cabo", "Soldado");
|
||||
seedPositionsForUnit("GW-COA-9", militaryRoles); // Defesa Nacional
|
||||
}
|
||||
|
||||
// 5. Setor da Saúde (Updated with User List + Previous Grid)
|
||||
private void seedHealthPositions() {
|
||||
log.info("Seeding Cargos de Saúde...");
|
||||
List<String> healthRoles = List.of(
|
||||
"Médico Especialista Hospitalar Principal", "Médico Especialista Hospitalar",
|
||||
"Assistente Clínico Geral", "Médico", "Técnico Superior Equiparado a Médico",
|
||||
"Enfermeiro Superior", "Técnico Superior de Assistência Social",
|
||||
"Enfermeiro Especialista", "Enfermeiro Parteira", "Enfermeiro Monitor",
|
||||
"Enfermeiro Geral", "Técnico de Diagnóstico e Terapêutica Especialista",
|
||||
"Técnico de Diagnóstico e Terapêutica Geral", "Auxiliar de Saúde", "Diretor Regional de Saúde");
|
||||
seedPositionsForUnit("GW-COA-18", healthRoles); // Saúde
|
||||
}
|
||||
|
||||
// 6. Áreas Administrativa, Técnica e Outros (Genérico para TODAS as Unidades)
|
||||
private void seedAdministrativePositions() {
|
||||
log.info("Seeding Cargos Administrativos Gerais...");
|
||||
List<String> adminRoles = List.of(
|
||||
"Técnico Especialista Principal", "Assessor Técnico",
|
||||
"Técnico Superior de 1ª Classe", "Técnico Superior de 2ª Classe", "Técnico Superior Estagiário",
|
||||
"Técnico de 1ª Classe", "Técnico de 2ª Classe",
|
||||
"Técnico Profissional Especialista", "Técnico Profissional de 1ª Classe",
|
||||
"Chefe de Repartição", "Chefe de Secção",
|
||||
"1º Oficial", "2º Oficial", "3º Oficial", "Oficial de Diligência",
|
||||
"Assistente Administrativo Especialista", "Assistente Administrativo Principal",
|
||||
"Assistente Administrativo",
|
||||
"Aspirante", "Escriturário Dactilógrafo", "Motorista", "Auxiliar", "Pessoal de Limpeza");
|
||||
|
||||
// Aplica a TODAS as OrgUnits do sistema (Ministérios e Secretarias)
|
||||
List<OrgUnit> allUnits = orgUnitRepository.findAll();
|
||||
for (OrgUnit unit : allUnits) {
|
||||
for (String role : adminRoles) {
|
||||
createPosition(unit, generateSlug(role), role, "Administrativo");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper Methods
|
||||
private void seedPositionsForUnit(String orgUnitCode, List<String> roles) {
|
||||
orgUnitRepository.findByCode(orgUnitCode).ifPresent(unit -> {
|
||||
for (String role : roles) {
|
||||
createPosition(unit, generateSlug(role), role, "Carreira Específica");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String generateSlug(String text) {
|
||||
if (text == null)
|
||||
return "UNK";
|
||||
|
||||
// Normalize and remove accents
|
||||
String normalized = text.replaceAll("[ÁÀÃÂÄ]", "A")
|
||||
.replaceAll("[ÉÈÊË]", "E")
|
||||
.replaceAll("[ÍÌÎÏ]", "I")
|
||||
.replaceAll("[ÓÒÕÔÖ]", "O")
|
||||
.replaceAll("[ÚÙÛÜ]", "U")
|
||||
.replaceAll("[Ç]", "C")
|
||||
.replaceAll("[áàãâä]", "a")
|
||||
.replaceAll("[éèêë]", "e")
|
||||
.replaceAll("[íìîï]", "i")
|
||||
.replaceAll("[óòõôö]", "o")
|
||||
.replaceAll("[úùûü]", "u")
|
||||
.replaceAll("[ç]", "c");
|
||||
|
||||
// Convert to CamelCase/PascalCase
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String[] parts = normalized.split("\\s+");
|
||||
for (String part : parts) {
|
||||
String p = part.replaceAll("[^a-zA-Z0-9]", "");
|
||||
if (p.length() > 0) {
|
||||
sb.append(p.substring(0, 1).toUpperCase());
|
||||
if (p.length() > 1) {
|
||||
sb.append(p.substring(1).toLowerCase());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String slug = sb.toString();
|
||||
|
||||
if (slug.length() > 30) {
|
||||
slug = slug.substring(0, 30);
|
||||
}
|
||||
return slug;
|
||||
}
|
||||
|
||||
private void createPosition(OrgUnit orgUnit, String suffix, String title, String level) {
|
||||
String code = orgUnit.getCode() + "-" + suffix;
|
||||
// Ensure uniqueness check matches the entity constraint
|
||||
if (positionRepository.findByCode(code).isEmpty()) {
|
||||
positionRepository.save(Position.builder()
|
||||
.orgUnit(orgUnit)
|
||||
.code(code)
|
||||
.title(title)
|
||||
.level(level)
|
||||
.isActive(true)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanup() {
|
||||
log.info("Limpando base de dados do Orçamento (NUCLEAR TRUNCATE)...");
|
||||
try {
|
||||
// PostgreSQL specific: TRUNCATE with CASCADE to clean everything efficiently
|
||||
// Order doesn't matter with CASCADE, but listing helps clarity
|
||||
String sql = "TRUNCATE TABLE " +
|
||||
"payment_order, " +
|
||||
"cash_account, " +
|
||||
"budget_execution, " +
|
||||
"budget_entry, " +
|
||||
"budget_line, " +
|
||||
"position, " +
|
||||
"org_unit, " +
|
||||
"ministry, " +
|
||||
"economic_classification, " +
|
||||
"bank, " +
|
||||
"salary_grid, " +
|
||||
"salary_step, " +
|
||||
"salary_grade, " +
|
||||
"salary_category, " +
|
||||
"career_regime " +
|
||||
"CASCADE";
|
||||
|
||||
jdbcTemplate.execute(sql);
|
||||
log.info("Limpeza NUCLEAR realizada com sucesso.");
|
||||
} catch (Exception e) {
|
||||
log.error("Falha na limpeza NUCLEAR. Tentando fallback para DELETE.", e);
|
||||
// Fallback just in case, though TRUNCATE should work on Postgres
|
||||
jdbcTemplate.execute("DELETE FROM payment_order");
|
||||
jdbcTemplate.execute("DELETE FROM cash_account");
|
||||
jdbcTemplate.execute("DELETE FROM budget_execution");
|
||||
jdbcTemplate.execute("DELETE FROM budget_entry");
|
||||
jdbcTemplate.execute("DELETE FROM budget_line");
|
||||
jdbcTemplate.execute("DELETE FROM position");
|
||||
jdbcTemplate.execute("DELETE FROM org_unit");
|
||||
jdbcTemplate.execute("DELETE FROM ministry");
|
||||
jdbcTemplate.execute("DELETE FROM economic_classification");
|
||||
jdbcTemplate.execute("DELETE FROM bank");
|
||||
jdbcTemplate.execute("DELETE FROM salary_grid");
|
||||
jdbcTemplate.execute("DELETE FROM salary_step");
|
||||
jdbcTemplate.execute("DELETE FROM salary_grade");
|
||||
jdbcTemplate.execute("DELETE FROM salary_category");
|
||||
jdbcTemplate.execute("DELETE FROM career_regime");
|
||||
}
|
||||
}
|
||||
|
||||
private void seedBanks() {
|
||||
log.info("Seeding Bancos...");
|
||||
createBank("BCEAO", "Banco Central dos Estados da África Ocidental", "001");
|
||||
createBank("BAO", "Banco da África Ocidental", "002");
|
||||
createBank("BDU", "Banco da União", "003");
|
||||
createBank("BIA", "Banco Internacional da África", "004");
|
||||
createBank("ECOBANK", "Ecobank Guiné-Bissau", "005");
|
||||
createBank("ORABANK", "Orabank Guiné-Bissau", "006");
|
||||
createBank("TESOURO", "Tesouro Público", "000"); // Conta Única do Tesouro (Internal)
|
||||
}
|
||||
|
||||
private void createBank(String code, String name, String bankCode) {
|
||||
if (bankRepository.findByCode(code).isEmpty()) {
|
||||
bankRepository.save(br.gov.sigefp.common.domain.Bank.builder()
|
||||
.code(code)
|
||||
.name(name)
|
||||
.swiftCode(bankCode) // Map numeric code to swiftCode for now
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
private void seedEconomicClassifications() {
|
||||
log.info("Seeding Classificações Econômicas...");
|
||||
|
||||
// Receitas
|
||||
createClassification("7111", "Contribuição predial (Urbana e Rústica)", "REVENUE", "010101");
|
||||
createClassification("712", "Imposto profissional (Função Pública e Outros)", "REVENUE", "010102");
|
||||
createClassification("7143", "Imposto de Democracia", "REVENUE", "010106");
|
||||
createClassification("7151", "Imposto Geral s/Venda (IGV/IVA)", "REVENUE", "020301");
|
||||
createClassification("7161", "Impostos de selo e estampilhas", "REVENUE", "020401");
|
||||
createClassification("7171", "Direitos de importação", "REVENUE", "020101");
|
||||
createClassification("7182", "Imposto extraordinário", "REVENUE", "020103");
|
||||
createClassification("7199", "Outros (Impostos Comunitários)", "REVENUE", "020199");
|
||||
createClassification("72143", "Licenças de Pescas", "REVENUE", "030101");
|
||||
createClassification("7232", "Juros do Sector Público", "REVENUE", "040401");
|
||||
createClassification("73124", "Segurança Social", "REVENUE", "050204");
|
||||
createClassification("72931", "Venda de serviços pelas administrações públicas", "REVENUE", "060301");
|
||||
|
||||
// Despesas - Pessoal
|
||||
createClassification("6111", "Salários do pessoal do quadro", "EXPENSE", null);
|
||||
createClassification("6112", "Salários do pessoal em qualquer outra situação", "EXPENSE", null);
|
||||
createClassification("6135", "Saúde e Indemnizações", "EXPENSE", null);
|
||||
createClassification("6139", "Outras Gratificações", "EXPENSE", null);
|
||||
createClassification("6151", "Encargos com saúde", "EXPENSE", null);
|
||||
|
||||
// Despesas - Funcionamento
|
||||
createClassification("6212", "Combustíveis e lubrificantes", "EXPENSE", null);
|
||||
createClassification("6213", "Consumo de secretaria", "EXPENSE", null);
|
||||
createClassification("6219", "Outros bens não duradouros", "EXPENSE", null);
|
||||
createClassification("6261", "Comunicações", "EXPENSE", null);
|
||||
createClassification("6271", "Locação de edifícios", "EXPENSE", null);
|
||||
createClassification("6281", "Transporte Exterior", "EXPENSE", null);
|
||||
createClassification("6283", "Ajudas de custo Exterior", "EXPENSE", null);
|
||||
createClassification("6295", "Alimentação", "EXPENSE", null);
|
||||
|
||||
// Despesas - Transferências
|
||||
createClassification("6312", "Serviços autónomos", "EXPENSE", null);
|
||||
createClassification("6422", "Associações desportivas", "EXPENSE", null);
|
||||
createClassification("6433", "Pensões provisórias de aposentação", "EXPENSE", null);
|
||||
createClassification("6434", "Pensões de aposentação, reforma, invalidade", "EXPENSE", null);
|
||||
createClassification("6499", "Outras transferências correntes", "EXPENSE", null);
|
||||
createClassification("6611", "Incentivos para a cobrança de receitas", "EXPENSE", null);
|
||||
createClassification("6691", "Outras despesas comuns", "EXPENSE", null);
|
||||
|
||||
// Investimento e Dívida
|
||||
createClassification("2311", "Construções e grandes reparações", "EXPENSE", null);
|
||||
createClassification("2411", "Mobiliário e Material de secretaria", "EXPENSE", null);
|
||||
createClassification("2431", "Material de transporte", "EXPENSE", null);
|
||||
createClassification("2441", "Maquinaria e equipamentos", "EXPENSE", null);
|
||||
createClassification("1511", "Empréstimos para projetos (Multilateral)", "EXPENSE", null);
|
||||
createClassification("6511", "Juros e custos financeiros da dívida", "EXPENSE", null);
|
||||
|
||||
// Universal Generic Backup
|
||||
createClassification("99.00.00", "Dotação Geral (Importado)", "EXPENSE", null);
|
||||
}
|
||||
|
||||
private void createClassification(String code, String description, String type, String uemoaCode) {
|
||||
if (economicClassificationRepository.findByCode(code).isEmpty()) {
|
||||
economicClassificationRepository.save(EconomicClassification.builder()
|
||||
.code(code)
|
||||
.description(description)
|
||||
.type(type)
|
||||
.uemoaCode(uemoaCode)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
private FiscalYear ensureFiscalYear2025() {
|
||||
return fiscalYearRepository.findByYear(2025)
|
||||
.orElseGet(() -> fiscalYearRepository.save(FiscalYear.builder()
|
||||
.year(2025)
|
||||
.startDate(LocalDate.of(2025, 1, 1))
|
||||
.endDate(LocalDate.of(2025, 12, 31))
|
||||
.status("OPEN")
|
||||
.build()));
|
||||
}
|
||||
|
||||
private void seedItem(OgeItemDTO item, FiscalYear fiscalYear) {
|
||||
Ministry ministry;
|
||||
OrgUnit orgUnit;
|
||||
|
||||
if ("ORG_UNIT".equals(item.getType()) && item.getMinistryCode() != null) {
|
||||
// Case 1: Subordinate Org Unit (Secretariats)
|
||||
// Find parent ministry
|
||||
ministry = ministryRepository.findByCode(item.getMinistryCode())
|
||||
.orElseThrow(() -> new RuntimeException("Ministry not found for code: " + item.getMinistryCode()));
|
||||
|
||||
// Find parent unit (Ministry Cabinet)
|
||||
OrgUnit parentUnit = orgUnitRepository.findByCode(item.getMinistryCode())
|
||||
.orElseThrow(
|
||||
() -> new RuntimeException("Parent Unit not found for code: " + item.getMinistryCode()));
|
||||
|
||||
// Create Sub-Unit
|
||||
orgUnit = orgUnitRepository.findByCode(item.getCode())
|
||||
.orElseGet(() -> orgUnitRepository.save(OrgUnit.builder()
|
||||
.ministry(ministry)
|
||||
.parentUnit(parentUnit)
|
||||
.code(item.getCode())
|
||||
.name(item.getName())
|
||||
.unitType("Secretaria de Estado")
|
||||
.isActive(true)
|
||||
.build()));
|
||||
} else {
|
||||
// Case 2: Top Level Ministry
|
||||
ministry = ministryRepository.findByCode(item.getCode())
|
||||
.orElseGet(() -> ministryRepository.save(Ministry.builder()
|
||||
.code(item.getCode())
|
||||
.name(item.getName())
|
||||
.isActive(true)
|
||||
.build()));
|
||||
|
||||
// Create Main Org Unit (Cabinet)
|
||||
Ministry finalMinistry = ministry;
|
||||
orgUnit = orgUnitRepository.findByCode(item.getCode())
|
||||
.orElseGet(() -> orgUnitRepository.save(OrgUnit.builder()
|
||||
.ministry(finalMinistry)
|
||||
.code(item.getCode())
|
||||
.name(item.getName())
|
||||
.unitType("Gabinete Ministerial")
|
||||
.isActive(true)
|
||||
.build()));
|
||||
}
|
||||
|
||||
// C. Budget Line (Generic)
|
||||
String lineCode = item.getCode() + ".GEN";
|
||||
Ministry finalMinistryForLine = ministry;
|
||||
OrgUnit finalOrgUnitForLine = orgUnit;
|
||||
|
||||
BudgetLine budgetLine = budgetLineRepository.findByCode(lineCode)
|
||||
.orElseGet(() -> budgetLineRepository.save(BudgetLine.builder()
|
||||
.fiscalYear(fiscalYear)
|
||||
.ministry(finalMinistryForLine.getId())
|
||||
.orgUnit(finalOrgUnitForLine.getId())
|
||||
.code(lineCode)
|
||||
.description("Dotação Geral - " + item.getName())
|
||||
.economicClass("99.00.00") // Generic
|
||||
.build()));
|
||||
|
||||
// D. Budget Entry (Initial Allocation)
|
||||
seedBudgetEntry(budgetLine, item.getTotalAllocated());
|
||||
}
|
||||
|
||||
private void seedBudgetEntry(BudgetLine budgetLine, BigDecimal totalAllocated) {
|
||||
// Check if entry already exists to avoid duplication
|
||||
boolean entryExists = budgetEntryRepository.findAll().stream()
|
||||
.anyMatch(be -> be.getBudgetLine().getId().equals(budgetLine.getId())
|
||||
&& be.getType() == BudgetEntryType.INITIAL_ALLOCATION);
|
||||
|
||||
if (!entryExists) {
|
||||
// Create Entry
|
||||
budgetEntryRepository.save(BudgetEntry.builder()
|
||||
.budgetLine(budgetLine)
|
||||
.type(BudgetEntryType.INITIAL_ALLOCATION)
|
||||
.amount(totalAllocated.multiply(BigDecimal.valueOf(1000))) // Convert Mil FCFA to FCFA
|
||||
.transactionDate(LocalDate.of(2025, 1, 1))
|
||||
.documentReference("Lei OGE 2025")
|
||||
.description("Dotação Inicial Importada")
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
static class OgeItemDTO {
|
||||
private String code;
|
||||
private String name;
|
||||
private BigDecimal totalAllocated;
|
||||
private String type; // MINISTRY, ORG_UNIT
|
||||
private String ministryCode; // Optional, for ORG_UNIT
|
||||
}
|
||||
|
||||
@Data
|
||||
static class SalaryGridItemDTO {
|
||||
private String code;
|
||||
private String name;
|
||||
private String baseSalary;
|
||||
private String subsidy;
|
||||
private String gross;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:postgresql://localhost:5432/sigefp_dev
|
||||
username: sigefp_dev
|
||||
password: sigefp_dev
|
||||
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: update
|
||||
show-sql: true
|
||||
|
||||
logging:
|
||||
level:
|
||||
br.gov.sigefp: DEBUG
|
||||
org.hibernate.SQL: DEBUG
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
# Configuração específica para ambiente da Guiné-Bissau
|
||||
|
||||
spring:
|
||||
datasource:
|
||||
url: ${DATABASE_URL:jdbc:postgresql://localhost:5432/sigefp_gw}
|
||||
username: ${DATABASE_USERNAME:sigefp_gw}
|
||||
password: ${DATABASE_PASSWORD}
|
||||
hikari:
|
||||
maximum-pool-size: 20
|
||||
minimum-idle: 10
|
||||
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: validate
|
||||
show-sql: false
|
||||
properties:
|
||||
hibernate:
|
||||
jdbc:
|
||||
time_zone: Africa/Bissau
|
||||
|
||||
jackson:
|
||||
serialization:
|
||||
write-dates-as-timestamps: false
|
||||
time-zone: Africa/Bissau
|
||||
locale: pt_GW
|
||||
|
||||
server:
|
||||
port: ${SERVER_PORT:8080}
|
||||
|
||||
# Configurações específicas para Guiné-Bissau
|
||||
guinea-bissau:
|
||||
country:
|
||||
code: GW
|
||||
name: Guiné-Bissau
|
||||
currency:
|
||||
code: XOF
|
||||
symbol: FCFA
|
||||
timezone: Africa/Bissau
|
||||
locale: pt_GW
|
||||
phone-code: "+245"
|
||||
date-format: "dd/MM/yyyy"
|
||||
datetime-format: "dd/MM/yyyy HH:mm"
|
||||
|
||||
logging:
|
||||
level:
|
||||
root: INFO
|
||||
br.gov.sigefp: INFO
|
||||
pattern:
|
||||
console: "%d{dd/MM/yyyy HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
|
||||
file: "%d{dd/MM/yyyy HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
spring:
|
||||
datasource:
|
||||
url: ${DATABASE_URL:jdbc:postgresql://localhost:5432/sigefp}
|
||||
username: ${DATABASE_USERNAME:sigefp_user}
|
||||
password: ${DATABASE_PASSWORD}
|
||||
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: validate
|
||||
show-sql: false
|
||||
|
||||
logging:
|
||||
level:
|
||||
root: WARN
|
||||
br.gov.sigefp: INFO
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
spring:
|
||||
application:
|
||||
name: sigefp-api
|
||||
|
||||
datasource:
|
||||
url: jdbc:postgresql://localhost:5432/sigefp
|
||||
username: postgres
|
||||
password: 'postgres' # Altere para a senha real do seu PostgreSQL
|
||||
driver-class-name: org.postgresql.Driver
|
||||
hikari:
|
||||
maximum-pool-size: 10
|
||||
minimum-idle: 5
|
||||
connection-timeout: 30000
|
||||
idle-timeout: 600000
|
||||
max-lifetime: 1800000
|
||||
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: update # validate, update, create, create-drop
|
||||
# update: cria/atualiza tabelas automaticamente (desenvolvimento)
|
||||
# validate: apenas valida se tabelas existem (produção)
|
||||
show-sql: false
|
||||
properties:
|
||||
hibernate:
|
||||
dialect: org.hibernate.dialect.PostgreSQLDialect
|
||||
format_sql: true
|
||||
jdbc:
|
||||
batch_size: 20
|
||||
order_inserts: true
|
||||
order_updates: true
|
||||
|
||||
jackson:
|
||||
serialization:
|
||||
write-dates-as-timestamps: false
|
||||
time-zone: Africa/Bissau
|
||||
locale: pt_GW
|
||||
default-property-inclusion: non_null
|
||||
|
||||
server:
|
||||
port: 8081
|
||||
servlet:
|
||||
context-path: /
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: health,info,metrics
|
||||
endpoint:
|
||||
health:
|
||||
show-details: when-authorized
|
||||
|
||||
# JWT Configuration
|
||||
jwt:
|
||||
secret: MySecretKeyForJWTTokenGenerationThatMustBeAtLeast256BitsLongForProductionUse
|
||||
expiration: 86400000 # 24 horas em milissegundos
|
||||
refresh-expiration: 604800000 # 7 dias em milissegundos
|
||||
|
||||
# SpringDoc OpenAPI (Swagger) Configuration
|
||||
springdoc:
|
||||
api-docs:
|
||||
path: /api-docs
|
||||
swagger-ui:
|
||||
path: /swagger-ui.html
|
||||
enabled: true
|
||||
operations-sorter: method
|
||||
tags-sorter: alpha
|
||||
try-it-out-enabled: true
|
||||
show-actuator: false
|
||||
|
||||
logging:
|
||||
level:
|
||||
root: INFO
|
||||
br.gov.sigefp: DEBUG
|
||||
org.springframework.security: DEBUG
|
||||
org.hibernate.SQL: DEBUG
|
||||
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
|
||||
pattern:
|
||||
console: "%d{dd/MM/yyyy HH:mm:ss} - %msg%n"
|
||||
file: "%d{dd/MM/yyyy HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
|
||||
|
||||
# Configurações específicas para Guiné-Bissau
|
||||
guinea-bissau:
|
||||
country:
|
||||
code: GW
|
||||
name: Guiné-Bissau
|
||||
currency:
|
||||
code: XOF
|
||||
symbol: FCFA
|
||||
timezone: Africa/Bissau
|
||||
locale: pt_GW
|
||||
phone-code: "+245"
|
||||
date-format: "dd/MM/yyyy"
|
||||
datetime-format: "dd/MM/yyyy HH:mm"
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
[
|
||||
{
|
||||
"code": "GW-COA-1",
|
||||
"name": "Assembleia Nacional Popular",
|
||||
"totalAllocated": 2778966,
|
||||
"type": "MINISTRY"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-2",
|
||||
"name": "Presidência da República",
|
||||
"totalAllocated": 5463499,
|
||||
"type": "MINISTRY"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-3",
|
||||
"name": "Presidência do Conselho de Ministros",
|
||||
"totalAllocated": 3266823,
|
||||
"type": "MINISTRY"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-4",
|
||||
"name": "Supremo Tribunal da Justiça",
|
||||
"totalAllocated": 1000769,
|
||||
"type": "MINISTRY"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-5",
|
||||
"name": "Tribunal de Contas",
|
||||
"totalAllocated": 641839,
|
||||
"type": "MINISTRY"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-6",
|
||||
"name": "Ministério da Justiça",
|
||||
"totalAllocated": 4501901,
|
||||
"type": "MINISTRY"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-7",
|
||||
"name": "Ministério Público",
|
||||
"totalAllocated": 1458317,
|
||||
"type": "MINISTRY"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-8",
|
||||
"name": "Ministério dos Negócios Estrangeiros, Cooperação Internacional e das Comunidades",
|
||||
"totalAllocated": 3373030,
|
||||
"type": "MINISTRY"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-9",
|
||||
"name": "Ministério da Defesa Nacional",
|
||||
"totalAllocated": 14727418,
|
||||
"type": "MINISTRY"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-10",
|
||||
"name": "Ministério do Interior",
|
||||
"totalAllocated": 7929642,
|
||||
"type": "MINISTRY"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-11",
|
||||
"name": "Ministério da Administração Territorial",
|
||||
"totalAllocated": 1183228,
|
||||
"type": "MINISTRY"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-12",
|
||||
"name": "Ministério das Finanças",
|
||||
"totalAllocated": 36027419,
|
||||
"type": "MINISTRY"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-13",
|
||||
"name": "Ministério da Administração Pública, Trabalho, Emprego e Segurança Social",
|
||||
"totalAllocated": 4912616,
|
||||
"type": "MINISTRY"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-14",
|
||||
"name": "Ministério da Economia, Plano e Integração Regional",
|
||||
"totalAllocated": 4747843,
|
||||
"type": "MINISTRY"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-15",
|
||||
"name": "Ministério da Educação e Ensino Superior",
|
||||
"totalAllocated": 33314968,
|
||||
"type": "MINISTRY"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-17",
|
||||
"name": "Ministério da Presidência de Conselho de Ministros e Assuntos Parlamentares",
|
||||
"totalAllocated": 252056,
|
||||
"type": "MINISTRY"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-18",
|
||||
"name": "Ministério da Saúde",
|
||||
"totalAllocated": 18729529,
|
||||
"type": "MINISTRY"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-19",
|
||||
"name": "Ministério da Mulher, Família e Solidariedade Social",
|
||||
"totalAllocated": 2056998,
|
||||
"type": "MINISTRY"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-21",
|
||||
"name": "Ministério das Obras Públicas, Habitação e Urbanismo",
|
||||
"totalAllocated": 23302981,
|
||||
"type": "MINISTRY"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-22",
|
||||
"name": "Ministério dos Recursos Naturais e Energia",
|
||||
"totalAllocated": 1862077,
|
||||
"type": "MINISTRY"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-23",
|
||||
"name": "Ministério da Agricultura e Desenvolvimento Rural",
|
||||
"totalAllocated": 12701738,
|
||||
"type": "MINISTRY"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-24",
|
||||
"name": "Ministério das Pescas",
|
||||
"totalAllocated": 4391120,
|
||||
"type": "MINISTRY"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-25",
|
||||
"name": "Ministério do Comércio e Indústria",
|
||||
"totalAllocated": 673322,
|
||||
"type": "MINISTRY"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-26",
|
||||
"name": "Ministério do Turismo e do Artesanato",
|
||||
"totalAllocated": 156200,
|
||||
"type": "MINISTRY"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-30",
|
||||
"name": "Secretaria de Estado das Comunidades",
|
||||
"totalAllocated": 61750,
|
||||
"type": "ORG_UNIT",
|
||||
"ministryCode": "GW-COA-8"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-31",
|
||||
"name": "Secretaria de Estado da Cooperação Internacional",
|
||||
"totalAllocated": 61200,
|
||||
"type": "ORG_UNIT",
|
||||
"ministryCode": "GW-COA-8"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-32",
|
||||
"name": "Secretaria de Estado do Tesouro",
|
||||
"totalAllocated": 196660079,
|
||||
"type": "ORG_UNIT",
|
||||
"ministryCode": "GW-COA-12"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-33",
|
||||
"name": "Secretaria de Estado do Orçamento",
|
||||
"totalAllocated": 284726,
|
||||
"type": "ORG_UNIT",
|
||||
"ministryCode": "GW-COA-12"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-35",
|
||||
"name": "Secretaria de Estado da Juventude e Desporto",
|
||||
"totalAllocated": 1531278,
|
||||
"type": "ORG_UNIT",
|
||||
"ministryCode": "GW-COA-94"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-36",
|
||||
"name": "Secretaria de Estado da Gestão Hospitalar",
|
||||
"totalAllocated": 61200,
|
||||
"type": "ORG_UNIT",
|
||||
"ministryCode": "GW-COA-18"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-38",
|
||||
"name": "Secretaria de Estado da Ordem Pública",
|
||||
"totalAllocated": 60000,
|
||||
"type": "ORG_UNIT",
|
||||
"ministryCode": "GW-COA-10"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-39",
|
||||
"name": "Secretaria de Estado dos Combatentes da Liberdade",
|
||||
"totalAllocated": 1682088,
|
||||
"type": "ORG_UNIT",
|
||||
"ministryCode": "GW-COA-9"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-43",
|
||||
"name": "Ministério dos Transportes e Comunicações",
|
||||
"totalAllocated": 1120122,
|
||||
"type": "MINISTRY"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-46",
|
||||
"name": "Secretaria de Estado do Plano e Integração Regional",
|
||||
"totalAllocated": 315543,
|
||||
"type": "ORG_UNIT",
|
||||
"ministryCode": "GW-COA-14"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-49",
|
||||
"name": "Ministério da Energia e Indústria",
|
||||
"totalAllocated": 9354235,
|
||||
"type": "MINISTRY"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-102",
|
||||
"name": "Secretaria de Estado da Presidência do Conselho de Ministros",
|
||||
"totalAllocated": 61200,
|
||||
"type": "ORG_UNIT",
|
||||
"ministryCode": "GW-COA-17"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-72",
|
||||
"name": "Ministério do Ensino Superior",
|
||||
"totalAllocated": 122400,
|
||||
"type": "MINISTRY"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-73",
|
||||
"name": "Secretaria de Estado da Cultura",
|
||||
"totalAllocated": 60000,
|
||||
"type": "ORG_UNIT",
|
||||
"ministryCode": "GW-COA-94"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-94",
|
||||
"name": "Ministério da Cultura, Juventude e Desportes",
|
||||
"totalAllocated": 140000,
|
||||
"type": "MINISTRY"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-96",
|
||||
"name": "Ministério da Comunicação Social",
|
||||
"totalAllocated": 732882,
|
||||
"type": "MINISTRY"
|
||||
},
|
||||
{
|
||||
"code": "GW-COA-99",
|
||||
"name": "Ministério do Ambiente e Biodiversidade",
|
||||
"totalAllocated": 3406910,
|
||||
"type": "MINISTRY"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,849 @@
|
||||
[
|
||||
{
|
||||
"code": "0P01",
|
||||
"name": "Presidente da República",
|
||||
"baseSalary": "2000000.00",
|
||||
"subsidy": "400000.00",
|
||||
"gross": "2400000.00"
|
||||
},
|
||||
{
|
||||
"code": "0P02",
|
||||
"name": "Presidente da Assembleia",
|
||||
"baseSalary": "1700000.00",
|
||||
"subsidy": "340000.00",
|
||||
"gross": "2040000.00"
|
||||
},
|
||||
{
|
||||
"code": "0P03",
|
||||
"name": "Primatura, PSTJ, PGR, PT Contas",
|
||||
"baseSalary": "1600000.00",
|
||||
"subsidy": "320000.00",
|
||||
"gross": "1920000.00"
|
||||
},
|
||||
{
|
||||
"code": "0P05",
|
||||
"name": "Ministros",
|
||||
"baseSalary": "1200000.00",
|
||||
"subsidy": "240000.00",
|
||||
"gross": "1440000.00"
|
||||
},
|
||||
{
|
||||
"code": "0P06",
|
||||
"name": "S. Estado, Pr e Vice-Pr Trib. Circulo, Juiz desembargador e Pr",
|
||||
"baseSalary": "1100000.00",
|
||||
"subsidy": "220000.00",
|
||||
"gross": "1320000.00"
|
||||
},
|
||||
{
|
||||
"code": "0P07",
|
||||
"name": "Governador(a) de região",
|
||||
"baseSalary": "300000.00",
|
||||
"subsidy": "60000.00",
|
||||
"gross": "360000.00"
|
||||
},
|
||||
{
|
||||
"code": "1A01",
|
||||
"name": "Sec. Geral, Juiz de Direito e Delegado, Contador Chefe",
|
||||
"baseSalary": "272000.00",
|
||||
"subsidy": "54400.00",
|
||||
"gross": "326400.00"
|
||||
},
|
||||
{
|
||||
"code": "1B01",
|
||||
"name": "Inspector Geral",
|
||||
"baseSalary": "255000.00",
|
||||
"subsidy": "51000.00",
|
||||
"gross": "306000.00"
|
||||
},
|
||||
{
|
||||
"code": "1C01",
|
||||
"name": "Dir. Geral, Juiz Sec.-Lic., Cont. Esp., Secretarias de 0P03, Pr.",
|
||||
"baseSalary": "250000.00",
|
||||
"subsidy": "50000.00",
|
||||
"gross": "300000.00"
|
||||
},
|
||||
{
|
||||
"code": "1D05",
|
||||
"name": "Chefe de Gabinete",
|
||||
"baseSalary": "230000.00",
|
||||
"subsidy": "46000.00",
|
||||
"gross": "276000.00"
|
||||
},
|
||||
{
|
||||
"code": "10",
|
||||
"name": "Assessor",
|
||||
"baseSalary": "190380.00",
|
||||
"subsidy": "38076.00",
|
||||
"gross": "228456.00"
|
||||
},
|
||||
{
|
||||
"code": "1F01",
|
||||
"name": "Administrador(a) de Sector",
|
||||
"baseSalary": "160000.00",
|
||||
"subsidy": "32000.00",
|
||||
"gross": "192000.00"
|
||||
},
|
||||
{
|
||||
"code": "2A01",
|
||||
"name": "Juiz, Delegado de Sec. Não Lic. Em Direito, Sec. Judicial, Dir. S",
|
||||
"baseSalary": "135000.00",
|
||||
"subsidy": "27000.00",
|
||||
"gross": "162000.00"
|
||||
},
|
||||
{
|
||||
"code": "2B01",
|
||||
"name": "Dir. Serviço 2, Médico (4), Sec. Tec., Cont. Ver., Escrivão STJ",
|
||||
"baseSalary": "135000.00",
|
||||
"subsidy": "27000.00",
|
||||
"gross": "162000.00"
|
||||
},
|
||||
{
|
||||
"code": "2C01",
|
||||
"name": "Dir. Serviço 3., tec. (BO), Médico (5.B), Prof. B, Escrivão Adjun",
|
||||
"baseSalary": "135000.00",
|
||||
"subsidy": "27000.00",
|
||||
"gross": "162000.00"
|
||||
},
|
||||
{
|
||||
"code": "2D01",
|
||||
"name": "Técnico especialista principal",
|
||||
"baseSalary": "100000.00",
|
||||
"subsidy": "20000.00",
|
||||
"gross": "120000.00"
|
||||
},
|
||||
{
|
||||
"code": "3A01",
|
||||
"name": "Chefe Rep. Adj. Tec., Dir. EB (CD,E), Secretarias de (Min & SE)",
|
||||
"baseSalary": "90000.00",
|
||||
"subsidy": "18000.00",
|
||||
"gross": "108000.00"
|
||||
},
|
||||
{
|
||||
"code": "3B02",
|
||||
"name": "Assessor técnico",
|
||||
"baseSalary": "85000.00",
|
||||
"subsidy": "17000.00",
|
||||
"gross": "102000.00"
|
||||
},
|
||||
{
|
||||
"code": "3B01",
|
||||
"name": "Tec. Sup. 2, Secretaria de (Min & SE), Secret. (D. Geral)",
|
||||
"baseSalary": "75000.00",
|
||||
"subsidy": "15000.00",
|
||||
"gross": "90000.00"
|
||||
},
|
||||
{
|
||||
"code": "3C01",
|
||||
"name": "Tec. Sup. Estagiário",
|
||||
"baseSalary": "73000.00",
|
||||
"subsidy": "14600.00",
|
||||
"gross": "87600.00"
|
||||
},
|
||||
{
|
||||
"code": "3D03",
|
||||
"name": "Técnico especialista",
|
||||
"baseSalary": "70000.00",
|
||||
"subsidy": "14000.00",
|
||||
"gross": "84000.00"
|
||||
},
|
||||
{
|
||||
"code": "3D02",
|
||||
"name": "Técnico superior de 2ª classe",
|
||||
"baseSalary": "65000.00",
|
||||
"subsidy": "13000.00",
|
||||
"gross": "78000.00"
|
||||
},
|
||||
{
|
||||
"code": "3D01",
|
||||
"name": "Tec. Médico",
|
||||
"baseSalary": "60000.00",
|
||||
"subsidy": "12000.00",
|
||||
"gross": "72000.00"
|
||||
},
|
||||
{
|
||||
"code": "3000",
|
||||
"name": "Técnico 1ª classe",
|
||||
"baseSalary": "58000.00",
|
||||
"subsidy": "11600.00",
|
||||
"gross": "69600.00"
|
||||
},
|
||||
{
|
||||
"code": "300",
|
||||
"name": "Técnico 2ª classe",
|
||||
"baseSalary": "56000.00",
|
||||
"subsidy": "11200.00",
|
||||
"gross": "67200.00"
|
||||
},
|
||||
{
|
||||
"code": "30",
|
||||
"name": "Chefe Secção",
|
||||
"baseSalary": "55000.00",
|
||||
"subsidy": "11000.00",
|
||||
"gross": "66000.00"
|
||||
},
|
||||
{
|
||||
"code": "3F03",
|
||||
"name": "Técnico profissional especialista principal",
|
||||
"baseSalary": "53000.00",
|
||||
"subsidy": "10600.00",
|
||||
"gross": "63600.00"
|
||||
},
|
||||
{
|
||||
"code": "3F02",
|
||||
"name": "Técnico profissional especialista",
|
||||
"baseSalary": "52000.00",
|
||||
"subsidy": "10400.00",
|
||||
"gross": "62400.00"
|
||||
},
|
||||
{
|
||||
"code": "3F01",
|
||||
"name": "1. Oficial, de diligência",
|
||||
"baseSalary": "49000.00",
|
||||
"subsidy": "9800.00",
|
||||
"gross": "58800.00"
|
||||
},
|
||||
{
|
||||
"code": "3G01",
|
||||
"name": "2. Oficial",
|
||||
"baseSalary": "47000.00",
|
||||
"subsidy": "9400.00",
|
||||
"gross": "56400.00"
|
||||
},
|
||||
{
|
||||
"code": "3H05",
|
||||
"name": "Técnico profissional 2ª classe",
|
||||
"baseSalary": "45000.00",
|
||||
"subsidy": "9000.00",
|
||||
"gross": "54000.00"
|
||||
},
|
||||
{
|
||||
"code": "3H04",
|
||||
"name": "Assistente Administrativo Especialista",
|
||||
"baseSalary": "43000.00",
|
||||
"subsidy": "8600.00",
|
||||
"gross": "51600.00"
|
||||
},
|
||||
{
|
||||
"code": "3H03",
|
||||
"name": "Assistente Administrativo principal",
|
||||
"baseSalary": "42000.00",
|
||||
"subsidy": "8400.00",
|
||||
"gross": "50400.00"
|
||||
},
|
||||
{
|
||||
"code": "3H02",
|
||||
"name": "Assistente Administrativo",
|
||||
"baseSalary": "41800.00",
|
||||
"subsidy": "8360.00",
|
||||
"gross": "50160.00"
|
||||
},
|
||||
{
|
||||
"code": "3H01",
|
||||
"name": "3. Oficial",
|
||||
"baseSalary": "41667.00",
|
||||
"subsidy": "8333.00",
|
||||
"gross": "50000.00"
|
||||
},
|
||||
{
|
||||
"code": "3I01",
|
||||
"name": "Aspirante",
|
||||
"baseSalary": "41667.00",
|
||||
"subsidy": "8333.00",
|
||||
"gross": "50000.00"
|
||||
},
|
||||
{
|
||||
"code": "3J01",
|
||||
"name": "Escriturário Dactilógrafo",
|
||||
"baseSalary": "41667.00",
|
||||
"subsidy": "8333.00",
|
||||
"gross": "50000.00"
|
||||
},
|
||||
{
|
||||
"code": "4A01",
|
||||
"name": "Pessoal não Adm.",
|
||||
"baseSalary": "41667.00",
|
||||
"subsidy": "8333.00",
|
||||
"gross": "50000.00"
|
||||
},
|
||||
{
|
||||
"code": "4B01",
|
||||
"name": "Pessoal não Adm.",
|
||||
"baseSalary": "41667.00",
|
||||
"subsidy": "8333.00",
|
||||
"gross": "50000.00"
|
||||
},
|
||||
{
|
||||
"code": "6A02",
|
||||
"name": "Presidente de Supremo Tribunal",
|
||||
"baseSalary": "1600000.00",
|
||||
"subsidy": "320000.00",
|
||||
"gross": "1920000.00"
|
||||
},
|
||||
{
|
||||
"code": "6A01",
|
||||
"name": "Proc. Gral Rep. / Pres. Trib Contas",
|
||||
"baseSalary": "1440000.00",
|
||||
"subsidy": "288000.00",
|
||||
"gross": "1728000.00"
|
||||
},
|
||||
{
|
||||
"code": "6B01",
|
||||
"name": "Vice-Pres. do STJ / Vice-Proc. Gral Rep. / Vice-Pres. Trib Contas",
|
||||
"baseSalary": "1029158.00",
|
||||
"subsidy": "205832.00",
|
||||
"gross": "1234990.00"
|
||||
},
|
||||
{
|
||||
"code": "6C01",
|
||||
"name": "Juiz Cons. STJ / P. G. A. / Juiz Cons. TC / Dir. Nac. PJ",
|
||||
"baseSalary": "785408.00",
|
||||
"subsidy": "157082.00",
|
||||
"gross": "942490.00"
|
||||
},
|
||||
{
|
||||
"code": "6D01",
|
||||
"name": "Juiz Desemb. / P. R. / D. N. A. PJ",
|
||||
"baseSalary": "687908.00",
|
||||
"subsidy": "137582.00",
|
||||
"gross": "825490.00"
|
||||
},
|
||||
{
|
||||
"code": "60",
|
||||
"name": "Juiz de Direito & Delegados >",
|
||||
"baseSalary": "3.00",
|
||||
"subsidy": "0.00",
|
||||
"gross": "3.00"
|
||||
},
|
||||
{
|
||||
"code": "6F01",
|
||||
"name": "Juiz de Direito & Delegados <=",
|
||||
"baseSalary": "3.00",
|
||||
"subsidy": "0.00",
|
||||
"gross": "3.00"
|
||||
},
|
||||
{
|
||||
"code": "6G01",
|
||||
"name": "Dir. do DIC / Dir. do LPC / Dir. do DCATE / DGPGR",
|
||||
"baseSalary": "412500.00",
|
||||
"subsidy": "82500.00",
|
||||
"gross": "495000.00"
|
||||
},
|
||||
{
|
||||
"code": "6H01",
|
||||
"name": "Perito sup. Crim. Niv. III",
|
||||
"baseSalary": "369424.00",
|
||||
"subsidy": "73885.00",
|
||||
"gross": "443309.00"
|
||||
},
|
||||
{
|
||||
"code": "6I01",
|
||||
"name": "Sec. Judicial & Sec. Tecnicos no Tribunal Sup. / Sub-Inspector",
|
||||
"baseSalary": "320826.00",
|
||||
"subsidy": "64165.00",
|
||||
"gross": "384991.00"
|
||||
},
|
||||
{
|
||||
"code": "6J01",
|
||||
"name": "Juiz sectorial & Deleg. Sectorial sem licenc.",
|
||||
"baseSalary": "275000.00",
|
||||
"subsidy": "55000.00",
|
||||
"gross": "330000.00"
|
||||
},
|
||||
{
|
||||
"code": "6K01",
|
||||
"name": "Agente de Inv. Crim. de Niv. I / Chefe G. Prisional",
|
||||
"baseSalary": "252076.00",
|
||||
"subsidy": "50415.00",
|
||||
"gross": "302491.00"
|
||||
},
|
||||
{
|
||||
"code": "6L01",
|
||||
"name": "Escrivao de Directo / Tecnicos Principais",
|
||||
"baseSalary": "229174.00",
|
||||
"subsidy": "45835.00",
|
||||
"gross": "275009.00"
|
||||
},
|
||||
{
|
||||
"code": "6M01",
|
||||
"name": "Adjunto Escrivao de Direito / Tecnico Adjunto, Agente de Inv. C",
|
||||
"baseSalary": "220000.00",
|
||||
"subsidy": "44000.00",
|
||||
"gross": "264000.00"
|
||||
},
|
||||
{
|
||||
"code": "6N01",
|
||||
"name": "Adjunto Escrivao de Direito / Tecnico Adjunto, Agente de Inv. C",
|
||||
"baseSalary": "183326.00",
|
||||
"subsidy": "36665.00",
|
||||
"gross": "219991.00"
|
||||
},
|
||||
{
|
||||
"code": "6O01",
|
||||
"name": "Oficiais de Delegacia / Escriturios Judiciais, Agente de Inv. de",
|
||||
"baseSalary": "137500.00",
|
||||
"subsidy": "27500.00",
|
||||
"gross": "165000.00"
|
||||
},
|
||||
{
|
||||
"code": "6P01",
|
||||
"name": "Chefe de serviços de segurança interna",
|
||||
"baseSalary": "119174.00",
|
||||
"subsidy": "23835.00",
|
||||
"gross": "143009.00"
|
||||
},
|
||||
{
|
||||
"code": "6Q01",
|
||||
"name": "Pessoal de segurança interna de nivel III",
|
||||
"baseSalary": "110000.00",
|
||||
"subsidy": "22000.00",
|
||||
"gross": "132000.00"
|
||||
},
|
||||
{
|
||||
"code": "5A03",
|
||||
"name": "Medico Esp. HospPr. Escalão",
|
||||
"baseSalary": "3.00",
|
||||
"subsidy": "260300.00",
|
||||
"gross": "260303.00"
|
||||
},
|
||||
{
|
||||
"code": "5A02",
|
||||
"name": "Medico Esp. HospPr. Escalão",
|
||||
"baseSalary": "2.00",
|
||||
"subsidy": "254120.00",
|
||||
"gross": "254122.00"
|
||||
},
|
||||
{
|
||||
"code": "2A05",
|
||||
"name": "Medico Esp. Hosp Pr.",
|
||||
"baseSalary": "250000.00",
|
||||
"subsidy": "50000.00",
|
||||
"gross": "300000.00"
|
||||
},
|
||||
{
|
||||
"code": "5A01",
|
||||
"name": "Medico Esp. HospPr.",
|
||||
"baseSalary": "250000.00",
|
||||
"subsidy": "50000.00",
|
||||
"gross": "300000.00"
|
||||
},
|
||||
{
|
||||
"code": "5B03",
|
||||
"name": "Medico Esp. Hosp. Escalão",
|
||||
"baseSalary": "3.00",
|
||||
"subsidy": "241967.00",
|
||||
"gross": "241970.00"
|
||||
},
|
||||
{
|
||||
"code": "5B02",
|
||||
"name": "Medico Esp. Hosp. Escalão",
|
||||
"baseSalary": "2.00",
|
||||
"subsidy": "235787.00",
|
||||
"gross": "235789.00"
|
||||
},
|
||||
{
|
||||
"code": "2B05",
|
||||
"name": "Medico Esp. Hosp.",
|
||||
"baseSalary": "230000.00",
|
||||
"subsidy": "46000.00",
|
||||
"gross": "276000.00"
|
||||
},
|
||||
{
|
||||
"code": "5B01",
|
||||
"name": "Medico Esp. Hosp.",
|
||||
"baseSalary": "230000.00",
|
||||
"subsidy": "46000.00",
|
||||
"gross": "276000.00"
|
||||
},
|
||||
{
|
||||
"code": "5C03",
|
||||
"name": "Ass. Clin. Geral/Equi Escalão",
|
||||
"baseSalary": "3.00",
|
||||
"subsidy": "202740.00",
|
||||
"gross": "202743.00"
|
||||
},
|
||||
{
|
||||
"code": "5D03",
|
||||
"name": "Medico Escalão",
|
||||
"baseSalary": "3.00",
|
||||
"subsidy": "202740.00",
|
||||
"gross": "202743.00"
|
||||
},
|
||||
{
|
||||
"code": "5C02",
|
||||
"name": "Ass. Clin. Geral/Equi Escalão",
|
||||
"baseSalary": "2.00",
|
||||
"subsidy": "196560.00",
|
||||
"gross": "196562.00"
|
||||
},
|
||||
{
|
||||
"code": "5D02",
|
||||
"name": "Medico Escalão",
|
||||
"baseSalary": "2.00",
|
||||
"subsidy": "196560.00",
|
||||
"gross": "196562.00"
|
||||
},
|
||||
{
|
||||
"code": "5C01",
|
||||
"name": "Ass. Clin. Geral/Equi",
|
||||
"baseSalary": "190380.00",
|
||||
"subsidy": "38076.00",
|
||||
"gross": "228456.00"
|
||||
},
|
||||
{
|
||||
"code": "5D01",
|
||||
"name": "Medico",
|
||||
"baseSalary": "190380.00",
|
||||
"subsidy": "38076.00",
|
||||
"gross": "228456.00"
|
||||
},
|
||||
{
|
||||
"code": "2C05",
|
||||
"name": "Ass. C. G. / Equi",
|
||||
"baseSalary": "190000.00",
|
||||
"subsidy": "38000.00",
|
||||
"gross": "228000.00"
|
||||
},
|
||||
{
|
||||
"code": "2D05",
|
||||
"name": "Medico",
|
||||
"baseSalary": "190000.00",
|
||||
"subsidy": "38000.00",
|
||||
"gross": "228000.00"
|
||||
},
|
||||
{
|
||||
"code": "5F03",
|
||||
"name": "Enfermeiro Superior Escalão",
|
||||
"baseSalary": "3.00",
|
||||
"subsidy": "147500.00",
|
||||
"gross": "147503.00"
|
||||
},
|
||||
{
|
||||
"code": "5G03",
|
||||
"name": "Tec. Sup. A. Soc. Escalão",
|
||||
"baseSalary": "3.00",
|
||||
"subsidy": "147500.00",
|
||||
"gross": "147503.00"
|
||||
},
|
||||
{
|
||||
"code": "5F02",
|
||||
"name": "Enfermeiro Superior Escalão",
|
||||
"baseSalary": "2.00",
|
||||
"subsidy": "141667.00",
|
||||
"gross": "141669.00"
|
||||
},
|
||||
{
|
||||
"code": "5G02",
|
||||
"name": "Tec. Sup. A. Soc. Escalão",
|
||||
"baseSalary": "2.00",
|
||||
"subsidy": "141667.00",
|
||||
"gross": "141669.00"
|
||||
},
|
||||
{
|
||||
"code": "5F01",
|
||||
"name": "Enfermeiro Superior",
|
||||
"baseSalary": "135000.00",
|
||||
"subsidy": "27000.00",
|
||||
"gross": "162000.00"
|
||||
},
|
||||
{
|
||||
"code": "5G01",
|
||||
"name": "Tec. Sup. A. Soc.",
|
||||
"baseSalary": "135000.00",
|
||||
"subsidy": "27000.00",
|
||||
"gross": "162000.00"
|
||||
},
|
||||
{
|
||||
"code": "2F05",
|
||||
"name": "Enf. Sup.",
|
||||
"baseSalary": "130000.00",
|
||||
"subsidy": "26000.00",
|
||||
"gross": "156000.00"
|
||||
},
|
||||
{
|
||||
"code": "2G05",
|
||||
"name": "Tec. Sup. A. Soc",
|
||||
"baseSalary": "130000.00",
|
||||
"subsidy": "26000.00",
|
||||
"gross": "156000.00"
|
||||
},
|
||||
{
|
||||
"code": "5J06",
|
||||
"name": "Enf./Pat./Especialist Escalão",
|
||||
"baseSalary": "6.00",
|
||||
"subsidy": "117020.00",
|
||||
"gross": "117026.00"
|
||||
},
|
||||
{
|
||||
"code": "5J05",
|
||||
"name": "Enf./Pat./Especialist Escalão",
|
||||
"baseSalary": "5.00",
|
||||
"subsidy": "112900.00",
|
||||
"gross": "112905.00"
|
||||
},
|
||||
{
|
||||
"code": "5J04",
|
||||
"name": "Enf./Pat./Especialist Escalão",
|
||||
"baseSalary": "4.00",
|
||||
"subsidy": "108780.00",
|
||||
"gross": "108784.00"
|
||||
},
|
||||
{
|
||||
"code": "5K06",
|
||||
"name": "Enf. Monitor Escalão",
|
||||
"baseSalary": "6.00",
|
||||
"subsidy": "107220.00",
|
||||
"gross": "107226.00"
|
||||
},
|
||||
{
|
||||
"code": "5M06",
|
||||
"name": "Enf./Pat./Geral Escalão",
|
||||
"baseSalary": "6.00",
|
||||
"subsidy": "107220.00",
|
||||
"gross": "107226.00"
|
||||
},
|
||||
{
|
||||
"code": "5J03",
|
||||
"name": "Enf./Part./Especialist 3ºEsc.",
|
||||
"baseSalary": "106300.00",
|
||||
"subsidy": "21260.00",
|
||||
"gross": "127560.00"
|
||||
},
|
||||
{
|
||||
"code": "5I02",
|
||||
"name": "Tec. Dt. Especialista Escalão",
|
||||
"baseSalary": "2.00",
|
||||
"subsidy": "106180.00",
|
||||
"gross": "106182.00"
|
||||
},
|
||||
{
|
||||
"code": "5K05",
|
||||
"name": "Enf. Monitor Escalão",
|
||||
"baseSalary": "5.00",
|
||||
"subsidy": "104700.00",
|
||||
"gross": "104705.00"
|
||||
},
|
||||
{
|
||||
"code": "5L05",
|
||||
"name": "Tec. Dt. Escalão",
|
||||
"baseSalary": "5.00",
|
||||
"subsidy": "104700.00",
|
||||
"gross": "104705.00"
|
||||
},
|
||||
{
|
||||
"code": "5M05",
|
||||
"name": "Enf./Pat./Geral Escalão",
|
||||
"baseSalary": "5.00",
|
||||
"subsidy": "104700.00",
|
||||
"gross": "104705.00"
|
||||
},
|
||||
{
|
||||
"code": "5J02",
|
||||
"name": "Enf./Pat./Especialist Escalão",
|
||||
"baseSalary": "2.00",
|
||||
"subsidy": "102100.00",
|
||||
"gross": "102102.00"
|
||||
},
|
||||
{
|
||||
"code": "5K04",
|
||||
"name": "Enf. Monitor Escalão",
|
||||
"baseSalary": "4.00",
|
||||
"subsidy": "100500.00",
|
||||
"gross": "100504.00"
|
||||
},
|
||||
{
|
||||
"code": "5L04",
|
||||
"name": "Tec. Dt. Escalão",
|
||||
"baseSalary": "4.00",
|
||||
"subsidy": "100500.00",
|
||||
"gross": "100504.00"
|
||||
},
|
||||
{
|
||||
"code": "5M04",
|
||||
"name": "Enf./Pat./Geral Escalão",
|
||||
"baseSalary": "4.00",
|
||||
"subsidy": "100500.00",
|
||||
"gross": "100504.00"
|
||||
},
|
||||
{
|
||||
"code": "2I05",
|
||||
"name": "Tdt Esp.",
|
||||
"baseSalary": "100000.00",
|
||||
"subsidy": "20000.00",
|
||||
"gross": "120000.00"
|
||||
},
|
||||
{
|
||||
"code": "2J05",
|
||||
"name": "Enf./ Part. / Esp",
|
||||
"baseSalary": "100000.00",
|
||||
"subsidy": "20000.00",
|
||||
"gross": "120000.00"
|
||||
},
|
||||
{
|
||||
"code": "5I01",
|
||||
"name": "Tec. Dt. Especialista",
|
||||
"baseSalary": "100000.00",
|
||||
"subsidy": "20000.00",
|
||||
"gross": "120000.00"
|
||||
},
|
||||
{
|
||||
"code": "5J01",
|
||||
"name": "Enf./Pat./Especialist",
|
||||
"baseSalary": "100000.00",
|
||||
"subsidy": "20000.00",
|
||||
"gross": "120000.00"
|
||||
},
|
||||
{
|
||||
"code": "5H05",
|
||||
"name": "Enfermeiro Professor Escalão",
|
||||
"baseSalary": "5.00",
|
||||
"subsidy": "96820.00",
|
||||
"gross": "96825.00"
|
||||
},
|
||||
{
|
||||
"code": "5K03",
|
||||
"name": "Enf. Monitor Escalão",
|
||||
"baseSalary": "3.00",
|
||||
"subsidy": "96300.00",
|
||||
"gross": "96303.00"
|
||||
},
|
||||
{
|
||||
"code": "5L03",
|
||||
"name": "Tec. Dt. Escalão",
|
||||
"baseSalary": "3.00",
|
||||
"subsidy": "96300.00",
|
||||
"gross": "96303.00"
|
||||
},
|
||||
{
|
||||
"code": "5M03",
|
||||
"name": "Enf./Pat./Geral Escalão",
|
||||
"baseSalary": "3.00",
|
||||
"subsidy": "96300.00",
|
||||
"gross": "96303.00"
|
||||
},
|
||||
{
|
||||
"code": "5K02",
|
||||
"name": "Enf. Monitor Escalão",
|
||||
"baseSalary": "2.00",
|
||||
"subsidy": "94200.00",
|
||||
"gross": "94202.00"
|
||||
},
|
||||
{
|
||||
"code": "5L02",
|
||||
"name": "Tec. Dt. Escalão",
|
||||
"baseSalary": "2.00",
|
||||
"subsidy": "94200.00",
|
||||
"gross": "94202.00"
|
||||
},
|
||||
{
|
||||
"code": "5M02",
|
||||
"name": "Enf./Pat./Geral Escalão",
|
||||
"baseSalary": "2.00",
|
||||
"subsidy": "94200.00",
|
||||
"gross": "94202.00"
|
||||
},
|
||||
{
|
||||
"code": "5H04",
|
||||
"name": "Enfermeiro Professor Escalão",
|
||||
"baseSalary": "4.00",
|
||||
"subsidy": "92700.00",
|
||||
"gross": "92704.00"
|
||||
},
|
||||
{
|
||||
"code": "2K05",
|
||||
"name": "Enf. Monitor",
|
||||
"baseSalary": "90000.00",
|
||||
"subsidy": "18000.00",
|
||||
"gross": "108000.00"
|
||||
},
|
||||
{
|
||||
"code": "2L05",
|
||||
"name": "Tdt",
|
||||
"baseSalary": "90000.00",
|
||||
"subsidy": "18000.00",
|
||||
"gross": "108000.00"
|
||||
},
|
||||
{
|
||||
"code": "2M05",
|
||||
"name": "Enf/ Part / Geral",
|
||||
"baseSalary": "90000.00",
|
||||
"subsidy": "18000.00",
|
||||
"gross": "108000.00"
|
||||
},
|
||||
{
|
||||
"code": "5K01",
|
||||
"name": "Enf. Monitor",
|
||||
"baseSalary": "90000.00",
|
||||
"subsidy": "18000.00",
|
||||
"gross": "108000.00"
|
||||
},
|
||||
{
|
||||
"code": "5L01",
|
||||
"name": "Tec. Dt.",
|
||||
"baseSalary": "90000.00",
|
||||
"subsidy": "18000.00",
|
||||
"gross": "108000.00"
|
||||
},
|
||||
{
|
||||
"code": "5M01",
|
||||
"name": "Enf./Pat./Geral",
|
||||
"baseSalary": "90000.00",
|
||||
"subsidy": "18000.00",
|
||||
"gross": "108000.00"
|
||||
},
|
||||
{
|
||||
"code": "5N05",
|
||||
"name": "Auxiliar 5ºEsc",
|
||||
"baseSalary": "89700.00",
|
||||
"subsidy": "17940.00",
|
||||
"gross": "107640.00"
|
||||
},
|
||||
{
|
||||
"code": "5H03",
|
||||
"name": "Enfermeiro Professor Escalão",
|
||||
"baseSalary": "3.00",
|
||||
"subsidy": "88580.00",
|
||||
"gross": "88583.00"
|
||||
},
|
||||
{
|
||||
"code": "5H02",
|
||||
"name": "Enfermeiro Professor Escalão",
|
||||
"baseSalary": "2.00",
|
||||
"subsidy": "86100.00",
|
||||
"gross": "86102.00"
|
||||
},
|
||||
{
|
||||
"code": "5N04",
|
||||
"name": "Auxiliar 4ºEsc",
|
||||
"baseSalary": "85500.00",
|
||||
"subsidy": "17100.00",
|
||||
"gross": "102600.00"
|
||||
},
|
||||
{
|
||||
"code": "5N03",
|
||||
"name": "Auxiliar 3ºEsc",
|
||||
"baseSalary": "81300.00",
|
||||
"subsidy": "16260.00",
|
||||
"gross": "97560.00"
|
||||
},
|
||||
{
|
||||
"code": "5N02",
|
||||
"name": "Auxiliar 2ºEsc",
|
||||
"baseSalary": "77100.00",
|
||||
"subsidy": "15420.00",
|
||||
"gross": "92520.00"
|
||||
},
|
||||
{
|
||||
"code": "2N05",
|
||||
"name": "Auxiliares",
|
||||
"baseSalary": "75000.00",
|
||||
"subsidy": "15000.00",
|
||||
"gross": "90000.00"
|
||||
},
|
||||
{
|
||||
"code": "5N01",
|
||||
"name": "Auxiliar 1ºEsc",
|
||||
"baseSalary": "75000.00",
|
||||
"subsidy": "15000.00",
|
||||
"gross": "90000.00"
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user