feat: otimização de performance e ajustes finais

This commit is contained in:
Idrissa Banora
2026-05-18 10:49:32 +00:00
commit 52a7c4f9cf
579 changed files with 156489 additions and 0 deletions
@@ -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
Binary file not shown.
+152
View File
@@ -0,0 +1,152 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>br.gov.sigefp</groupId>
<artifactId>sigefp-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>sigefp-api</artifactId>
<packaging>jar</packaging>
<name>SIGEFP API</name>
<description>API REST principal do sistema</description>
<dependencies>
<!-- Módulos do sistema -->
<dependency>
<groupId>br.gov.sigefp</groupId>
<artifactId>sigefp-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>br.gov.sigefp</groupId>
<artifactId>sigefp-admin</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>br.gov.sigefp</groupId>
<artifactId>sigefp-org</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>br.gov.sigefp</groupId>
<artifactId>sigefp-rh</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>br.gov.sigefp</groupId>
<artifactId>sigefp-budget</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>br.gov.sigefp</groupId>
<artifactId>sigefp-treasury</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Spring Boot Starters -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Database -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
<!-- SpringDoc OpenAPI (Swagger) -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.3.0</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -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);
}
}
@@ -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");
}
}
@@ -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());
}
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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();
}
}
@@ -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");
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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();
}
}
@@ -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());
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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,53 @@
-- 1. Ensure Deduction Types exist (With Mandatory field and Valid UUIDs)
-- IRPS
INSERT INTO deduction_type (id, created_at, updated_at, version, code, name, mandatory, economic_class_code)
VALUES ('d0000000-0000-0000-0000-000000000001'::uuid, now(), now(), 0, 'IRPS', 'Imposto Profissional (IRPS)', true, NULL)
ON CONFLICT (code) DO UPDATE SET mandatory = EXCLUDED.mandatory;
-- ID (Democracy)
INSERT INTO deduction_type (id, created_at, updated_at, version, code, name, mandatory, economic_class_code)
VALUES ('d0000000-0000-0000-0000-000000000002'::uuid, now(), now(), 0, 'ID', 'Imposto de Democracia', true, NULL)
ON CONFLICT (code) DO UPDATE SET mandatory = EXCLUDED.mandatory;
-- ID_REF (Democracy Reformados)
INSERT INTO deduction_type (id, created_at, updated_at, version, code, name, mandatory, economic_class_code)
VALUES ('d0000000-0000-0000-0000-000000000003'::uuid, now(), now(), 0, 'ID_REF', 'Imposto Democracia (Reformados)', true, NULL)
ON CONFLICT (code) DO UPDATE SET mandatory = EXCLUDED.mandatory;
-- 2. Force Clean Tax Bracket Table
DELETE FROM tax_bracket;
-- 3. Insert IRPS Brackets (Progressive)
INSERT INTO tax_bracket (id, created_at, updated_at, version, valid_from, lower_limit, upper_limit, rate_percentage, excess_deduction, fixed_amount, deduction_type_id)
VALUES
('b0000000-0000-0000-0000-000000000001'::uuid, now(), now(), 0, '2024-01-01', 0.00, 41667.00, 0.0100, 0.00, NULL, (SELECT id FROM deduction_type WHERE code = 'IRPS')),
('b0000000-0000-0000-0000-000000000002'::uuid, now(), now(), 0, '2024-01-01', 41668.00, 83333.00, 0.0600, 2083.00, NULL, (SELECT id FROM deduction_type WHERE code = 'IRPS')),
('b0000000-0000-0000-0000-000000000003'::uuid, now(), now(), 0, '2024-01-01', 83334.00, 208333.00, 0.0800, 3750.00, NULL, (SELECT id FROM deduction_type WHERE code = 'IRPS')),
('b0000000-0000-0000-0000-000000000004'::uuid, now(), now(), 0, '2024-01-01', 208334.00, 300000.00, 0.1000, 7917.00, NULL, (SELECT id FROM deduction_type WHERE code = 'IRPS')),
('b0000000-0000-0000-0000-000000000005'::uuid, now(), now(), 0, '2024-01-01', 300001.00, 400500.00, 0.1200, 13917.00, NULL, (SELECT id FROM deduction_type WHERE code = 'IRPS')),
('b0000000-0000-0000-0000-000000000006'::uuid, now(), now(), 0, '2024-01-01', 400501.00, 750000.00, 0.1400, 21927.00, NULL, (SELECT id FROM deduction_type WHERE code = 'IRPS')),
('b0000000-0000-0000-0000-000000000007'::uuid, now(), now(), 0, '2024-01-01', 750001.00, 1100000.00, 0.1600, 36927.00, NULL, (SELECT id FROM deduction_type WHERE code = 'IRPS')),
('b0000000-0000-0000-0000-000000000008'::uuid, now(), now(), 0, '2024-01-01', 1100001.00, 1500000.00, 0.1800, 58927.00, NULL, (SELECT id FROM deduction_type WHERE code = 'IRPS')),
('b0000000-0000-0000-0000-000000000009'::uuid, now(), now(), 0, '2024-01-01', 1500001.00, NULL, 0.2000, 88929.00, NULL, (SELECT id FROM deduction_type WHERE code = 'IRPS'));
-- 4. Insert Democracy Tax Brackets (Fixed Amount) - ID
INSERT INTO tax_bracket (id, created_at, updated_at, version, valid_from, lower_limit, upper_limit, rate_percentage, excess_deduction, fixed_amount, deduction_type_id)
VALUES
('b0000000-0000-0000-0000-000000000010'::uuid, now(), now(), 0, '2024-01-01', 0.00, 41667.00, NULL, NULL, 500.00, (SELECT id FROM deduction_type WHERE code = 'ID')),
('b0000000-0000-0000-0000-000000000011'::uuid, now(), now(), 0, '2024-01-01', 41668.00, 83333.00, NULL, NULL, 1000.00, (SELECT id FROM deduction_type WHERE code = 'ID')),
('b0000000-0000-0000-0000-000000000012'::uuid, now(), now(), 0, '2024-01-01', 83334.00, 208333.00, NULL, NULL, 2000.00, (SELECT id FROM deduction_type WHERE code = 'ID')),
('b0000000-0000-0000-0000-000000000013'::uuid, now(), now(), 0, '2024-01-01', 208334.00, 300000.00, NULL, NULL, 4000.00, (SELECT id FROM deduction_type WHERE code = 'ID')),
('b0000000-0000-0000-0000-000000000014'::uuid, now(), now(), 0, '2024-01-01', 300001.00, 405500.00, NULL, NULL, 6000.00, (SELECT id FROM deduction_type WHERE code = 'ID')),
('b0000000-0000-0000-0000-000000000015'::uuid, now(), now(), 0, '2024-01-01', 405501.00, 750000.00, NULL, NULL, 10000.00, (SELECT id FROM deduction_type WHERE code = 'ID')),
('b0000000-0000-0000-0000-000000000016'::uuid, now(), now(), 0, '2024-01-01', 750001.00, 1100000.00, NULL, NULL, 15000.00, (SELECT id FROM deduction_type WHERE code = 'ID')),
('b0000000-0000-0000-0000-000000000017'::uuid, now(), now(), 0, '2024-01-01', 1100001.00, 1500000.00, NULL, NULL, 17000.00, (SELECT id FROM deduction_type WHERE code = 'ID')),
('b0000000-0000-0000-0000-000000000018'::uuid, now(), now(), 0, '2024-01-01', 1500001.00, NULL, NULL, NULL, 20000.00, (SELECT id FROM deduction_type WHERE code = 'ID'));
-- 5. Insert Democracy Tax Brackets (Reformados) - ID_REF
-- 200.500 (Assuming up to 200.500)
INSERT INTO tax_bracket (id, created_at, updated_at, version, valid_from, lower_limit, upper_limit, rate_percentage, excess_deduction, fixed_amount, deduction_type_id)
VALUES
('b0000000-0000-0000-0000-000000000019'::uuid, now(), now(), 0, '2024-01-01', 0.00, 200500.00, NULL, NULL, 500.00, (SELECT id FROM deduction_type WHERE code = 'ID_REF')),
('b0000000-0000-0000-0000-000000000020'::uuid, now(), now(), 0, '2024-01-01', 200501.00, 500000.00, NULL, NULL, 1000.00, (SELECT id FROM deduction_type WHERE code = 'ID_REF')),
('b0000000-0000-0000-0000-000000000021'::uuid, now(), now(), 0, '2024-01-01', 500001.00, 1000000.00, NULL, NULL, 2000.00, (SELECT id FROM deduction_type WHERE code = 'ID_REF')),
('b0000000-0000-0000-0000-000000000022'::uuid, now(), now(), 0, '2024-01-01', 1000001.00, NULL, NULL, NULL, 3000.00, (SELECT id FROM deduction_type WHERE code = 'ID_REF'));
@@ -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"
}
]