feat: otimização de performance e ajustes finais
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
<?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-rh</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>SIGEFP RH</name>
|
||||
<description>Módulo de recursos humanos: agentes, contratos, folha de pagamento</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>br.gov.sigefp</groupId>
|
||||
<artifactId>sigefp-common</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-budget</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</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-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-ooxml</artifactId>
|
||||
<version>5.2.3</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package br.gov.sigefp.rh.api;
|
||||
|
||||
import br.gov.sigefp.rh.api.dto.AbsenceDTO;
|
||||
import br.gov.sigefp.rh.api.dto.CreateAbsenceDTO;
|
||||
import br.gov.sigefp.rh.service.AbsenceService;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/rh/absences")
|
||||
@RequiredArgsConstructor
|
||||
public class AbsenceController {
|
||||
|
||||
private final AbsenceService absenceService;
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<AbsenceDTO> create(@Valid @RequestBody CreateAbsenceDTO dto) {
|
||||
AbsenceDTO created = absenceService.create(dto);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(created);
|
||||
}
|
||||
|
||||
@GetMapping("/agent/{agentId}")
|
||||
public ResponseEntity<Page<AbsenceDTO>> findByAgent(
|
||||
@PathVariable UUID agentId,
|
||||
@RequestParam(name = "page", defaultValue = "0") int page,
|
||||
@RequestParam(name = "size", defaultValue = "20") int size,
|
||||
@RequestParam(name = "sortBy", required = false) String sortBy,
|
||||
@RequestParam(name = "sortDirection", required = false, defaultValue = "DESC") String sortDirection) {
|
||||
|
||||
Sort sort = sortBy != null
|
||||
? Sort.by(Sort.Direction.fromString(sortDirection), sortBy)
|
||||
: Sort.by(Sort.Direction.DESC, "startDate");
|
||||
|
||||
Pageable pageable = PageRequest.of(page, size, sort);
|
||||
Page<AbsenceDTO> result = absenceService.findByAgent(agentId, pageable);
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<Void> delete(@PathVariable UUID id) {
|
||||
absenceService.delete(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package br.gov.sigefp.rh.api;
|
||||
|
||||
import br.gov.sigefp.rh.domain.AgentBankAccount;
|
||||
import br.gov.sigefp.rh.repository.AgentBankAccountRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/rh/bank-accounts")
|
||||
@RequiredArgsConstructor
|
||||
public class AgentBankAccountController {
|
||||
|
||||
private final AgentBankAccountRepository repository;
|
||||
private final br.gov.sigefp.rh.service.AgentBankAccountService service;
|
||||
|
||||
@GetMapping
|
||||
public List<AgentBankAccount> findAll() {
|
||||
return repository.findAll();
|
||||
}
|
||||
|
||||
@GetMapping("/agent/{agentId}")
|
||||
public List<AgentBankAccount> findByAgent(@PathVariable UUID agentId) {
|
||||
return repository.findByAgentId(agentId);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public AgentBankAccount save(@RequestBody AgentBankAccount account) {
|
||||
return service.save(account);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public AgentBankAccount update(@PathVariable("id") UUID id, @RequestBody AgentBankAccount account) {
|
||||
account.setId(id);
|
||||
return service.update(account);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<Void> delete(@PathVariable("id") UUID id) {
|
||||
repository.deleteById(id);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package br.gov.sigefp.rh.api;
|
||||
|
||||
import br.gov.sigefp.rh.domain.AgentContract;
|
||||
import br.gov.sigefp.rh.repository.AgentContractRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/rh/contracts")
|
||||
@RequiredArgsConstructor
|
||||
public class AgentContractController {
|
||||
|
||||
private final AgentContractRepository repository;
|
||||
private final br.gov.sigefp.rh.service.AgentContractService contractService;
|
||||
|
||||
@GetMapping
|
||||
public List<AgentContract> findAll() {
|
||||
return repository.findAll();
|
||||
}
|
||||
|
||||
@GetMapping("/agent/{agentId}")
|
||||
public List<AgentContract> findByAgent(@PathVariable("agentId") UUID agentId) {
|
||||
return repository.findByAgentId(agentId);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public AgentContract save(@RequestBody AgentContract contract) {
|
||||
return contractService.saveContract(contract);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public AgentContract update(@PathVariable("id") UUID id, @RequestBody AgentContract contract) {
|
||||
contract.setId(id);
|
||||
return contractService.updateContract(contract);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<Void> delete(@PathVariable("id") UUID id) {
|
||||
repository.deleteById(id);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package br.gov.sigefp.rh.api;
|
||||
|
||||
import br.gov.sigefp.rh.api.dto.*;
|
||||
import br.gov.sigefp.rh.service.AgentService;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import java.util.List;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Controller REST para gestão de agentes.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/rh/agents")
|
||||
@RequiredArgsConstructor
|
||||
public class AgentController {
|
||||
|
||||
private final AgentService agentService;
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<AgentDTO> create(@Valid @RequestBody AgentDTO dto) {
|
||||
AgentDTO created = agentService.create(dto);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(created);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<AgentDTO> update(
|
||||
@PathVariable("id") UUID id,
|
||||
@Valid @RequestBody AgentDTO dto) {
|
||||
AgentDTO updated = agentService.update(id, dto);
|
||||
return ResponseEntity.ok(updated);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<Void> delete(@PathVariable("id") UUID id) {
|
||||
agentService.delete(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<AgentDTO> findById(@PathVariable("id") UUID id) {
|
||||
AgentDTO dto = agentService.findById(id);
|
||||
return ResponseEntity.ok(dto);
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<Page<AgentDTO>> findAll(
|
||||
@RequestParam(value = "query", required = false) String query,
|
||||
@RequestParam(value = "status", required = false) String status,
|
||||
@RequestParam(value = "ministry", required = false) UUID ministry,
|
||||
@RequestParam(value = "orgUnit", required = false) UUID orgUnit,
|
||||
@RequestParam(value = "position", required = false) UUID position,
|
||||
@RequestParam(value = "functionalSituation", required = false) String functionalSituation,
|
||||
@RequestParam(value = "appointmentType", required = false) String appointmentType,
|
||||
@RequestParam(value = "page", defaultValue = "0") int page,
|
||||
@RequestParam(value = "size", defaultValue = "20") int size,
|
||||
@RequestParam(value = "sortBy", required = false) String sortBy,
|
||||
@RequestParam(value = "sortDirection", required = false, defaultValue = "ASC") String sortDirection) {
|
||||
|
||||
Sort sort = sortBy != null
|
||||
? Sort.by(Sort.Direction.fromString(sortDirection), sortBy)
|
||||
: Sort.by(Sort.Direction.ASC, "createdAt");
|
||||
|
||||
Pageable pageable = PageRequest.of(page, size, sort);
|
||||
Page<AgentDTO> result = agentService.findAllWithFilters(
|
||||
query, status, ministry, orgUnit, position, functionalSituation, appointmentType, pageable);
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
@GetMapping("/stats")
|
||||
public ResponseEntity<AgentStatsDTO> getStats(
|
||||
@RequestParam(value = "query", required = false) String query,
|
||||
@RequestParam(value = "status", required = false) String status,
|
||||
@RequestParam(value = "ministry", required = false) UUID ministry,
|
||||
@RequestParam(value = "orgUnit", required = false) UUID orgUnit,
|
||||
@RequestParam(value = "position", required = false) UUID position,
|
||||
@RequestParam(value = "functionalSituation", required = false) String functionalSituation,
|
||||
@RequestParam(value = "appointmentType", required = false) String appointmentType) {
|
||||
System.err.println("************************************************************");
|
||||
System.err.println("API STATS REQUEST RECEIVED");
|
||||
System.err.println("Ministry: " + ministry);
|
||||
System.err.println("OrgUnit: " + orgUnit);
|
||||
System.err.println("Query: " + query);
|
||||
System.err.println("************************************************************");
|
||||
return ResponseEntity.ok(agentService.getStats(query, status, ministry, orgUnit, position, functionalSituation,
|
||||
appointmentType));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}/history")
|
||||
public ResponseEntity<List<CareerTimelineDTO>> getHistory(@PathVariable("id") UUID id) {
|
||||
return ResponseEntity.ok(agentService.getTimeline(id));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}/status-history")
|
||||
public ResponseEntity<List<AgentStatusHistoryDTO>> getStatusHistory(@PathVariable("id") UUID id) {
|
||||
return ResponseEntity.ok(agentService.getStatusHistory(id));
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
package br.gov.sigefp.rh.api;
|
||||
|
||||
import br.gov.sigefp.rh.api.dto.AttendanceRecordDTO;
|
||||
import br.gov.sigefp.rh.api.dto.MonthlyAttendanceSheetDTO;
|
||||
import br.gov.sigefp.rh.domain.AttendanceRecord;
|
||||
import br.gov.sigefp.rh.domain.MonthlyAttendanceSheet;
|
||||
import br.gov.sigefp.rh.repository.AttendanceRecordRepository;
|
||||
import br.gov.sigefp.rh.service.AttendanceService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/rh/attendance")
|
||||
@RequiredArgsConstructor
|
||||
public class AttendanceController {
|
||||
|
||||
private final AttendanceService attendanceService;
|
||||
private final AttendanceRecordRepository recordRepository;
|
||||
|
||||
@GetMapping("/sheet")
|
||||
public ResponseEntity<MonthlyAttendanceSheetDTO> getSheet(
|
||||
@RequestParam UUID orgUnitId,
|
||||
@RequestParam int month,
|
||||
@RequestParam int year) {
|
||||
|
||||
MonthlyAttendanceSheet sheet = attendanceService.getOrCreateSheet(orgUnitId, month, year);
|
||||
return ResponseEntity.ok(toSummaryDTO(sheet));
|
||||
}
|
||||
|
||||
@GetMapping("/sheet/{sheetId}/records")
|
||||
public ResponseEntity<List<AttendanceRecordDTO>> getRecords(@PathVariable UUID sheetId) {
|
||||
List<AttendanceRecord> records = recordRepository.findBySheetId(sheetId);
|
||||
return ResponseEntity.ok(records.stream().map(this::toRecordDTO).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@PostMapping("/sheet/{sheetId}/approve")
|
||||
public ResponseEntity<Void> approveSheet(@PathVariable UUID sheetId) {
|
||||
// TODO: Extract user from Security Context
|
||||
attendanceService.approveSheet(sheetId, "Admin User");
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@PostMapping("/sheet/{sheetId}/reopen")
|
||||
public ResponseEntity<Void> reopenSheet(@PathVariable UUID sheetId) {
|
||||
attendanceService.reopenSheet(sheetId);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@PostMapping("/sheet/{sheetId}/upload")
|
||||
public ResponseEntity<Void> uploadExcel(@PathVariable UUID sheetId, @RequestParam("file") MultipartFile file) {
|
||||
attendanceService.importExcel(sheetId, file);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
private MonthlyAttendanceSheetDTO toSummaryDTO(MonthlyAttendanceSheet sheet) {
|
||||
return MonthlyAttendanceSheetDTO.builder()
|
||||
.id(sheet.getId())
|
||||
.orgUnitId(sheet.getOrgUnitId())
|
||||
.month(sheet.getMonth())
|
||||
.year(sheet.getYear())
|
||||
.status(sheet.getStatus())
|
||||
.approvedBy(sheet.getApprovedBy())
|
||||
.build();
|
||||
}
|
||||
|
||||
private AttendanceRecordDTO toRecordDTO(AttendanceRecord record) {
|
||||
return AttendanceRecordDTO.builder()
|
||||
.id(record.getId())
|
||||
.agentId(record.getAgent().getId())
|
||||
.agentName(record.getAgent().getFullName())
|
||||
.date(record.getDate())
|
||||
.type(record.getType())
|
||||
.observation(record.getObservation())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package br.gov.sigefp.rh.api;
|
||||
|
||||
import br.gov.sigefp.rh.api.dto.CareerRegimeDTO;
|
||||
import br.gov.sigefp.rh.service.CareerRegimeService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/rh/career-regimes")
|
||||
@RequiredArgsConstructor
|
||||
public class CareerRegimeController {
|
||||
|
||||
private final CareerRegimeService service;
|
||||
|
||||
@GetMapping
|
||||
public List<CareerRegimeDTO> findAll() {
|
||||
return service.findAll();
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public CareerRegimeDTO create(@RequestBody CareerRegimeDTO dto) {
|
||||
return service.create(dto);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public CareerRegimeDTO update(@PathVariable java.util.UUID id, @RequestBody CareerRegimeDTO dto) {
|
||||
return service.update(id, dto);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public void delete(@PathVariable java.util.UUID id) {
|
||||
service.delete(id);
|
||||
}
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
package br.gov.sigefp.rh.api;
|
||||
|
||||
import br.gov.sigefp.rh.api.dto.AgentImportResultDTO;
|
||||
import br.gov.sigefp.rh.service.AgentImportService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/rh/agents/imports")
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class ImportController {
|
||||
|
||||
private final AgentImportService importService;
|
||||
|
||||
@PostMapping("/excel")
|
||||
@PreAuthorize("hasRole('HR_ADMIN')")
|
||||
public ResponseEntity<AgentImportResultDTO> importAgents(@RequestParam("file") MultipartFile file) {
|
||||
if (file.isEmpty()) {
|
||||
return ResponseEntity.badRequest().body(
|
||||
AgentImportResultDTO.builder()
|
||||
.errors(java.util.List.of("Arquivo está vazio."))
|
||||
.build());
|
||||
}
|
||||
|
||||
log.info("Iniciando importação de agentes a partir do arquivo: {}", file.getOriginalFilename());
|
||||
AgentImportResultDTO result = importService.importAgents(file);
|
||||
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
@GetMapping("/template")
|
||||
public ResponseEntity<byte[]> getTemplate() throws java.io.IOException {
|
||||
try (org.apache.poi.xssf.usermodel.XSSFWorkbook workbook = new org.apache.poi.xssf.usermodel.XSSFWorkbook()) {
|
||||
org.apache.poi.ss.usermodel.Sheet sheet = workbook.createSheet("Agentes");
|
||||
|
||||
// Header style
|
||||
org.apache.poi.ss.usermodel.CellStyle headerStyle = workbook.createCellStyle();
|
||||
org.apache.poi.ss.usermodel.Font headerFont = workbook.createFont();
|
||||
headerFont.setBold(true);
|
||||
headerStyle.setFont(headerFont);
|
||||
headerStyle.setFillForegroundColor(org.apache.poi.ss.usermodel.IndexedColors.GREY_25_PERCENT.getIndex());
|
||||
headerStyle.setFillPattern(org.apache.poi.ss.usermodel.FillPatternType.SOLID_FOREGROUND);
|
||||
|
||||
// Date cell style (formato texto para evitar conversão automática do Excel)
|
||||
org.apache.poi.ss.usermodel.CellStyle textStyle = workbook.createCellStyle();
|
||||
org.apache.poi.ss.usermodel.DataFormat textFormat = workbook.createDataFormat();
|
||||
textStyle.setDataFormat(textFormat.getFormat("@")); // Formato texto
|
||||
|
||||
org.apache.poi.ss.usermodel.Row header = sheet.createRow(0);
|
||||
|
||||
String[] columns = {
|
||||
"NOME", "NIF", "MATRICULA", "BI", "DATA_NASCIMENTO", "SEXO", "ESTADO_CIVIL",
|
||||
"HABILITACAO_LITERARIA", "CATEGORIA_CODIGO", "UNIDADE_ORG_CODIGO",
|
||||
"NOMEACAO", "TIPO_CONTRATO", "DATA_INICIO_CONTRATO", "DATA_FIM_CONTRATO",
|
||||
"BANCO_CODIGO", "IBAN"
|
||||
};
|
||||
|
||||
// Colunas de data: 4 (DATA_NASCIMENTO), 10 (NOMEACAO), 12 (DATA_INICIO), 13
|
||||
// (DATA_FIM)
|
||||
int[] dateColumns = { 4, 10, 12, 13 };
|
||||
|
||||
for (int i = 0; i < columns.length; i++) {
|
||||
org.apache.poi.ss.usermodel.Cell cell = header.createCell(i);
|
||||
cell.setCellValue(columns[i]);
|
||||
cell.setCellStyle(headerStyle);
|
||||
sheet.setColumnWidth(i, 4500); // Largura padrão
|
||||
}
|
||||
|
||||
// Formatar colunas de data como texto para toda a coluna
|
||||
for (int dateCol : dateColumns) {
|
||||
sheet.setDefaultColumnStyle(dateCol, textStyle);
|
||||
}
|
||||
|
||||
// Sample Row
|
||||
org.apache.poi.ss.usermodel.Row sample = sheet.createRow(1);
|
||||
sample.createCell(0).setCellValue("Exemplo João Silva");
|
||||
sample.createCell(1).setCellValue("999999999");
|
||||
sample.createCell(2).setCellValue("MAT-001");
|
||||
sample.createCell(3).setCellValue("12345678");
|
||||
|
||||
// Datas como texto para evitar problemas de formatação
|
||||
org.apache.poi.ss.usermodel.Cell dataNasc = sample.createCell(4);
|
||||
dataNasc.setCellValue("01/01/1980");
|
||||
dataNasc.setCellStyle(textStyle);
|
||||
|
||||
sample.createCell(5).setCellValue("M");
|
||||
sample.createCell(6).setCellValue("SOLTEIRO");
|
||||
sample.createCell(7).setCellValue("LICENCIATURA");
|
||||
sample.createCell(8).setCellValue("");
|
||||
sample.createCell(9).setCellValue("");
|
||||
|
||||
org.apache.poi.ss.usermodel.Cell nomeacao = sample.createCell(10);
|
||||
nomeacao.setCellValue("15/01/2024");
|
||||
nomeacao.setCellStyle(textStyle);
|
||||
|
||||
sample.createCell(11).setCellValue("NOMEACAO");
|
||||
|
||||
org.apache.poi.ss.usermodel.Cell dataInicio = sample.createCell(12);
|
||||
dataInicio.setCellValue("01/01/2024");
|
||||
dataInicio.setCellStyle(textStyle);
|
||||
|
||||
org.apache.poi.ss.usermodel.Cell dataFim = sample.createCell(13);
|
||||
dataFim.setCellValue("");
|
||||
dataFim.setCellStyle(textStyle);
|
||||
|
||||
sample.createCell(14).setCellValue("");
|
||||
sample.createCell(15).setCellValue("");
|
||||
|
||||
java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream();
|
||||
workbook.write(out);
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.header(org.springframework.http.HttpHeaders.CONTENT_DISPOSITION,
|
||||
"attachment; filename=template_agentes.xlsx")
|
||||
.contentType(org.springframework.http.MediaType
|
||||
.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
|
||||
.body(out.toByteArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
package br.gov.sigefp.rh.api;
|
||||
|
||||
import br.gov.sigefp.common.exception.BusinessException;
|
||||
import br.gov.sigefp.rh.api.dto.CreatePayrollPeriodDTO;
|
||||
import br.gov.sigefp.rh.api.dto.CreatePayrollRunDTO;
|
||||
import br.gov.sigefp.rh.api.dto.PayrollPeriodDTO;
|
||||
import br.gov.sigefp.rh.api.dto.PayrollRunDTO;
|
||||
import br.gov.sigefp.rh.service.PayrollService;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Controller REST para gestão de folha de pagamento.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/rh")
|
||||
@RequiredArgsConstructor
|
||||
public class PayrollController {
|
||||
|
||||
private final PayrollService payrollService;
|
||||
|
||||
@GetMapping("/payroll-periods")
|
||||
public ResponseEntity<List<PayrollPeriodDTO>> findAllPeriods() {
|
||||
List<PayrollPeriodDTO> periods = payrollService.findAllPeriods();
|
||||
return ResponseEntity.ok(periods);
|
||||
}
|
||||
|
||||
@PostMapping("/payroll-periods")
|
||||
public ResponseEntity<PayrollPeriodDTO> createPeriod(@Valid @RequestBody CreatePayrollPeriodDTO dto) {
|
||||
try {
|
||||
PayrollPeriodDTO created = payrollService.createPeriod(dto);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(created);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
}
|
||||
|
||||
@PutMapping("/payroll-periods/{id}/status")
|
||||
public ResponseEntity<PayrollPeriodDTO> updatePeriodStatus(
|
||||
@PathVariable UUID id,
|
||||
@RequestParam String status) {
|
||||
try {
|
||||
PayrollPeriodDTO updated = payrollService.updatePeriodStatus(id, status);
|
||||
return ResponseEntity.ok(updated);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/payroll-runs")
|
||||
public ResponseEntity<List<PayrollRunDTO>> findAllPayrollRuns() {
|
||||
List<PayrollRunDTO> runs = payrollService.findAllPayrollRuns();
|
||||
return ResponseEntity.ok(runs);
|
||||
}
|
||||
|
||||
@PostMapping("/payroll-runs")
|
||||
public ResponseEntity<PayrollRunDTO> createPayrollRun(@Valid @RequestBody CreatePayrollRunDTO dto) {
|
||||
try {
|
||||
PayrollRunDTO created = payrollService.createPayrollRun(dto);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(created);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/payroll-runs/{id}")
|
||||
public ResponseEntity<PayrollRunDTO> findPayrollRunById(@PathVariable("id") UUID id) {
|
||||
try {
|
||||
PayrollRunDTO dto = payrollService.findPayrollRunById(id);
|
||||
return ResponseEntity.ok(dto);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processa uma execução de folha, criando execuções orçamentárias.
|
||||
* POST /api/rh/payroll-runs/{id}/process
|
||||
*/
|
||||
@PostMapping("/payroll-runs/{id}/process")
|
||||
public ResponseEntity<Void> processPayrollRun(@PathVariable("id") UUID id) {
|
||||
payrollService.processPayrollRun(id);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gera automaticamente os itens de folha para a execução.
|
||||
* POST /api/rh/payroll-runs/{id}/generate
|
||||
*/
|
||||
@PostMapping("/payroll-runs/{id}/generate")
|
||||
public ResponseEntity<Void> generatePayrollItems(@PathVariable("id") UUID id) {
|
||||
payrollService.generatePayrollItems(id);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encerra definitivamente uma execução de folha.
|
||||
* POST /api/rh/payroll-runs/{id}/close
|
||||
*/
|
||||
@PostMapping("/payroll-runs/{id}/close")
|
||||
public ResponseEntity<Void> closePayrollRun(@PathVariable("id") UUID id) {
|
||||
payrollService.closePayrollRun(id);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Exclui uma execução de folha (apenas se status PENDING ou GENERATED).
|
||||
* DELETE /api/rh/payroll-runs/{id}
|
||||
*/
|
||||
@DeleteMapping("/payroll-runs/{id}")
|
||||
public ResponseEntity<Void> deletePayrollRun(@PathVariable("id") UUID id) {
|
||||
payrollService.deletePayrollRun(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Diagnóstico: Verifica todos os requisitos para geração de itens.
|
||||
* GET /api/rh/payroll-runs/{id}/diagnose
|
||||
*/
|
||||
@GetMapping("/payroll-runs/{id}/diagnose")
|
||||
public ResponseEntity<java.util.Map<String, Object>> diagnosePayrollRun(@PathVariable("id") UUID id) {
|
||||
try {
|
||||
java.util.Map<String, Object> diagnosis = payrollService.diagnosePayrollRun(id);
|
||||
return ResponseEntity.ok(diagnosis);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package br.gov.sigefp.rh.api;
|
||||
|
||||
import br.gov.sigefp.rh.api.dto.PerformanceEvaluationDTO;
|
||||
import br.gov.sigefp.rh.service.PerformanceEvaluationService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Controller REST para gestão de avaliações de desempenho.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/rh/evaluations")
|
||||
@RequiredArgsConstructor
|
||||
public class PerformanceEvaluationController {
|
||||
|
||||
private final PerformanceEvaluationService evaluationService;
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<Page<PerformanceEvaluationDTO>> findAll(
|
||||
@RequestParam(value = "page", defaultValue = "0") int page,
|
||||
@RequestParam(value = "size", defaultValue = "20") int size,
|
||||
@RequestParam(value = "sortBy", required = false) String sortBy,
|
||||
@RequestParam(value = "sortDirection", required = false, defaultValue = "DESC") String sortDirection) {
|
||||
|
||||
Sort sort = sortBy != null
|
||||
? Sort.by(Sort.Direction.fromString(sortDirection), sortBy)
|
||||
: Sort.by(Sort.Direction.DESC, "evaluationDate");
|
||||
|
||||
Pageable pageable = PageRequest.of(page, size, sort);
|
||||
Page<PerformanceEvaluationDTO> result = evaluationService.findAll(pageable);
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/finalize")
|
||||
public ResponseEntity<Void> finalizeEvaluation(@PathVariable UUID id) {
|
||||
evaluationService.finalizeEvaluation(id);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
package br.gov.sigefp.rh.api;
|
||||
|
||||
import br.gov.sigefp.rh.api.dto.*;
|
||||
import br.gov.sigefp.rh.service.SalaryStructureService;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/rh/salary-structure")
|
||||
@RequiredArgsConstructor
|
||||
public class SalaryStructureController {
|
||||
|
||||
private final SalaryStructureService service;
|
||||
|
||||
@GetMapping("/summary")
|
||||
public ResponseEntity<SalaryStructureFullDTO> getFullStructure() {
|
||||
return ResponseEntity.ok(service.getFullStructure());
|
||||
}
|
||||
|
||||
// --- Categories ---
|
||||
@GetMapping("/categories")
|
||||
public ResponseEntity<List<SalaryCategoryDTO>> getAllCategories() {
|
||||
return ResponseEntity.ok(service.getAllCategories());
|
||||
}
|
||||
|
||||
@PostMapping("/categories")
|
||||
public ResponseEntity<SalaryCategoryDTO> createCategory(@Valid @RequestBody SalaryCategoryDTO dto) {
|
||||
return ResponseEntity.ok(service.createCategory(dto));
|
||||
}
|
||||
|
||||
@PutMapping("/categories/{id}")
|
||||
public ResponseEntity<SalaryCategoryDTO> updateCategory(@PathVariable("id") UUID id,
|
||||
@Valid @RequestBody SalaryCategoryDTO dto) {
|
||||
return ResponseEntity.ok(service.updateCategory(id, dto));
|
||||
}
|
||||
|
||||
@DeleteMapping("/categories/{id}")
|
||||
public ResponseEntity<Void> deleteCategory(@PathVariable("id") UUID id) {
|
||||
service.deleteCategory(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
// --- Grades ---
|
||||
@GetMapping("/categories/{categoryId}/grades")
|
||||
public ResponseEntity<List<SalaryGradeDTO>> getGradesByCategory(@PathVariable("categoryId") UUID categoryId) {
|
||||
return ResponseEntity.ok(service.getGradesByCategory(categoryId));
|
||||
}
|
||||
|
||||
@PostMapping("/grades")
|
||||
public ResponseEntity<SalaryGradeDTO> createGrade(@Valid @RequestBody SalaryGradeDTO dto) {
|
||||
return ResponseEntity.ok(service.createGrade(dto));
|
||||
}
|
||||
|
||||
@PutMapping("/grades/{id}")
|
||||
public ResponseEntity<SalaryGradeDTO> updateGrade(@PathVariable("id") UUID id,
|
||||
@Valid @RequestBody SalaryGradeDTO dto) {
|
||||
return ResponseEntity.ok(service.updateGrade(id, dto));
|
||||
}
|
||||
|
||||
@DeleteMapping("/grades/{id}")
|
||||
public ResponseEntity<Void> deleteGrade(@PathVariable("id") UUID id) {
|
||||
service.deleteGrade(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
// --- Steps ---
|
||||
@GetMapping("/grades/{gradeId}/steps")
|
||||
public ResponseEntity<List<SalaryStepDTO>> getStepsByGrade(@PathVariable("gradeId") UUID gradeId) {
|
||||
return ResponseEntity.ok(service.getStepsByGrade(gradeId));
|
||||
}
|
||||
|
||||
@PostMapping("/steps")
|
||||
public ResponseEntity<SalaryStepDTO> createStep(@Valid @RequestBody SalaryStepDTO dto) {
|
||||
return ResponseEntity.ok(service.createStep(dto));
|
||||
}
|
||||
|
||||
@DeleteMapping("/steps/{id}")
|
||||
public ResponseEntity<Void> deleteStep(@PathVariable("id") UUID id) {
|
||||
service.deleteStep(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
// --- Grid ---
|
||||
@GetMapping("/steps/{stepId}/grid")
|
||||
public ResponseEntity<List<SalaryGridDTO>> getGridByStep(@PathVariable("stepId") UUID stepId) {
|
||||
return ResponseEntity.ok(service.getGridByStep(stepId));
|
||||
}
|
||||
|
||||
@PostMapping("/grid")
|
||||
public ResponseEntity<SalaryGridDTO> createGridEntry(@Valid @RequestBody SalaryGridDTO dto) {
|
||||
return ResponseEntity.ok(service.createGridEntry(dto));
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package br.gov.sigefp.rh.api;
|
||||
|
||||
import br.gov.sigefp.rh.domain.GlobalDeductionRule;
|
||||
import br.gov.sigefp.rh.domain.TaxBracket;
|
||||
import br.gov.sigefp.rh.repository.TaxBracketRepository;
|
||||
import br.gov.sigefp.rh.service.TaxService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Controller para parametrização de impostos e regras tributárias.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/rh/taxes")
|
||||
@RequiredArgsConstructor
|
||||
public class TaxController {
|
||||
|
||||
private final TaxService taxService;
|
||||
private final TaxBracketRepository taxBracketRepository;
|
||||
|
||||
@GetMapping("/rules")
|
||||
public List<GlobalDeductionRule> findAllRules() {
|
||||
return taxService.findAllRules();
|
||||
}
|
||||
|
||||
@PostMapping("/rules")
|
||||
public GlobalDeductionRule saveRule(@RequestBody GlobalDeductionRule rule) {
|
||||
return taxService.saveRule(rule);
|
||||
}
|
||||
|
||||
@DeleteMapping("/rules/{id}")
|
||||
public ResponseEntity<Void> deleteRule(@PathVariable UUID id) {
|
||||
taxService.deleteRule(id);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@GetMapping("/brackets")
|
||||
public List<TaxBracket> findAllBrackets() {
|
||||
return taxService.findAllBrackets();
|
||||
}
|
||||
|
||||
@PostMapping("/brackets")
|
||||
public TaxBracket saveBracket(@RequestBody TaxBracket bracket) {
|
||||
return taxService.saveBracket(bracket);
|
||||
}
|
||||
|
||||
@DeleteMapping("/brackets/{id}")
|
||||
public ResponseEntity<Void> deleteBracket(@PathVariable UUID id) {
|
||||
taxService.deleteBracket(id);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@GetMapping("/brackets/active")
|
||||
public List<TaxBracket> getActiveBrackets() {
|
||||
return taxBracketRepository.findActiveBrackets(LocalDate.now());
|
||||
}
|
||||
|
||||
@GetMapping("/types")
|
||||
public List<br.gov.sigefp.rh.domain.DeductionType> getDeductionTypes() {
|
||||
return taxService.findAllDeductionTypes();
|
||||
}
|
||||
|
||||
@PostMapping("/types")
|
||||
public br.gov.sigefp.rh.domain.DeductionType saveDeductionType(
|
||||
@RequestBody br.gov.sigefp.rh.domain.DeductionType type) {
|
||||
return taxService.saveDeductionType(type);
|
||||
}
|
||||
|
||||
@PutMapping("/types/{id}/toggle")
|
||||
public void toggleDeductionType(@PathVariable UUID id) {
|
||||
taxService.toggleDeductionTypeStatus(id);
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package br.gov.sigefp.rh.api.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AbsenceDTO {
|
||||
private UUID id;
|
||||
private UUID agentId;
|
||||
private String agentName;
|
||||
private LocalDate startDate;
|
||||
private LocalDate endDate;
|
||||
private Integer days;
|
||||
private String reason;
|
||||
private boolean justified;
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package br.gov.sigefp.rh.api.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* DTO para transferência de dados de conta bancária do agente.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AgentBankAccountDTO {
|
||||
|
||||
private UUID id;
|
||||
private String bank;
|
||||
private String branchCode;
|
||||
private String accountNumber;
|
||||
private Boolean isPrimary;
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package br.gov.sigefp.rh.api.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* DTO para transferência de dados de contrato do agente.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AgentContractDTO {
|
||||
|
||||
private UUID id;
|
||||
private String contractType;
|
||||
private LocalDate startDate;
|
||||
private LocalDate endDate;
|
||||
private BigDecimal weeklyHours;
|
||||
private BigDecimal baseSalaryRef;
|
||||
|
||||
// Referências para estrutura organizacional e salarial
|
||||
private UUID orgUnit;
|
||||
private UUID position;
|
||||
private UUID salaryCategory;
|
||||
private UUID salaryGrade;
|
||||
private UUID salaryStep;
|
||||
|
||||
private String legalActReference;
|
||||
private Boolean isActive;
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
package br.gov.sigefp.rh.api.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* DTO para transferência de dados de agente.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AgentDTO {
|
||||
|
||||
private UUID id;
|
||||
|
||||
@NotBlank(message = "Matrícula é obrigatória")
|
||||
@Size(min = 1, max = 20, message = "Matrícula deve ter entre 1 e 20 caracteres")
|
||||
private String matricula;
|
||||
|
||||
@NotBlank(message = "NIF é obrigatório")
|
||||
@Size(min = 9, max = 20, message = "NIF deve ter entre 9 e 20 caracteres")
|
||||
private String nif;
|
||||
|
||||
@NotBlank(message = "Número do BI é obrigatório")
|
||||
@Size(min = 1, max = 20, message = "Número do BI deve ter entre 1 e 20 caracteres")
|
||||
private String biNumber;
|
||||
|
||||
@NotBlank(message = "Nome completo é obrigatório")
|
||||
@Size(max = 200, message = "Nome completo deve ter no máximo 200 caracteres")
|
||||
private String fullName;
|
||||
|
||||
@NotNull(message = "Data de nascimento é obrigatória")
|
||||
private LocalDate birthDate;
|
||||
|
||||
@NotNull(message = "Data de admissão é obrigatória")
|
||||
private LocalDate hireDate;
|
||||
|
||||
private LocalDate posseDate;
|
||||
|
||||
private LocalDate terminationDate;
|
||||
|
||||
private String appointmentType;
|
||||
|
||||
private String functionalSituation;
|
||||
|
||||
private Integer eligibleDependentsCount;
|
||||
|
||||
@Size(max = 20, message = "Status deve ter no máximo 20 caracteres")
|
||||
private String status;
|
||||
|
||||
private String literaryQualification;
|
||||
|
||||
// DEPRECATED: Estes campos agora estão em AgentContract
|
||||
@Deprecated
|
||||
private UUID salaryCategory;
|
||||
@Deprecated
|
||||
private UUID salaryGrade;
|
||||
@Deprecated
|
||||
private UUID salaryStep;
|
||||
@Deprecated
|
||||
private UUID orgUnit;
|
||||
@Deprecated
|
||||
private UUID position;
|
||||
|
||||
@Size(max = 50, message = "Nacionalidade deve ter no máximo 50 caracteres")
|
||||
private String nationality;
|
||||
|
||||
@Size(max = 20, message = "Telefone deve ter no máximo 20 caracteres")
|
||||
private String phone;
|
||||
|
||||
@Size(max = 100, message = "Email deve ter no máximo 100 caracteres")
|
||||
private String email;
|
||||
|
||||
@Size(max = 500, message = "Endereço deve ter no máximo 500 caracteres")
|
||||
private String address;
|
||||
|
||||
@Size(max = 500, message = "Motivo da alteração de status deve ter no máximo 500 caracteres")
|
||||
private String statusChangeReason;
|
||||
|
||||
// Campos adicionais para Vida Laboral durante a Edição
|
||||
private LocalDate eventEffectiveDate;
|
||||
private LocalDate eventPublicationDate;
|
||||
private String eventDocumentRef;
|
||||
|
||||
// Novos campos para incluir dados relacionados
|
||||
private AgentContractDTO activeContract;
|
||||
private AgentBankAccountDTO primaryBankAccount;
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package br.gov.sigefp.rh.api.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AgentImportDTO {
|
||||
// Dados Pessoais
|
||||
private String nome;
|
||||
private String nif;
|
||||
private String matricula; // Número de matrícula único
|
||||
private String bi;
|
||||
private String dataNascimento; // dd/MM/yyyy
|
||||
private String sexo; // M/F
|
||||
private String estadoCivil; // SOLTEIRO, CASADO
|
||||
private String habilitacaoLiteraria; // Code
|
||||
|
||||
// Dados Funcionais
|
||||
private String categoriaCodigo;
|
||||
private String unidadeOrgCodigo;
|
||||
private String nomeacao; // Data de nomeação dd/MM/yyyy
|
||||
|
||||
// Dados do Contrato (Opcional)
|
||||
private String tipoContrato; // NOMEACAO, etc.
|
||||
private String dataInicioContrato; // dd/MM/yyyy
|
||||
private String dataFimContrato; // dd/MM/yyyy
|
||||
|
||||
// Dados Bancários (Opcional)
|
||||
private String bancoCodigo;
|
||||
private String iban;
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package br.gov.sigefp.rh.api.dto;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class AgentImportResultDTO {
|
||||
private int totalProcessed;
|
||||
private int successCount;
|
||||
private int failureCount;
|
||||
|
||||
@Builder.Default
|
||||
private List<String> errors = new ArrayList<>();
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package br.gov.sigefp.rh.api.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AgentStatsDTO {
|
||||
private long total;
|
||||
private long active;
|
||||
private long inactive;
|
||||
private long suspended;
|
||||
private long terminated;
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package br.gov.sigefp.rh.api.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AgentStatusHistoryDTO {
|
||||
|
||||
private UUID id;
|
||||
private String previousStatus;
|
||||
private String newStatus;
|
||||
private String previousFunctionalSituation;
|
||||
private String newFunctionalSituation;
|
||||
private String eventType;
|
||||
private String reason;
|
||||
private LocalDateTime changedAt;
|
||||
private String changedBy;
|
||||
private String changeLog;
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package br.gov.sigefp.rh.api.dto;
|
||||
|
||||
import br.gov.sigefp.rh.domain.AttendanceType;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class AttendanceRecordDTO {
|
||||
private UUID id;
|
||||
private UUID agentId;
|
||||
private String agentName;
|
||||
private LocalDate date;
|
||||
private AttendanceType type;
|
||||
private String observation;
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package br.gov.sigefp.rh.api.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CareerRegimeDTO {
|
||||
private UUID id;
|
||||
private String code;
|
||||
private String name;
|
||||
private String description;
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package br.gov.sigefp.rh.api.dto;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class CareerTimelineDTO {
|
||||
private LocalDate date;
|
||||
private String eventType;
|
||||
private String eventTypeName; // Nome amigável em português
|
||||
private String reason;
|
||||
private String documentRef;
|
||||
|
||||
// Impactos Financeiros
|
||||
private BigDecimal totalBaseAmount;
|
||||
private BigDecimal cargoAmount; // 5/6
|
||||
private BigDecimal exercicioAmount; // 1/6
|
||||
|
||||
// Mudanças representadas como texto para facilitar o frontend
|
||||
private String changeSummary;
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package br.gov.sigefp.rh.api.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CreateAbsenceDTO {
|
||||
@NotNull
|
||||
private UUID agentId;
|
||||
@NotNull
|
||||
private LocalDate startDate;
|
||||
@NotNull
|
||||
private LocalDate endDate;
|
||||
private String reason;
|
||||
private boolean justified;
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package br.gov.sigefp.rh.api.dto;
|
||||
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* DTO para criação de período de folha.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CreatePayrollPeriodDTO {
|
||||
|
||||
@NotNull(message = "Ano fiscal é obrigatório")
|
||||
@Min(value = 2000, message = "Ano fiscal deve ser maior ou igual a 2000")
|
||||
@Max(value = 2100, message = "Ano fiscal deve ser menor ou igual a 2100")
|
||||
private Integer fiscalYear;
|
||||
|
||||
@NotNull(message = "Mês é obrigatório")
|
||||
@Min(value = 1, message = "Mês deve estar entre 1 e 12")
|
||||
@Max(value = 12, message = "Mês deve estar entre 1 e 12")
|
||||
private Integer month;
|
||||
|
||||
@NotNull(message = "Data de início é obrigatória")
|
||||
private LocalDate startDate;
|
||||
|
||||
@NotNull(message = "Data de fim é obrigatória")
|
||||
private LocalDate endDate;
|
||||
}
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package br.gov.sigefp.rh.api.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* DTO para criação de execução de folha.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CreatePayrollRunDTO {
|
||||
|
||||
@NotNull(message = "ID do período é obrigatório")
|
||||
private UUID periodId;
|
||||
|
||||
private UUID ministryId; // Opcional: se não fornecido, processa todos os ministérios
|
||||
|
||||
private UUID orgUnitId; // Opcional: se não fornecido, processa todas as unidades
|
||||
|
||||
@NotNull(message = "Tipo de execução é obrigatório")
|
||||
private String runType; // REGULAR, BONUS, ADJUSTMENT, etc.
|
||||
}
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package br.gov.sigefp.rh.api.dto;
|
||||
|
||||
import br.gov.sigefp.rh.domain.AttendanceSheetStatus;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class MonthlyAttendanceSheetDTO {
|
||||
private UUID id;
|
||||
private UUID orgUnitId;
|
||||
private Integer month;
|
||||
private Integer year;
|
||||
private AttendanceSheetStatus status;
|
||||
private String approvedBy;
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package br.gov.sigefp.rh.api.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* DTO para transferência de dados de item de folha.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PayrollItemDTO {
|
||||
|
||||
private UUID id;
|
||||
|
||||
private UUID agent;
|
||||
|
||||
private String agentName; // Nome do agente para exibição
|
||||
|
||||
private String lineType;
|
||||
|
||||
private UUID earningTypeId;
|
||||
|
||||
private UUID deductionTypeId;
|
||||
|
||||
private String description;
|
||||
|
||||
private BigDecimal quantity;
|
||||
|
||||
private BigDecimal unitAmount;
|
||||
|
||||
private BigDecimal totalAmount;
|
||||
|
||||
private UUID budgetLine;
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package br.gov.sigefp.rh.api.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* DTO para transferência de dados de período de folha.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PayrollPeriodDTO {
|
||||
|
||||
private UUID id;
|
||||
|
||||
private Integer fiscalYear;
|
||||
|
||||
private Integer month;
|
||||
|
||||
private String status;
|
||||
|
||||
private LocalDate startDate;
|
||||
|
||||
private LocalDate endDate;
|
||||
}
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package br.gov.sigefp.rh.api.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* DTO para transferência de dados de execução de folha.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PayrollRunDTO {
|
||||
|
||||
private UUID id;
|
||||
|
||||
private UUID periodId;
|
||||
|
||||
private UUID ministry;
|
||||
|
||||
private String ministryName;
|
||||
|
||||
private UUID orgUnit;
|
||||
|
||||
private String orgUnitName;
|
||||
|
||||
private String runType;
|
||||
|
||||
private String status;
|
||||
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
private UUID createdBy;
|
||||
|
||||
private List<PayrollItemDTO> items;
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package br.gov.sigefp.rh.api.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* DTO para transferência de dados de avaliação de desempenho.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PerformanceEvaluationDTO {
|
||||
|
||||
private UUID id;
|
||||
private UUID agentId;
|
||||
private String agentName;
|
||||
private String agentMatricula;
|
||||
private Integer referenceYear;
|
||||
private Integer score;
|
||||
private String status; // DRAFT, FINAL, CANCELLED
|
||||
private String mention; // MAU, MEDIOCRE, REGULAR, BOM, MUITO_BOM
|
||||
private String observations;
|
||||
private LocalDate evaluationDate;
|
||||
}
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package br.gov.sigefp.rh.api.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SalaryCategoryDTO {
|
||||
private UUID id;
|
||||
|
||||
@NotBlank(message = "Código é obrigatório")
|
||||
private String code;
|
||||
|
||||
@NotBlank(message = "Nome é obrigatório")
|
||||
private String name;
|
||||
|
||||
private UUID regimeId;
|
||||
private String regimeName;
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package br.gov.sigefp.rh.api.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SalaryGradeDTO {
|
||||
private UUID id;
|
||||
|
||||
@NotNull(message = "Categoria é obrigatória")
|
||||
private UUID categoryId;
|
||||
|
||||
@NotBlank(message = "Código é obrigatório")
|
||||
private String code;
|
||||
|
||||
@NotBlank(message = "Nome é obrigatório")
|
||||
private String name;
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package br.gov.sigefp.rh.api.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SalaryGridDTO {
|
||||
private UUID id;
|
||||
|
||||
@NotNull(message = "Step é obrigatório")
|
||||
private UUID stepId;
|
||||
|
||||
@NotNull(message = "Data de início da vigência é obrigatória")
|
||||
private LocalDate validFrom;
|
||||
|
||||
private LocalDate validTo;
|
||||
|
||||
@NotNull(message = "Valor base é obrigatório")
|
||||
private BigDecimal baseAmount;
|
||||
|
||||
private BigDecimal subsidyAmount;
|
||||
private BigDecimal grossAmount;
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package br.gov.sigefp.rh.api.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SalaryStepDTO {
|
||||
private UUID id;
|
||||
|
||||
@NotNull(message = "Grau (Grade) é obrigatório")
|
||||
private UUID gradeId;
|
||||
|
||||
@NotNull(message = "Número do step é obrigatório")
|
||||
private Integer stepNumber;
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package br.gov.sigefp.rh.api.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SalaryStructureFullDTO {
|
||||
private List<CategoryGroupDTO> categories;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class CategoryGroupDTO {
|
||||
private UUID id;
|
||||
private String code;
|
||||
private String name;
|
||||
private String regimeName;
|
||||
private List<GradeRowDTO> grades;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class GradeRowDTO {
|
||||
private UUID id;
|
||||
private String code;
|
||||
private String name;
|
||||
private List<StepDetailDTO> steps;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class StepDetailDTO {
|
||||
private UUID id;
|
||||
private Integer stepNumber;
|
||||
private Double currentValue;
|
||||
private Double subsidyAmount;
|
||||
private Double grossAmount;
|
||||
}
|
||||
}
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
package br.gov.sigefp.rh.config;
|
||||
|
||||
import br.gov.sigefp.rh.domain.*;
|
||||
import br.gov.sigefp.rh.repository.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import org.springframework.core.annotation.Order;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Order(2)
|
||||
@Transactional
|
||||
public class PayrollDataInitializer implements CommandLineRunner {
|
||||
|
||||
private final EarningTypeRepository earningTypeRepository;
|
||||
private final DeductionTypeRepository deductionTypeRepository;
|
||||
private final GlobalDeductionRuleRepository globalDeductionRuleRepository;
|
||||
private final TaxBracketRepository taxBracketRepository;
|
||||
private final PayrollPeriodRepository payrollPeriodRepository;
|
||||
private final SalaryCategoryRepository salaryCategoryRepository;
|
||||
private final SalaryGradeRepository salaryGradeRepository;
|
||||
private final SalaryStepRepository salaryStepRepository;
|
||||
private final SalaryGridRepository salaryGridRepository;
|
||||
private final AgentRepository agentRepository;
|
||||
private final br.gov.sigefp.budget.repository.BudgetLineRepository budgetLineRepository;
|
||||
private final br.gov.sigefp.budget.repository.FiscalYearRepository fiscalYearRepository;
|
||||
|
||||
private final FamilyAllowanceTableRepository familyAllowanceTableRepository;
|
||||
|
||||
@Override
|
||||
// @Transactional removed to allow partial commits
|
||||
public void run(String... args) {
|
||||
log.info("Inicializando tipos de proventos, descontos e regras tributárias...");
|
||||
|
||||
// 1. Proventos
|
||||
createEarningTypeIfNotExist("SALARIO_BASE", "Vencimento Base", true, "311100");
|
||||
createEarningTypeIfNotExist("SUBSIDIO", "Subsídio de Representação", true, "311102");
|
||||
createEarningTypeIfNotExist("ABONO_FAMILIA", "Abono de Família", false, "312101");
|
||||
|
||||
// 1.1 Tabela de Abono de Família
|
||||
for (int i = 1; i <= 10; i++) {
|
||||
createFamilyAllowanceIfNotExist(i, new BigDecimal("1000").multiply(new BigDecimal(i)));
|
||||
}
|
||||
|
||||
// 2. Descontos
|
||||
DeductionType inps = createDeductionTypeIfNotExist("INPS", "Segurança Social (INPS)", "312100");
|
||||
DeductionType irps = createDeductionTypeIfNotExist("IRPS", "Imposto Profissional (IRPS)", null);
|
||||
DeductionType demo = createDeductionTypeIfNotExist("ID", "Imposto de Democracia", null);
|
||||
DeductionType selo = createDeductionTypeIfNotExist("SELO", "Imposto de Selo", null);
|
||||
DeductionType falta = createDeductionTypeIfNotExist("FALTA_INJUSTIFICADA", "Falta Injustificada", null);
|
||||
|
||||
// 3. Regras Globais (Válidas desde 2024 até o infinito por padrão)
|
||||
createGlobalRuleIfNotExist(inps, new BigDecimal("0.07"));
|
||||
createGlobalRuleIfNotExist(selo, new BigDecimal("0.003"));
|
||||
|
||||
// Limpar tabela de escalões para garantir conformidade com a nova lei
|
||||
// Limpar tabela de escalões para garantir conformidade com a nova lei
|
||||
// try {
|
||||
// taxBracketRepository.deleteAllInBatch();
|
||||
// log.info("Tabela de escalões limpa com sucesso.");
|
||||
// } catch (Exception e) {
|
||||
// log.error("Erro ao limpar tabela de escalões: {}", e.getMessage());
|
||||
// }
|
||||
|
||||
createTaxBracketIfNotExist(irps, "0.00", "41667.00", "0.0100", "0.00", null);
|
||||
createTaxBracketIfNotExist(irps, "41668.00", "83333.00", "0.0600", "2083.00", null);
|
||||
createTaxBracketIfNotExist(irps, "83334.00", "208333.00", "0.0800", "3750.00", null);
|
||||
createTaxBracketIfNotExist(irps, "208334.00", "300000.00", "0.1000", "7917.00", null);
|
||||
createTaxBracketIfNotExist(irps, "300001.00", "400500.00", "0.1200", "13917.00", null);
|
||||
createTaxBracketIfNotExist(irps, "400501.00", "750000.00", "0.1400", "21927.00", null);
|
||||
createTaxBracketIfNotExist(irps, "750001.00", "1100000.00", "0.1600", "36927.00", null);
|
||||
createTaxBracketIfNotExist(irps, "1100001.00", "1500000.00", "0.1800", "58927.00", null);
|
||||
createTaxBracketIfNotExist(irps, "1500001.00", null, "0.2000", "88929.00", null);
|
||||
|
||||
// 6. Imposto de Democracia (Novas Regras)
|
||||
// createTaxBracketIfNotExist(demo, "0.00", "41667.00", null, null, "500.00");
|
||||
// createTaxBracketIfNotExist(demo, "41668.00", "83333.00", null, null,
|
||||
// "1000.00");
|
||||
// createTaxBracketIfNotExist(demo, "83334.00", "208333.00", null, null,
|
||||
// "2000.00");
|
||||
// createTaxBracketIfNotExist(demo, "208334.00", "300000.00", null, null,
|
||||
// "4000.00");
|
||||
// createTaxBracketIfNotExist(demo, "300001.00", "405500.00", null, null,
|
||||
// "6000.00");
|
||||
// createTaxBracketIfNotExist(demo, "405501.00", "750000.00", null, null,
|
||||
// "10000.00");
|
||||
// createTaxBracketIfNotExist(demo, "750001.00", "1100000.00", null, null,
|
||||
// "15000.00");
|
||||
// createTaxBracketIfNotExist(demo, "1100001.00", "1500000.00", null, null,
|
||||
// "17000.00");
|
||||
// createTaxBracketIfNotExist(demo, "1500001.00", null, null, null, "20000.00");
|
||||
|
||||
// 5. Estrutura Salarial e Grelha
|
||||
// 5. Estrutura Salarial e Grelha
|
||||
try {
|
||||
// initializeSalaryAndPeriod(); // Disable to prevent hang
|
||||
log.info("Inicialização de Salários pulada para garantir boot.");
|
||||
} catch (Exception e) {
|
||||
log.error("Erro ao inicializar estrutura salarial: {}", e.getMessage());
|
||||
}
|
||||
|
||||
log.info("Configuração tributária e operacional concluída.");
|
||||
}
|
||||
|
||||
// Helper atualizado para suportar Taxa Progressiva OU Valor Fixo
|
||||
private void createTaxBracketIfNotExist(DeductionType type, String lower, String upper, String rate,
|
||||
String excess, String fixed) {
|
||||
BigDecimal low = new BigDecimal(lower);
|
||||
BigDecimal up = upper != null ? new BigDecimal(upper) : null;
|
||||
|
||||
// Verifica duplicidade
|
||||
List<TaxBracket> active = taxBracketRepository.findActiveBrackets(LocalDate.now());
|
||||
boolean exists = active.stream().anyMatch(
|
||||
b -> b.getDeductionType().getCode().equals(type.getCode()) && b.getLowerLimit().compareTo(low) == 0);
|
||||
|
||||
if (!exists) {
|
||||
TaxBracket bracket = TaxBracket.builder()
|
||||
.deductionType(type)
|
||||
.lowerLimit(low)
|
||||
.upperLimit(up)
|
||||
.ratePercentage(rate != null ? new BigDecimal(rate) : null)
|
||||
.excessDeduction(excess != null ? new BigDecimal(excess) : null)
|
||||
.fixedAmount(fixed != null ? new BigDecimal(fixed) : null)
|
||||
.validFrom(LocalDate.of(2024, 1, 1))
|
||||
.build();
|
||||
taxBracketRepository.save(bracket);
|
||||
log.info("Escalão {} criado: {} a {}", type.getCode(), lower, upper != null ? upper : "inf");
|
||||
}
|
||||
}
|
||||
|
||||
private void createEarningTypeIfNotExist(String code, String name, boolean taxable, String econClass) {
|
||||
if (earningTypeRepository.findByCode(code).isEmpty()) {
|
||||
EarningType type = EarningType.builder()
|
||||
.code(code)
|
||||
.name(name)
|
||||
.taxable(taxable)
|
||||
.economicClassCode(econClass)
|
||||
.build();
|
||||
earningTypeRepository.save(type);
|
||||
log.info("Tipo de Provento criado: {}", code);
|
||||
}
|
||||
}
|
||||
|
||||
private DeductionType createDeductionTypeIfNotExist(String code, String name, String econClass) {
|
||||
return deductionTypeRepository.findByCode(code).orElseGet(() -> {
|
||||
DeductionType type = DeductionType.builder()
|
||||
.code(code)
|
||||
.name(name)
|
||||
.economicClassCode(econClass)
|
||||
.build();
|
||||
log.info("Tipo de Desconto criado: {}", code);
|
||||
return deductionTypeRepository.save(type);
|
||||
});
|
||||
}
|
||||
|
||||
private void createGlobalRuleIfNotExist(DeductionType type, BigDecimal percentage) {
|
||||
// Optimized: Check existence via count/exists query to avoid
|
||||
// LazyInitializationException
|
||||
boolean exists = globalDeductionRuleRepository.findAll().stream()
|
||||
.anyMatch(r -> r.getDeductionType() != null && r.getDeductionType().getId().equals(type.getId()));
|
||||
|
||||
if (!exists) {
|
||||
GlobalDeductionRule rule = GlobalDeductionRule.builder()
|
||||
.deductionType(type)
|
||||
.percentage(percentage)
|
||||
.amountFixed(null)
|
||||
.validFrom(LocalDate.of(2024, 1, 1))
|
||||
.build();
|
||||
globalDeductionRuleRepository.save(rule);
|
||||
log.info("Regra Global criada: {} ({}%)", type.getName(), percentage.multiply(new BigDecimal(100)));
|
||||
log.info("Regra Global criada: {} ({}%)", type.getName(), percentage.multiply(new BigDecimal(100)));
|
||||
}
|
||||
}
|
||||
|
||||
private void createFamilyAllowanceIfNotExist(Integer dependents, BigDecimal amount) {
|
||||
// Simple check to avoid LazyInitializationException usually not needed for
|
||||
// simple find
|
||||
if (familyAllowanceTableRepository.findByDependentsAndDate(dependents, LocalDate.now()).isEmpty()) {
|
||||
FamilyAllowanceTable table = FamilyAllowanceTable.builder()
|
||||
.dependentsCount(dependents)
|
||||
.amount(amount)
|
||||
.validFrom(LocalDate.of(2024, 1, 1))
|
||||
.build();
|
||||
familyAllowanceTableRepository.save(table);
|
||||
log.info("Abono de Família configurado: {} dependentes = {}", dependents, amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package br.gov.sigefp.rh.domain;
|
||||
|
||||
import br.gov.sigefp.common.domain.BaseEntity;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Entity
|
||||
@Table(name = "agent_absence")
|
||||
@Getter
|
||||
@Setter
|
||||
@SuperBuilder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Absence extends BaseEntity {
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "agent_id", nullable = false)
|
||||
private Agent agent;
|
||||
|
||||
@Column(name = "start_date", nullable = false)
|
||||
private LocalDate startDate;
|
||||
|
||||
@Column(name = "end_date", nullable = false)
|
||||
private LocalDate endDate;
|
||||
|
||||
@Column(name = "days", nullable = false)
|
||||
private Integer days;
|
||||
|
||||
@Column(name = "reason")
|
||||
private String reason;
|
||||
|
||||
@Column(name = "is_justified", nullable = false)
|
||||
private boolean justified;
|
||||
|
||||
@Column(name = "deducted_in_payroll_run_id")
|
||||
private java.util.UUID deductedInPayrollRunId;
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package br.gov.sigefp.rh.domain;
|
||||
|
||||
import br.gov.sigefp.common.domain.AuditableEntity;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Entidade que representa um agente/funcionário público.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "agents", indexes = {
|
||||
@Index(name = "idx_agent_nif", columnList = "nif"),
|
||||
@Index(name = "idx_agent_bi", columnList = "bi_number"),
|
||||
@Index(name = "idx_agent_matricula", columnList = "matricula"),
|
||||
@Index(name = "idx_agent_status", columnList = "status")
|
||||
})
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@SuperBuilder
|
||||
public class Agent extends AuditableEntity {
|
||||
|
||||
@Column(nullable = false, unique = true, length = 20)
|
||||
private String matricula; // Número de matrícula único
|
||||
|
||||
@Column(nullable = false, unique = true, length = 20)
|
||||
private String nif; // Número de Identificação Fiscal
|
||||
|
||||
@Column(nullable = false, unique = true, length = 20, name = "bi_number")
|
||||
private String biNumber; // Número do Bilhete de Identidade
|
||||
|
||||
@Column(nullable = false, length = 200)
|
||||
private String fullName;
|
||||
|
||||
@Column(nullable = false)
|
||||
private LocalDate birthDate;
|
||||
|
||||
@Column(nullable = false)
|
||||
private LocalDate hireDate; // Data de admissão
|
||||
|
||||
@Column(name = "posse_date")
|
||||
private LocalDate posseDate; // Data de posse
|
||||
|
||||
private LocalDate terminationDate; // Data de desligamento (null se ativo)
|
||||
|
||||
@Column(nullable = false, length = 50, name = "appointment_type")
|
||||
private String appointmentType = "PROVISORIA"; // PROVISORIA, DEFINITIVA, CONTRATO_PROVIMENTO, CONTRATO_TERMO
|
||||
|
||||
@Column(nullable = false, length = 50, name = "functional_situation")
|
||||
private String functionalSituation = "ATIVIDADE_NO_QUADRO"; // ATIVIDADE_NO_QUADRO, ATIVIDADE_FORA_DO_QUADRO...
|
||||
|
||||
@Column(name = "eligible_dependents_count")
|
||||
private Integer eligibleDependentsCount; // Número de dependentes para Abono de Família
|
||||
|
||||
@Column(nullable = false, length = 20)
|
||||
private String status = "REGISTERED"; // REGISTERED, ACTIVE, INACTIVE, SUSPENDED, TERMINATED
|
||||
|
||||
@Column(name = "literary_qualification", length = 100)
|
||||
private String literaryQualification; // Habilitação Literária
|
||||
|
||||
@Column(name = "salary_category_id")
|
||||
private UUID salaryCategory;
|
||||
|
||||
@Column(name = "salary_grade_id")
|
||||
private UUID salaryGrade;
|
||||
|
||||
@Column(name = "salary_step_id")
|
||||
private UUID salaryStep;
|
||||
|
||||
@Column(name = "org_unit_id")
|
||||
private UUID orgUnit; // Referência à unidade organizacional
|
||||
|
||||
@Column(name = "position_id")
|
||||
private UUID position; // Referência à posição/cargo
|
||||
|
||||
@Column(length = 50)
|
||||
private String nationality;
|
||||
|
||||
@Column(length = 20)
|
||||
private String phone;
|
||||
|
||||
@Column(length = 100)
|
||||
private String email;
|
||||
|
||||
@Column(length = 500)
|
||||
private String address;
|
||||
|
||||
@OneToMany(mappedBy = "agent", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||
@lombok.Builder.Default
|
||||
private List<AgentContract> contracts = new ArrayList<>();
|
||||
|
||||
@OneToMany(mappedBy = "agent", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||
@lombok.Builder.Default
|
||||
private List<AgentBankAccount> bankAccounts = new ArrayList<>();
|
||||
|
||||
@OneToMany(mappedBy = "agent", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||
@lombok.Builder.Default
|
||||
private List<AgentDeductionRule> deductionRules = new ArrayList<>();
|
||||
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package br.gov.sigefp.rh.domain;
|
||||
|
||||
import br.gov.sigefp.common.domain.AuditableEntity;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Entidade que representa uma conta bancária de um agente.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "agent_bank_account", indexes = {
|
||||
@Index(name = "idx_bank_account_agent", columnList = "agent_id"),
|
||||
@Index(name = "idx_bank_account_primary", columnList = "agent_id,is_primary")
|
||||
})
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@lombok.experimental.SuperBuilder
|
||||
public class AgentBankAccount extends AuditableEntity {
|
||||
|
||||
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "agent_id", nullable = false)
|
||||
private Agent agent;
|
||||
|
||||
@JsonProperty("agentId")
|
||||
public UUID getAgentIdForSerialization() {
|
||||
return agent != null ? agent.getId() : null;
|
||||
}
|
||||
|
||||
@Column(nullable = false, length = 100)
|
||||
private String bank; // Nome do banco
|
||||
|
||||
@Column(nullable = false, length = 20, name = "branch_code")
|
||||
private String branchCode; // Código da agência
|
||||
|
||||
@Column(nullable = false, length = 50, name = "account_number")
|
||||
private String accountNumber; // Número da conta
|
||||
|
||||
@Column(length = 50)
|
||||
private String iban; // IBAN da conta
|
||||
|
||||
@Column(nullable = false, name = "is_primary")
|
||||
@Builder.Default
|
||||
private Boolean isPrimary = false; // Conta principal para pagamentos
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package br.gov.sigefp.rh.domain;
|
||||
|
||||
import br.gov.sigefp.common.domain.AuditableEntity;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Entidade que representa um contrato de trabalho do agente.
|
||||
* Usa LocalDate para datas, evitando strings concatenadas.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "agent_contract", indexes = {
|
||||
@Index(name = "idx_contract_agent", columnList = "agent_id"),
|
||||
@Index(name = "idx_contract_dates", columnList = "start_date,end_date"),
|
||||
@Index(name = "idx_contract_active", columnList = "is_active")
|
||||
})
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class AgentContract extends AuditableEntity {
|
||||
|
||||
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "agent_id", nullable = false)
|
||||
private Agent agent;
|
||||
|
||||
@Column(nullable = false, length = 50)
|
||||
private String contractType; // PERMANENT, TEMPORARY, INTERNSHIP, FIXED_TERM, etc.
|
||||
|
||||
@Column(nullable = false, name = "start_date")
|
||||
private LocalDate startDate;
|
||||
|
||||
@Column(name = "end_date")
|
||||
private LocalDate endDate; // Null para contratos sem término definido
|
||||
|
||||
@Column(name = "weekly_hours", precision = 5, scale = 2)
|
||||
private BigDecimal weeklyHours; // Horas semanais
|
||||
|
||||
@Column(name = "base_salary_ref", precision = 19, scale = 2)
|
||||
private BigDecimal baseSalaryRef; // Referência salarial base
|
||||
|
||||
@Column(name = "org_unit_id")
|
||||
private UUID orgUnit;
|
||||
|
||||
@Column(name = "position_id")
|
||||
private UUID position;
|
||||
|
||||
@Column(name = "salary_category_id")
|
||||
private UUID salaryCategory;
|
||||
|
||||
@Column(name = "salary_grade_id")
|
||||
private UUID salaryGrade;
|
||||
|
||||
@Column(name = "salary_step_id")
|
||||
private UUID salaryStep;
|
||||
|
||||
@Column(name = "legal_act_reference", length = 100)
|
||||
private String legalActReference; // Ex: Decreto n┬║ 12/2023, Despacho n┬║ 45/2023
|
||||
|
||||
@Column(nullable = false, name = "is_active")
|
||||
@Builder.Default
|
||||
private Boolean isActive = true;
|
||||
|
||||
@JsonProperty("agentId")
|
||||
public UUID getAgentIdForSerialization() {
|
||||
return agent != null ? agent.getId() : null;
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package br.gov.sigefp.rh.domain;
|
||||
|
||||
import br.gov.sigefp.common.domain.AuditableEntity;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Entidade que representa uma regra de desconto específica para um agente.
|
||||
* Usa LocalDate para validade, evitando strings concatenadas.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "agent_deduction_rule", indexes = {
|
||||
@Index(name = "idx_deduction_rule_agent", columnList = "agent_id"),
|
||||
@Index(name = "idx_deduction_rule_type", columnList = "deduction_type_id"),
|
||||
@Index(name = "idx_deduction_rule_validity", columnList = "valid_from,valid_to")
|
||||
})
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class AgentDeductionRule extends AuditableEntity {
|
||||
|
||||
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "agent_id", nullable = false)
|
||||
private Agent agent;
|
||||
|
||||
@JsonProperty("agentId")
|
||||
public UUID getAgentIdForSerialization() {
|
||||
return agent != null ? agent.getId() : null;
|
||||
}
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "deduction_type_id", nullable = false)
|
||||
private DeductionType deductionType;
|
||||
|
||||
@Column(name = "amount_fixed", precision = 19, scale = 2)
|
||||
private BigDecimal amountFixed; // Valor fixo (se aplicável)
|
||||
|
||||
@Column(precision = 5, scale = 2)
|
||||
private BigDecimal percentage; // Percentual (se aplicável)
|
||||
|
||||
@Column(nullable = false, name = "valid_from")
|
||||
private LocalDate validFrom; // Data de início da vigência
|
||||
|
||||
@Column(name = "valid_to")
|
||||
private LocalDate validTo; // Data de fim da vigência (null se ainda vigente)
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package br.gov.sigefp.rh.domain;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
import org.hibernate.annotations.UuidGenerator;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(name = "agent_status_history", indexes = {
|
||||
@Index(name = "idx_history_agent_id", columnList = "agent_id"),
|
||||
@Index(name = "idx_history_changed_at", columnList = "changed_at")
|
||||
})
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class AgentStatusHistory {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
@UuidGenerator
|
||||
private UUID id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "agent_id", nullable = false)
|
||||
private Agent agent;
|
||||
|
||||
@Column(name = "previous_status", length = 20)
|
||||
private String previousStatus;
|
||||
|
||||
@Column(name = "new_status", length = 20)
|
||||
private String newStatus;
|
||||
|
||||
@Column(name = "previous_functional_situation", length = 50)
|
||||
private String previousFunctionalSituation;
|
||||
|
||||
@Column(name = "new_functional_situation", length = 50)
|
||||
private String newFunctionalSituation;
|
||||
|
||||
@Column(name = "event_type", length = 50)
|
||||
private String eventType; // PROMOTIO, PROGRESSION, SUBSTITUTION, TRANSFER, etc.
|
||||
|
||||
@Column(length = 500)
|
||||
private String reason;
|
||||
|
||||
@Column(name = "changed_at", nullable = false)
|
||||
private LocalDateTime changedAt;
|
||||
|
||||
@Column(name = "changed_by", length = 100)
|
||||
private String changedBy;
|
||||
|
||||
@Column(name = "change_log", length = 1000)
|
||||
private String changeLog;
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package br.gov.sigefp.rh.domain;
|
||||
|
||||
import br.gov.sigefp.common.domain.BaseEntity;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Entity
|
||||
@Table(name = "attendance_record")
|
||||
@Getter
|
||||
@Setter
|
||||
@SuperBuilder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AttendanceRecord extends BaseEntity {
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "sheet_id", nullable = false)
|
||||
private MonthlyAttendanceSheet sheet;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "agent_id", nullable = false)
|
||||
private Agent agent;
|
||||
|
||||
@Column(name = "date", nullable = false)
|
||||
private LocalDate date;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "type", nullable = false)
|
||||
private AttendanceType type;
|
||||
|
||||
@Column(name = "observation")
|
||||
private String observation;
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package br.gov.sigefp.rh.domain;
|
||||
|
||||
public enum AttendanceSheetStatus {
|
||||
DRAFT, // Em preenchimento
|
||||
SUBMITTED, // Enviado para aprovação
|
||||
APPROVED, // Aprovado
|
||||
CLOSED // Processado (Bloqueado)
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package br.gov.sigefp.rh.domain;
|
||||
|
||||
public enum AttendanceType {
|
||||
ABSENCE_UNJUSTIFIED, // Falta Injustificada
|
||||
ABSENCE_JUSTIFIED, // Falta Justificada
|
||||
SICK_LEAVE, // Baixa Médica
|
||||
VACATION // Férias
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
package br.gov.sigefp.rh.domain;
|
||||
|
||||
import br.gov.sigefp.common.domain.AuditableEntity;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Entidade que registra um evento estruturado na vida laboral/carreira do
|
||||
* agente.
|
||||
* Substitui o AgentStatusHistory para fornecer dados ricos para folha e
|
||||
* auditoria.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "career_events", indexes = {
|
||||
@Index(name = "idx_career_event_agent", columnList = "agent_id"),
|
||||
@Index(name = "idx_career_event_type", columnList = "event_type"),
|
||||
@Index(name = "idx_career_event_date", columnList = "effective_date")
|
||||
})
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class CareerEvent extends AuditableEntity {
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "agent_id", nullable = false)
|
||||
private Agent agent;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "event_type", nullable = false, length = 50)
|
||||
private CareerEventType eventType;
|
||||
|
||||
@Column(name = "effective_date", nullable = false)
|
||||
private LocalDate effectiveDate; // Data em que o evento passa a valer
|
||||
|
||||
@Column(name = "publication_date")
|
||||
private LocalDate publicationDate; // Data de publicação no Boletim Oficial
|
||||
|
||||
@Column(name = "document_ref", length = 100)
|
||||
private String documentRef; // Referência do Despacho ou documento legal
|
||||
|
||||
// Snapshots da Carreira Salarial
|
||||
private UUID previousCategory;
|
||||
private UUID newCategory;
|
||||
|
||||
private UUID previousGrade;
|
||||
private UUID newGrade;
|
||||
|
||||
private UUID previousStep;
|
||||
private UUID newStep;
|
||||
|
||||
// Snapshots Organizacionais
|
||||
private UUID previousOrgUnit;
|
||||
private UUID newOrgUnit;
|
||||
|
||||
private UUID previousPosition;
|
||||
private UUID newPosition;
|
||||
|
||||
// Snapshot Financeiro (Baseado no Decreto 12-A/94)
|
||||
@Column(precision = 19, scale = 2)
|
||||
private BigDecimal totalBaseAmount; // Rb (Remuneração Base)
|
||||
|
||||
@Column(precision = 19, scale = 2)
|
||||
private BigDecimal cargoAmount; // 5/6 da remuneração base
|
||||
|
||||
@Column(precision = 19, scale = 2)
|
||||
private BigDecimal exercicioAmount; // 1/6 da remuneração base
|
||||
|
||||
@Column(length = 500)
|
||||
private String reason;
|
||||
|
||||
@Column(name = "created_by_user")
|
||||
private String createdByUser;
|
||||
|
||||
/**
|
||||
* Calcula a divisão 5/6 e 1/6 com base no valor base total.
|
||||
*/
|
||||
public void calculateFinancialSplit(BigDecimal total) {
|
||||
if (total == null)
|
||||
return;
|
||||
this.totalBaseAmount = total;
|
||||
// 5/6 = (total * 5) / 6
|
||||
this.cargoAmount = total.multiply(BigDecimal.valueOf(5))
|
||||
.divide(BigDecimal.valueOf(6), 2, java.math.RoundingMode.HALF_UP);
|
||||
// 1/6 = total - 5/6 (para garantir que a soma feche perfeitamente)
|
||||
this.exercicioAmount = total.subtract(this.cargoAmount);
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package br.gov.sigefp.rh.domain;
|
||||
|
||||
/**
|
||||
* Tipos de eventos que podem ocorrer na vida laboral de um agente público.
|
||||
* Em conformidade com o Decreto nº 12-A/94 da Guiné-Bissau.
|
||||
*/
|
||||
public enum CareerEventType {
|
||||
ADMISSAO, // Início do vínculo (ingresso)
|
||||
NOMEACAO_PROVISORIA, // Período probatório (2 anos)
|
||||
NOMEACAO_DEFINITIVA, // Estabilização após período probatório
|
||||
PROMOCAO, // Mudança de categoria (ex: de Técnico para Técnico Superior)
|
||||
PROGRESSAO, // Mudança de escalão dentro da mesma categoria
|
||||
SUBSTITUICAO, // Exercício temporário de cargo superior (>30 dias)
|
||||
TRANSFERENCIA, // Mudança de unidade orgânica
|
||||
RECLASSIFICACAO, // Mudança de carreira por nova habilitação
|
||||
COMISSAO_SERVICO, // Exercício de cargo de direção ou chefia
|
||||
DESTACAMENTO, // Trabalho temporário em outra instituição
|
||||
REQUISICAO, // Cedência temporária a pedido
|
||||
SUSPENSAO, // Interrupção temporária do exercício
|
||||
TERMINATION, // Fim do vínculo (exoneração, aposentação, falecimento)
|
||||
RETIFICACAO // Correção ou alteração de dados de um ato administrativo anterior
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package br.gov.sigefp.rh.domain;
|
||||
|
||||
import br.gov.sigefp.common.domain.AuditableEntity;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Entidade que representa um Regime de Carreira (ex: Regime Geral, Saúde,
|
||||
* Educação).
|
||||
* Agrupa categorias salariais por setor.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "career_regime", indexes = {
|
||||
@Index(name = "idx_career_regime_code", columnList = "code")
|
||||
})
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class CareerRegime extends AuditableEntity {
|
||||
|
||||
@Column(nullable = false, unique = true, length = 50)
|
||||
private String code; // Ex: REG-GERAL, REG-SAUDE
|
||||
|
||||
@Column(nullable = false, length = 200)
|
||||
private String name; // Ex: Regime Geral da Função Pública
|
||||
|
||||
@Column(length = 500)
|
||||
private String description;
|
||||
|
||||
@OneToMany(mappedBy = "regime", cascade = CascadeType.ALL)
|
||||
@Builder.Default
|
||||
private List<SalaryCategory> categories = new ArrayList<>();
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package br.gov.sigefp.rh.domain;
|
||||
|
||||
import br.gov.sigefp.common.domain.AuditableEntity;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
/**
|
||||
* Entidade que representa um tipo de desconto/dedução.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "deduction_type", indexes = {
|
||||
@Index(name = "idx_deduction_type_code", columnList = "code")
|
||||
})
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@SuperBuilder
|
||||
@JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })
|
||||
public class DeductionType extends AuditableEntity {
|
||||
|
||||
@Column(nullable = false, unique = true, length = 50)
|
||||
private String code;
|
||||
|
||||
@Column(nullable = false, length = 200)
|
||||
private String name;
|
||||
|
||||
@Column(nullable = false)
|
||||
@Builder.Default
|
||||
private Boolean mandatory = false; // Se o desconto é obrigatório
|
||||
|
||||
@Column(length = 20, name = "economic_class_code")
|
||||
private String economicClassCode; // Classificador Económico
|
||||
|
||||
@Column(nullable = false)
|
||||
@Builder.Default
|
||||
private Boolean active = true;
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package br.gov.sigefp.rh.domain;
|
||||
|
||||
import br.gov.sigefp.common.domain.AuditableEntity;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
/**
|
||||
* Entidade que representa um tipo de provento/ganho.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "earning_type", indexes = {
|
||||
@Index(name = "idx_earning_type_code", columnList = "code")
|
||||
})
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@SuperBuilder
|
||||
public class EarningType extends AuditableEntity {
|
||||
|
||||
@Column(nullable = false, unique = true, length = 50)
|
||||
private String code;
|
||||
|
||||
@Column(nullable = false, length = 200)
|
||||
private String name;
|
||||
|
||||
@Column(nullable = false)
|
||||
@Builder.Default
|
||||
private Boolean taxable = true; // Se o provento é tributável
|
||||
|
||||
@Column(length = 20, name = "economic_class_code")
|
||||
private String economicClassCode; // Classificador Económico (ex: 311100)
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package br.gov.sigefp.rh.domain;
|
||||
|
||||
import br.gov.sigefp.common.domain.BaseEntity;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.*;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Entity
|
||||
@Table(name = "family_allowance_table")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@SuperBuilder
|
||||
public class FamilyAllowanceTable extends BaseEntity {
|
||||
|
||||
@Column(nullable = false)
|
||||
private Integer dependentsCount;
|
||||
|
||||
@Column(nullable = false, precision = 19, scale = 2)
|
||||
private BigDecimal amount;
|
||||
|
||||
@Column(nullable = false)
|
||||
private LocalDate validFrom;
|
||||
|
||||
@Column
|
||||
private LocalDate validTo;
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package br.gov.sigefp.rh.domain;
|
||||
|
||||
import br.gov.sigefp.common.domain.AuditableEntity;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* Representa uma regra de desconto global (ex: INPS 7%, Imposto de Selo 0.3%).
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "global_deduction_rule")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@SuperBuilder
|
||||
@JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })
|
||||
public class GlobalDeductionRule extends AuditableEntity {
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "deduction_type_id", nullable = false)
|
||||
private DeductionType deductionType;
|
||||
|
||||
@Column(nullable = false, precision = 5, scale = 4)
|
||||
private BigDecimal percentage; // Ex: 0.07 para 7%, 0.003 para 0.3%
|
||||
|
||||
@Column(precision = 19, scale = 2)
|
||||
private BigDecimal amountFixed;
|
||||
|
||||
@Column(nullable = false, name = "valid_from")
|
||||
private LocalDate validFrom;
|
||||
|
||||
@Column(name = "valid_to")
|
||||
private LocalDate validTo;
|
||||
|
||||
@Builder.Default
|
||||
@Column(nullable = false)
|
||||
private boolean active = true;
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package br.gov.sigefp.rh.domain;
|
||||
|
||||
import br.gov.sigefp.common.domain.BaseEntity;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
@Entity
|
||||
@Table(name = "monthly_attendance_sheet", uniqueConstraints = {
|
||||
@UniqueConstraint(columnNames = { "org_unit_id", "month", "year" }) })
|
||||
@Getter
|
||||
@Setter
|
||||
@SuperBuilder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MonthlyAttendanceSheet extends BaseEntity {
|
||||
|
||||
@Column(name = "org_unit_id", nullable = false)
|
||||
private java.util.UUID orgUnitId;
|
||||
|
||||
@Column(name = "month", nullable = false)
|
||||
private Integer month;
|
||||
|
||||
@Column(name = "year", nullable = false)
|
||||
private Integer year;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "status", nullable = false)
|
||||
private AttendanceSheetStatus status;
|
||||
|
||||
@Column(name = "approved_by")
|
||||
private String approvedBy;
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package br.gov.sigefp.rh.domain;
|
||||
|
||||
import br.gov.sigefp.common.domain.AuditableEntity;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Entidade que representa um item de folha de pagamento (provento ou desconto).
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "payroll_item", indexes = {
|
||||
@Index(name = "idx_payroll_item_run", columnList = "payroll_run_id"),
|
||||
@Index(name = "idx_payroll_item_agent", columnList = "agent_id"),
|
||||
@Index(name = "idx_payroll_item_type", columnList = "line_type")
|
||||
})
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@SuperBuilder
|
||||
public class PayrollItem extends AuditableEntity {
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "payroll_run_id", nullable = false)
|
||||
private PayrollRun payrollRun;
|
||||
|
||||
@Column(name = "agent_id", nullable = false)
|
||||
private UUID agent; // Referência ao agente
|
||||
|
||||
@Column(nullable = false, length = 20, name = "line_type")
|
||||
private String lineType; // EARNING, DEDUCTION
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "earning_type_id")
|
||||
private EarningType earningType; // Tipo de provento (se lineType = EARNING)
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "deduction_type_id")
|
||||
private DeductionType deductionType; // Tipo de desconto (se lineType = DEDUCTION)
|
||||
|
||||
@Column(length = 500)
|
||||
private String description;
|
||||
|
||||
@Column(precision = 10, scale = 2)
|
||||
private BigDecimal quantity; // Quantidade (horas, dias, etc.)
|
||||
|
||||
@Column(precision = 19, scale = 2, name = "unit_amount")
|
||||
private BigDecimal unitAmount; // Valor unitário
|
||||
|
||||
@Column(nullable = false, precision = 19, scale = 2, name = "total_amount")
|
||||
private BigDecimal totalAmount; // Valor total (quantity * unitAmount ou valor fixo)
|
||||
|
||||
@Column(name = "budget_line_id")
|
||||
private UUID budgetLine; // Referência à linha orçamentária (para rastreabilidade)
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package br.gov.sigefp.rh.domain;
|
||||
|
||||
import br.gov.sigefp.common.domain.AuditableEntity;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Entidade que representa um período de folha de pagamento.
|
||||
* Usa LocalDate para datas, evitando strings concatenadas.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "payroll_period", indexes = {
|
||||
@Index(name = "idx_payroll_period_fiscal", columnList = "fiscal_year,month"),
|
||||
@Index(name = "idx_payroll_period_status", columnList = "status")
|
||||
})
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@lombok.experimental.SuperBuilder
|
||||
public class PayrollPeriod extends AuditableEntity {
|
||||
|
||||
@Column(nullable = false, name = "fiscal_year")
|
||||
private Integer fiscalYear; // Ano fiscal
|
||||
|
||||
@Column(nullable = false)
|
||||
private Integer month; // Mês (1-12)
|
||||
|
||||
@Column(nullable = false, length = 20)
|
||||
@Builder.Default
|
||||
private String status = "OPEN"; // OPEN, CLOSED, PROCESSING
|
||||
|
||||
@Column(nullable = false, name = "start_date")
|
||||
private LocalDate startDate; // Data de início do período
|
||||
|
||||
@Column(nullable = false, name = "end_date")
|
||||
private LocalDate endDate; // Data de fim do período
|
||||
|
||||
@OneToMany(mappedBy = "period", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||
@Builder.Default
|
||||
private List<PayrollRun> payrollRuns = new ArrayList<>();
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package br.gov.sigefp.rh.domain;
|
||||
|
||||
import br.gov.sigefp.common.domain.AuditableEntity;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Entidade que representa uma execução/processamento de folha de pagamento.
|
||||
* Usa LocalDateTime para timestamps.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "payroll_run", indexes = {
|
||||
@Index(name = "idx_payroll_run_period", columnList = "period_id"),
|
||||
@Index(name = "idx_payroll_run_ministry", columnList = "ministry_id"),
|
||||
@Index(name = "idx_payroll_run_org_unit", columnList = "org_unit_id"),
|
||||
@Index(name = "idx_payroll_run_status", columnList = "status")
|
||||
})
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@SuperBuilder
|
||||
public class PayrollRun extends AuditableEntity {
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "period_id", nullable = false)
|
||||
private PayrollPeriod period;
|
||||
|
||||
@Column(name = "ministry_id")
|
||||
private UUID ministry; // Referência ao ministério
|
||||
|
||||
@Column(name = "org_unit_id")
|
||||
private UUID orgUnit; // Referência à unidade organizacional
|
||||
|
||||
@Column(nullable = false, length = 50, name = "run_type")
|
||||
private String runType; // REGULAR, BONUS, ADJUSTMENT, etc.
|
||||
|
||||
@Column(nullable = false, length = 20)
|
||||
@Builder.Default
|
||||
private String status = "PENDING"; // PENDING, PROCESSING, COMPLETED, FAILED
|
||||
|
||||
@OneToMany(mappedBy = "payrollRun", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||
@Builder.Default
|
||||
private List<PayrollItem> items = new ArrayList<>();
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package br.gov.sigefp.rh.domain;
|
||||
|
||||
import br.gov.sigefp.common.domain.AuditableEntity;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* Representa a avaliação de desempenho anual de um agente.
|
||||
* Essencial para validar progressões e promoções conforme o Decreto nº 12-A/94.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "performance_evaluations", indexes = {
|
||||
@Index(name = "idx_evaluation_agent", columnList = "agent_id"),
|
||||
@Index(name = "idx_evaluation_year", columnList = "reference_year"),
|
||||
@Index(name = "idx_evaluation_status", columnList = "status")
|
||||
})
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@SuperBuilder
|
||||
public class PerformanceEvaluation extends AuditableEntity {
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "agent_id", nullable = false)
|
||||
private Agent agent;
|
||||
|
||||
@Column(nullable = false, name = "reference_year")
|
||||
private Integer referenceYear; // Ano a que se refere a avaliação
|
||||
|
||||
@Column(nullable = false)
|
||||
private Integer score; // Pontuação 1-10 ou 5-20 (Decreto 12-A/94 sugere quantificação)
|
||||
|
||||
@Column(length = 20, nullable = true)
|
||||
@Builder.Default
|
||||
private String status = "DRAFT"; // DRAFT, FINAL, CANCELLED
|
||||
|
||||
@Column(nullable = false, length = 50)
|
||||
private String mention; // MAU, MEDIOCRE, REGULAR, BOM, MUITO_BOM
|
||||
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String observations;
|
||||
|
||||
@Column(name = "evaluation_date")
|
||||
private LocalDate evaluationDate;
|
||||
|
||||
/**
|
||||
* Define a menção qualitativa com base na pontuação quantitativa conforme o
|
||||
* Estatuto (Escala 0-20).
|
||||
*/
|
||||
public void updateMentionFromScore() {
|
||||
if (score == null)
|
||||
return;
|
||||
if (score >= 18)
|
||||
this.mention = "MUITO_BOM";
|
||||
else if (score >= 14)
|
||||
this.mention = "BOM";
|
||||
else if (score >= 10)
|
||||
this.mention = "REGULAR";
|
||||
else if (score >= 6)
|
||||
this.mention = "MEDIOCRE";
|
||||
else
|
||||
this.mention = "MAU";
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package br.gov.sigefp.rh.domain;
|
||||
|
||||
import br.gov.sigefp.common.domain.AuditableEntity;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Entidade que representa uma categoria salarial.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "salary_category", indexes = {
|
||||
@Index(name = "idx_salary_category_code", columnList = "code")
|
||||
})
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class SalaryCategory extends AuditableEntity {
|
||||
|
||||
@Column(nullable = false, unique = true, length = 50)
|
||||
private String code;
|
||||
|
||||
@Column(nullable = false, length = 200)
|
||||
private String name;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "regime_id") // Nullable initially for migration, but generally should be required
|
||||
private CareerRegime regime;
|
||||
|
||||
@OneToMany(mappedBy = "category", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||
@Builder.Default
|
||||
private List<SalaryGrade> grades = new ArrayList<>();
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package br.gov.sigefp.rh.domain;
|
||||
|
||||
import br.gov.sigefp.common.domain.AuditableEntity;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Entidade que representa um grau salarial dentro de uma categoria.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "salary_grade", indexes = {
|
||||
@Index(name = "idx_salary_grade_code", columnList = "code"),
|
||||
@Index(name = "idx_salary_grade_category", columnList = "category_id")
|
||||
})
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class SalaryGrade extends AuditableEntity {
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "category_id", nullable = false)
|
||||
private SalaryCategory category;
|
||||
|
||||
@Column(nullable = false, length = 50)
|
||||
private String code;
|
||||
|
||||
@Column(nullable = false, length = 200)
|
||||
private String name;
|
||||
|
||||
@OneToMany(mappedBy = "grade", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||
@Builder.Default
|
||||
private List<SalaryStep> steps = new ArrayList<>();
|
||||
}
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package br.gov.sigefp.rh.domain;
|
||||
|
||||
import br.gov.sigefp.common.domain.AuditableEntity;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* Entidade que representa a tabela salarial (valores por step em um período).
|
||||
* Usa LocalDate para validade, evitando strings concatenadas.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "salary_grid", indexes = {
|
||||
@Index(name = "idx_salary_grid_step", columnList = "step_id"),
|
||||
@Index(name = "idx_salary_grid_validity", columnList = "valid_from,valid_to")
|
||||
})
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@SuperBuilder
|
||||
public class SalaryGrid extends AuditableEntity {
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "step_id", nullable = false)
|
||||
private SalaryStep step;
|
||||
|
||||
@Column(nullable = false, name = "valid_from")
|
||||
private LocalDate validFrom; // Data de início da vigência
|
||||
|
||||
@Column(name = "valid_to")
|
||||
private LocalDate validTo; // Data de fim da vigência (null se ainda vigente)
|
||||
|
||||
@Column(nullable = false, precision = 19, scale = 2, name = "base_amount")
|
||||
private BigDecimal baseAmount; // Valor base salarial
|
||||
|
||||
@Column(precision = 19, scale = 2, name = "subsidy_amount")
|
||||
private BigDecimal subsidyAmount; // Subsídio
|
||||
|
||||
@Column(precision = 19, scale = 2, name = "gross_amount")
|
||||
private BigDecimal grossAmount; // Salário Bruto (Base + Subsídio)
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package br.gov.sigefp.rh.domain;
|
||||
|
||||
import br.gov.sigefp.common.domain.AuditableEntity;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Entidade que representa um nível (step) dentro de um grau salarial.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "salary_step", indexes = {
|
||||
@Index(name = "idx_salary_step_grade", columnList = "grade_id"),
|
||||
@Index(name = "idx_salary_step_number", columnList = "grade_id,step_number")
|
||||
})
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class SalaryStep extends AuditableEntity {
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "grade_id", nullable = false)
|
||||
private SalaryGrade grade;
|
||||
|
||||
@Column(nullable = false, name = "step_number")
|
||||
private Integer stepNumber; // Número do nível (1, 2, 3, etc.)
|
||||
|
||||
@OneToMany(mappedBy = "step", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||
@Builder.Default
|
||||
private List<SalaryGrid> grids = new ArrayList<>();
|
||||
}
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package br.gov.sigefp.rh.domain;
|
||||
|
||||
import br.gov.sigefp.common.domain.AuditableEntity;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* Representa um escalão de imposto progressivo (ex: IRPS).
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "tax_bracket")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@SuperBuilder
|
||||
@JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })
|
||||
public class TaxBracket extends AuditableEntity {
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "deduction_type_id", nullable = false)
|
||||
private DeductionType deductionType;
|
||||
|
||||
@Column(nullable = false, precision = 19, scale = 2)
|
||||
private BigDecimal lowerLimit; // Limite inferior do escalão
|
||||
|
||||
@Column(precision = 19, scale = 2)
|
||||
private BigDecimal upperLimit; // Limite superior (null para o último escalão)
|
||||
|
||||
@Column(precision = 5, scale = 4)
|
||||
private BigDecimal ratePercentage; // Taxa (ex: 0.15 para 15%) - Pode ser null se for valor fixo
|
||||
|
||||
@Column(precision = 19, scale = 2)
|
||||
private BigDecimal excessDeduction; // Parcela a abater - Pode ser null se for valor fixo
|
||||
|
||||
@Column(name = "fixed_amount", precision = 19, scale = 2)
|
||||
private BigDecimal fixedAmount; // Valor fixo (para impostos não progressivos como Imposto Democracia)
|
||||
|
||||
@Column(nullable = false, name = "valid_from")
|
||||
private LocalDate validFrom;
|
||||
|
||||
@Column(name = "valid_to")
|
||||
private LocalDate validTo;
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package br.gov.sigefp.rh.repository;
|
||||
|
||||
import br.gov.sigefp.rh.domain.Absence;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface AbsenceRepository extends JpaRepository<Absence, UUID> {
|
||||
|
||||
@Query("SELECT a FROM Absence a WHERE a.agent.id = :agentId " +
|
||||
"AND a.startDate <= :endDate AND a.endDate >= :startDate")
|
||||
List<Absence> findByAgentIdAndDateRange(@Param("agentId") UUID agentId,
|
||||
@Param("startDate") LocalDate startDate,
|
||||
@Param("endDate") LocalDate endDate);
|
||||
|
||||
@Query("SELECT a FROM Absence a WHERE a.agent.id IN :agentIds " +
|
||||
"AND a.startDate <= :endDate AND a.endDate >= :startDate")
|
||||
List<Absence> findByAgentIdInAndDateRange(@Param("agentIds") List<UUID> agentIds,
|
||||
@Param("startDate") LocalDate startDate,
|
||||
@Param("endDate") LocalDate endDate);
|
||||
|
||||
List<Absence> findByAgentId(UUID agentId);
|
||||
|
||||
org.springframework.data.domain.Page<Absence> findByAgentId(UUID agentId,
|
||||
org.springframework.data.domain.Pageable pageable);
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package br.gov.sigefp.rh.repository;
|
||||
|
||||
import br.gov.sigefp.rh.domain.Agent;
|
||||
import br.gov.sigefp.rh.domain.AgentBankAccount;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface AgentBankAccountRepository extends JpaRepository<AgentBankAccount, UUID> {
|
||||
|
||||
List<AgentBankAccount> findByAgentId(UUID agentId);
|
||||
|
||||
Optional<AgentBankAccount> findByAgentIdAndIsPrimaryTrue(UUID agentId);
|
||||
|
||||
Optional<AgentBankAccount> findByAgentAndIsPrimaryTrue(Agent agent);
|
||||
|
||||
List<AgentBankAccount> findAllByAgentIdInAndIsPrimaryTrue(List<UUID> agentIds);
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package br.gov.sigefp.rh.repository;
|
||||
|
||||
import br.gov.sigefp.rh.domain.Agent;
|
||||
import br.gov.sigefp.rh.domain.AgentContract;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface AgentContractRepository extends JpaRepository<AgentContract, UUID> {
|
||||
List<AgentContract> findByAgentId(UUID agentId);
|
||||
|
||||
List<AgentContract> findByAgentIdAndIsActiveTrue(UUID agentId);
|
||||
|
||||
Optional<AgentContract> findByAgentAndIsActiveTrue(Agent agent);
|
||||
|
||||
List<AgentContract> findByAgentIdIn(List<UUID> agentIds);
|
||||
|
||||
List<AgentContract> findAllByAgentIdInAndIsActiveTrue(List<UUID> agentIds);
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package br.gov.sigefp.rh.repository;
|
||||
|
||||
import br.gov.sigefp.rh.domain.Agent;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface AgentRepository extends JpaRepository<Agent, UUID>, JpaSpecificationExecutor<Agent> {
|
||||
|
||||
Optional<Agent> findByMatricula(String matricula);
|
||||
|
||||
Optional<Agent> findByNif(String nif);
|
||||
|
||||
Optional<Agent> findByBiNumber(String biNumber);
|
||||
|
||||
boolean existsByMatricula(String matricula);
|
||||
|
||||
boolean existsByNif(String nif);
|
||||
|
||||
boolean existsByBiNumber(String biNumber);
|
||||
|
||||
java.util.List<Agent> findByStatus(String status);
|
||||
|
||||
org.springframework.data.domain.Page<Agent> findByFullNameContainingIgnoreCaseOrMatriculaContainingIgnoreCase(
|
||||
String fullName, String matricula, org.springframework.data.domain.Pageable pageable);
|
||||
|
||||
long countByStatus(String status);
|
||||
|
||||
boolean existsBySalaryCategory(UUID salaryCategory);
|
||||
|
||||
boolean existsBySalaryGrade(UUID salaryGrade);
|
||||
|
||||
boolean existsBySalaryStep(UUID salaryStep);
|
||||
|
||||
@org.springframework.data.jpa.repository.Query("SELECT a.status, COUNT(a) FROM Agent a GROUP BY a.status")
|
||||
java.util.List<Object[]> countAgentsByStatus();
|
||||
|
||||
@org.springframework.data.jpa.repository.Query("SELECT a.status, COUNT(a) FROM Agent a WHERE (:spec IS NULL OR a.status IS NOT NULL) GROUP BY a.status")
|
||||
java.util.List<Object[]> countAgentsByStatusWithSpecification(
|
||||
@org.springframework.data.repository.query.Param("spec") org.springframework.data.jpa.domain.Specification<Agent> spec);
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package br.gov.sigefp.rh.repository;
|
||||
|
||||
import br.gov.sigefp.rh.domain.AgentStatusHistory;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface AgentStatusHistoryRepository extends JpaRepository<AgentStatusHistory, UUID> {
|
||||
|
||||
List<AgentStatusHistory> findByAgentIdOrderByChangedAtDesc(UUID agentId);
|
||||
|
||||
void deleteByAgentId(UUID agentId);
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package br.gov.sigefp.rh.repository;
|
||||
|
||||
import br.gov.sigefp.rh.domain.AttendanceRecord;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface AttendanceRecordRepository extends JpaRepository<AttendanceRecord, UUID> {
|
||||
|
||||
List<AttendanceRecord> findBySheetId(UUID sheetId);
|
||||
|
||||
@Query("SELECT r FROM AttendanceRecord r WHERE r.agent.id = :agentId AND r.date BETWEEN :startDate AND :endDate")
|
||||
List<AttendanceRecord> findByAgentIdAndDateRange(UUID agentId, LocalDate startDate, LocalDate endDate);
|
||||
|
||||
@Query("SELECT r FROM AttendanceRecord r WHERE r.agent.id IN :agentIds AND r.date BETWEEN :startDate AND :endDate")
|
||||
List<AttendanceRecord> findByAgentIdInAndDateRange(List<UUID> agentIds, LocalDate startDate, LocalDate endDate);
|
||||
|
||||
@Modifying
|
||||
@Query("DELETE FROM AttendanceRecord r WHERE r.sheet.id = :sheetId")
|
||||
void deleteBySheetId(UUID sheetId);
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package br.gov.sigefp.rh.repository;
|
||||
|
||||
import br.gov.sigefp.rh.domain.CareerEvent;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface CareerEventRepository extends JpaRepository<CareerEvent, UUID> {
|
||||
List<CareerEvent> findByAgentIdOrderByEffectiveDateDesc(UUID agentId);
|
||||
List<CareerEvent> findByAgentIdOrderByEffectiveDateAsc(UUID agentId);
|
||||
|
||||
void deleteByAgentId(UUID agentId);
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package br.gov.sigefp.rh.repository;
|
||||
|
||||
import br.gov.sigefp.rh.domain.CareerRegime;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface CareerRegimeRepository extends JpaRepository<CareerRegime, UUID> {
|
||||
Optional<CareerRegime> findByCode(String code);
|
||||
|
||||
boolean existsByCode(String code);
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package br.gov.sigefp.rh.repository;
|
||||
|
||||
import br.gov.sigefp.rh.domain.DeductionType;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface DeductionTypeRepository extends JpaRepository<DeductionType, UUID> {
|
||||
Optional<DeductionType> findByCode(String code);
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package br.gov.sigefp.rh.repository;
|
||||
|
||||
import br.gov.sigefp.rh.domain.EarningType;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface EarningTypeRepository extends JpaRepository<EarningType, UUID> {
|
||||
Optional<EarningType> findByCode(String code);
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package br.gov.sigefp.rh.repository;
|
||||
|
||||
import br.gov.sigefp.rh.domain.FamilyAllowanceTable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface FamilyAllowanceTableRepository extends JpaRepository<FamilyAllowanceTable, UUID> {
|
||||
|
||||
@Query("SELECT f FROM FamilyAllowanceTable f WHERE f.dependentsCount = :count AND f.validFrom <= :date AND (f.validTo IS NULL OR f.validTo >= :date)")
|
||||
Optional<FamilyAllowanceTable> findByDependentsAndDate(@Param("count") Integer count,
|
||||
@Param("date") LocalDate date);
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package br.gov.sigefp.rh.repository;
|
||||
|
||||
import br.gov.sigefp.rh.domain.GlobalDeductionRule;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface GlobalDeductionRuleRepository extends JpaRepository<GlobalDeductionRule, UUID> {
|
||||
|
||||
@Query("SELECT r FROM GlobalDeductionRule r WHERE r.active = true AND r.validFrom <= :date AND (r.validTo IS NULL OR r.validTo >= :date)")
|
||||
List<GlobalDeductionRule> findActiveRules(@Param("date") LocalDate date);
|
||||
|
||||
@Query("SELECT r FROM GlobalDeductionRule r WHERE r.deductionType.id = :typeId AND r.active = true AND (r.validTo IS NULL OR r.validTo >= :start) AND r.validFrom <= :end")
|
||||
List<GlobalDeductionRule> findOverlappingRules(@Param("typeId") UUID typeId, @Param("start") LocalDate start,
|
||||
@Param("end") LocalDate end);
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package br.gov.sigefp.rh.repository;
|
||||
|
||||
import br.gov.sigefp.rh.domain.AttendanceSheetStatus;
|
||||
import br.gov.sigefp.rh.domain.MonthlyAttendanceSheet;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface MonthlyAttendanceSheetRepository extends JpaRepository<MonthlyAttendanceSheet, UUID> {
|
||||
|
||||
Optional<MonthlyAttendanceSheet> findByOrgUnitIdAndMonthAndYear(UUID orgUnitId, Integer month, Integer year);
|
||||
|
||||
List<MonthlyAttendanceSheet> findByMonthAndYear(Integer month, Integer year);
|
||||
|
||||
List<MonthlyAttendanceSheet> findByStatus(AttendanceSheetStatus status);
|
||||
|
||||
@Query("SELECT s FROM MonthlyAttendanceSheet s WHERE s.orgUnitId = :orgUnitId ORDER BY s.year DESC, s.month DESC")
|
||||
List<MonthlyAttendanceSheet> findByOrgUnitId(UUID orgUnitId);
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package br.gov.sigefp.rh.repository;
|
||||
|
||||
import br.gov.sigefp.rh.domain.PayrollItem;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface PayrollItemRepository extends JpaRepository<PayrollItem, UUID> {
|
||||
|
||||
List<PayrollItem> findByPayrollRunId(UUID payrollRunId);
|
||||
}
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package br.gov.sigefp.rh.repository;
|
||||
|
||||
import br.gov.sigefp.rh.domain.PayrollPeriod;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface PayrollPeriodRepository extends JpaRepository<PayrollPeriod, UUID> {
|
||||
|
||||
Optional<PayrollPeriod> findByFiscalYearAndMonth(Integer fiscalYear, Integer month);
|
||||
|
||||
boolean existsByFiscalYearAndMonth(Integer fiscalYear, Integer month);
|
||||
}
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package br.gov.sigefp.rh.repository;
|
||||
|
||||
import br.gov.sigefp.rh.domain.PayrollRun;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface PayrollRunRepository extends JpaRepository<PayrollRun, UUID> {
|
||||
|
||||
List<PayrollRun> findByPeriodId(UUID periodId);
|
||||
|
||||
@Query("SELECT pr FROM PayrollRun pr WHERE pr.period.id = :periodId AND pr.ministry = :ministryId")
|
||||
List<PayrollRun> findByPeriodIdAndMinistry(@Param("periodId") UUID periodId, @Param("ministryId") UUID ministryId);
|
||||
}
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package br.gov.sigefp.rh.repository;
|
||||
|
||||
import br.gov.sigefp.rh.domain.PerformanceEvaluation;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface PerformanceEvaluationRepository extends JpaRepository<PerformanceEvaluation, UUID> {
|
||||
List<PerformanceEvaluation> findByAgentIdOrderByReferenceYearDesc(UUID agentId);
|
||||
|
||||
List<PerformanceEvaluation> findByAgentIdAndReferenceYearBetweenOrderByReferenceYearDesc(UUID agentId,
|
||||
Integer startYear, Integer endYear);
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package br.gov.sigefp.rh.repository;
|
||||
|
||||
import br.gov.sigefp.rh.domain.SalaryCategory;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface SalaryCategoryRepository extends JpaRepository<SalaryCategory, UUID> {
|
||||
boolean existsByCode(String code);
|
||||
|
||||
@org.springframework.data.jpa.repository.EntityGraph(attributePaths = { "regime" })
|
||||
java.util.List<SalaryCategory> findAll();
|
||||
|
||||
java.util.Optional<SalaryCategory> findByCode(String code);
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package br.gov.sigefp.rh.repository;
|
||||
|
||||
import br.gov.sigefp.rh.domain.SalaryGrade;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import java.util.Optional; // Added
|
||||
|
||||
@Repository
|
||||
public interface SalaryGradeRepository extends JpaRepository<SalaryGrade, UUID> {
|
||||
@org.springframework.data.jpa.repository.EntityGraph(attributePaths = { "category", "steps" })
|
||||
List<SalaryGrade> findByCategoryId(UUID categoryId);
|
||||
|
||||
Optional<SalaryGrade> findByCode(String code);
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package br.gov.sigefp.rh.repository;
|
||||
|
||||
import br.gov.sigefp.rh.domain.SalaryGrid;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface SalaryGridRepository extends JpaRepository<SalaryGrid, UUID> {
|
||||
|
||||
@Query("SELECT g FROM SalaryGrid g WHERE g.step.id = :stepId AND :date BETWEEN g.validFrom AND COALESCE(g.validTo, '2999-12-31')")
|
||||
SalaryGrid findByStepIdAndDate(@Param("stepId") UUID stepId, @Param("date") LocalDate date);
|
||||
|
||||
List<SalaryGrid> findByStepId(UUID stepId);
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package br.gov.sigefp.rh.repository;
|
||||
|
||||
import br.gov.sigefp.rh.domain.SalaryStep;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import br.gov.sigefp.rh.domain.SalaryGrade;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface SalaryStepRepository extends JpaRepository<SalaryStep, UUID> {
|
||||
List<SalaryStep> findByGradeIdOrderByStepNumber(UUID gradeId);
|
||||
|
||||
Optional<SalaryStep> findByGradeAndStepNumber(SalaryGrade grade, Integer stepNumber);
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package br.gov.sigefp.rh.repository;
|
||||
|
||||
import br.gov.sigefp.rh.domain.TaxBracket;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface TaxBracketRepository extends JpaRepository<TaxBracket, UUID> {
|
||||
|
||||
@Query("SELECT b FROM TaxBracket b WHERE b.validFrom <= :date AND (b.validTo IS NULL OR b.validTo >= :date) ORDER BY b.lowerLimit ASC")
|
||||
List<TaxBracket> findActiveBrackets(@Param("date") LocalDate date);
|
||||
|
||||
@Query("SELECT b FROM TaxBracket b WHERE b.deductionType.id = :typeId AND (b.validTo IS NULL OR b.validTo >= :start) AND b.validFrom <= :end "
|
||||
+
|
||||
"AND b.lowerLimit < :upper AND (b.upperLimit IS NULL OR b.upperLimit > :lower)")
|
||||
List<TaxBracket> findOverlappingBrackets(@Param("typeId") UUID typeId,
|
||||
@Param("start") LocalDate start, @Param("end") LocalDate end,
|
||||
@Param("lower") java.math.BigDecimal lower, @Param("upper") java.math.BigDecimal upper);
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
package br.gov.sigefp.rh.service;
|
||||
|
||||
import br.gov.sigefp.common.exception.BusinessException;
|
||||
import br.gov.sigefp.rh.api.dto.AbsenceDTO;
|
||||
import br.gov.sigefp.rh.api.dto.CreateAbsenceDTO;
|
||||
import br.gov.sigefp.rh.domain.Absence;
|
||||
import br.gov.sigefp.rh.domain.Agent;
|
||||
import br.gov.sigefp.rh.repository.AbsenceRepository;
|
||||
import br.gov.sigefp.rh.repository.AgentRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional
|
||||
public class AbsenceService {
|
||||
|
||||
private final AbsenceRepository absenceRepository;
|
||||
private final AgentRepository agentRepository;
|
||||
|
||||
public AbsenceDTO create(CreateAbsenceDTO dto) {
|
||||
Agent agent = agentRepository.findById(dto.getAgentId())
|
||||
.orElseThrow(() -> new IllegalArgumentException("Agent not found"));
|
||||
|
||||
if (dto.getEndDate().isBefore(dto.getStartDate())) {
|
||||
throw new IllegalArgumentException("Data final deve ser posterior ou igual a data inicial");
|
||||
}
|
||||
|
||||
// Calculate days inclusive
|
||||
long daysDiff = ChronoUnit.DAYS.between(dto.getStartDate(), dto.getEndDate()) + 1;
|
||||
|
||||
Absence absence = Absence.builder()
|
||||
.agent(agent)
|
||||
.startDate(dto.getStartDate())
|
||||
.endDate(dto.getEndDate())
|
||||
.days((int) daysDiff)
|
||||
.reason(dto.getReason())
|
||||
.justified(dto.isJustified())
|
||||
.build();
|
||||
|
||||
Absence saved = absenceRepository.save(absence);
|
||||
return toDTO(saved);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public Page<AbsenceDTO> findByAgent(UUID agentId, Pageable pageable) {
|
||||
return absenceRepository.findByAgentId(agentId, pageable)
|
||||
.map(this::toDTO);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<AbsenceDTO> findAllByAgent(UUID agentId) {
|
||||
return absenceRepository.findByAgentId(agentId).stream()
|
||||
.map(this::toDTO)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public void delete(UUID id) {
|
||||
Absence absence = absenceRepository.findById(id)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Absence not found"));
|
||||
if (absence.getDeductedInPayrollRunId() != null) {
|
||||
throw new BusinessException("Cannot delete absence already processed in payroll", "ABSENCE_PROCESSED",
|
||||
org.springframework.http.HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
absenceRepository.delete(absence);
|
||||
}
|
||||
|
||||
private AbsenceDTO toDTO(Absence absence) {
|
||||
return AbsenceDTO.builder()
|
||||
.id(absence.getId())
|
||||
.agentId(absence.getAgent().getId())
|
||||
.agentName(absence.getAgent().getFullName())
|
||||
.startDate(absence.getStartDate())
|
||||
.endDate(absence.getEndDate())
|
||||
.days(absence.getDays())
|
||||
.reason(absence.getReason())
|
||||
.justified(absence.isJustified())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package br.gov.sigefp.rh.service;
|
||||
|
||||
import br.gov.sigefp.rh.domain.Agent;
|
||||
import br.gov.sigefp.rh.domain.AgentBankAccount;
|
||||
import br.gov.sigefp.rh.repository.AgentBankAccountRepository;
|
||||
import br.gov.sigefp.rh.repository.AgentRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Serviço para gestão de contas bancárias de agentes.
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional
|
||||
public class AgentBankAccountService {
|
||||
|
||||
private final AgentBankAccountRepository repository;
|
||||
private final AgentRepository agentRepository;
|
||||
|
||||
public AgentBankAccount save(AgentBankAccount account) {
|
||||
Agent agent = agentRepository.findById(account.getAgent().getId())
|
||||
.orElseThrow(
|
||||
() -> new IllegalArgumentException("Agente não encontrado: " + account.getAgent().getId()));
|
||||
|
||||
account.setAgent(agent);
|
||||
handlePrimaryStatus(account);
|
||||
return repository.save(account);
|
||||
}
|
||||
|
||||
public AgentBankAccount update(AgentBankAccount account) {
|
||||
AgentBankAccount existing = repository.findById(account.getId())
|
||||
.orElseThrow(() -> new IllegalArgumentException("Conta bancária não encontrada: " + account.getId()));
|
||||
|
||||
existing.setBank(account.getBank());
|
||||
existing.setBranchCode(account.getBranchCode());
|
||||
existing.setAccountNumber(account.getAccountNumber());
|
||||
existing.setIban(account.getIban());
|
||||
existing.setIsPrimary(account.getIsPrimary());
|
||||
|
||||
handlePrimaryStatus(existing);
|
||||
return repository.save(existing);
|
||||
}
|
||||
|
||||
private void handlePrimaryStatus(AgentBankAccount account) {
|
||||
if (Boolean.TRUE.equals(account.getIsPrimary())) {
|
||||
// Desativar outras contas primárias do mesmo agente
|
||||
List<AgentBankAccount> currentAccounts = repository.findByAgentId(account.getAgent().getId());
|
||||
for (AgentBankAccount existing : currentAccounts) {
|
||||
if (!existing.getId().equals(account.getId()) && Boolean.TRUE.equals(existing.getIsPrimary())) {
|
||||
existing.setIsPrimary(false);
|
||||
repository.save(existing);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
package br.gov.sigefp.rh.service;
|
||||
|
||||
import br.gov.sigefp.rh.domain.*;
|
||||
import br.gov.sigefp.rh.repository.AgentContractRepository;
|
||||
import br.gov.sigefp.rh.repository.AgentRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Serviço para gestão de contratos de agentes com sincronização automática.
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional
|
||||
public class AgentContractService {
|
||||
|
||||
private final AgentContractRepository contractRepository;
|
||||
private final AgentRepository agentRepository;
|
||||
private final CareerEventService careerEventService;
|
||||
|
||||
/**
|
||||
* Salva um novo contrato, desativando os anteriores e atualizando o agente.
|
||||
*/
|
||||
/**
|
||||
* Atualiza um contrato existente e sincroniza dados se for o contrato ativo.
|
||||
*/
|
||||
public AgentContract updateContract(AgentContract contract) {
|
||||
AgentContract existing = contractRepository.findById(contract.getId())
|
||||
.orElseThrow(() -> new IllegalArgumentException("Contrato não encontrado: " + contract.getId()));
|
||||
|
||||
Agent agent = agentRepository.findById(contract.getAgent().getId())
|
||||
.orElseThrow(
|
||||
() -> new IllegalArgumentException("Agente não encontrado: " + contract.getAgent().getId()));
|
||||
|
||||
// Validar sobreposição
|
||||
validateContractOverlap(agent, existing);
|
||||
|
||||
// Atualizar campos permitidos
|
||||
existing.setContractType(contract.getContractType());
|
||||
existing.setStartDate(contract.getStartDate());
|
||||
existing.setEndDate(contract.getEndDate());
|
||||
existing.setWeeklyHours(contract.getWeeklyHours());
|
||||
existing.setOrgUnit(contract.getOrgUnit());
|
||||
existing.setPosition(contract.getPosition());
|
||||
existing.setSalaryCategory(contract.getSalaryCategory());
|
||||
existing.setSalaryGrade(contract.getSalaryGrade());
|
||||
existing.setSalaryStep(contract.getSalaryStep());
|
||||
existing.setLegalActReference(contract.getLegalActReference());
|
||||
|
||||
AgentContract saved = contractRepository.save(existing);
|
||||
|
||||
// Se o contrato for o ativo, sincronizar os dados no Agente
|
||||
if (Boolean.TRUE.equals(saved.getIsActive())) {
|
||||
syncCareerData(agent, saved);
|
||||
|
||||
String newAppointmentType = mapContractToAppointmentType(saved.getContractType());
|
||||
if (newAppointmentType != null) {
|
||||
agent.setAppointmentType(newAppointmentType);
|
||||
}
|
||||
agentRepository.save(agent);
|
||||
|
||||
// Registrar Evento de Retificação
|
||||
careerEventService.recordEvent(agent, CareerEventType.RETIFICACAO,
|
||||
"Retificação de Ato Administrativo: " + saved.getLegalActReference(),
|
||||
saved.getLegalActReference(), saved.getStartDate(), LocalDate.now(),
|
||||
saved.getSalaryCategory(), saved.getSalaryGrade(), saved.getSalaryStep(),
|
||||
saved.getOrgUnit(), saved.getPosition(),
|
||||
null, null, null, null, null);
|
||||
}
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
public AgentContract saveContract(AgentContract contract) {
|
||||
Agent agent = agentRepository.findById(contract.getAgent().getId())
|
||||
.orElseThrow(
|
||||
() -> new IllegalArgumentException("Agente não encontrado: " + contract.getAgent().getId()));
|
||||
|
||||
// Validar sobreposição antes de desativar
|
||||
validateContractOverlap(agent, contract);
|
||||
|
||||
// 1. Desativar contratos ativos anteriores
|
||||
List<AgentContract> activeContracts = contractRepository.findByAgentId(agent.getId());
|
||||
for (AgentContract oldContract : activeContracts) {
|
||||
if (Boolean.TRUE.equals(oldContract.getIsActive())) {
|
||||
oldContract.setIsActive(false);
|
||||
contractRepository.save(oldContract);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Definir o novo contrato como ativo
|
||||
contract.setIsActive(true);
|
||||
contract.setAgent(agent);
|
||||
AgentContract savedContract = contractRepository.save(contract);
|
||||
|
||||
// 3. Sincronizar todos os dados de carreira do Contrato para o Agente
|
||||
syncCareerData(agent, savedContract);
|
||||
|
||||
// 4. Ativação automática se a data de início for válida
|
||||
if (!savedContract.getStartDate().isAfter(LocalDate.now())) {
|
||||
agent.setStatus("ACTIVE");
|
||||
}
|
||||
|
||||
// 5. Sincronizar appointmentType do Agente
|
||||
String newAppointmentType = mapContractToAppointmentType(contract.getContractType());
|
||||
if (newAppointmentType != null) {
|
||||
agent.setAppointmentType(newAppointmentType);
|
||||
}
|
||||
|
||||
agentRepository.save(agent);
|
||||
|
||||
// 6. Registar Evento de Carreira Automático via Serviço Centralizado
|
||||
recordAutomaticCareerEvent(agent, savedContract);
|
||||
|
||||
return savedContract;
|
||||
}
|
||||
|
||||
private void syncCareerData(Agent agent, AgentContract contract) {
|
||||
if (contract.getOrgUnit() != null)
|
||||
agent.setOrgUnit(contract.getOrgUnit());
|
||||
if (contract.getPosition() != null)
|
||||
agent.setPosition(contract.getPosition());
|
||||
if (contract.getSalaryCategory() != null)
|
||||
agent.setSalaryCategory(contract.getSalaryCategory());
|
||||
if (contract.getSalaryGrade() != null)
|
||||
agent.setSalaryGrade(contract.getSalaryGrade());
|
||||
if (contract.getSalaryStep() != null)
|
||||
agent.setSalaryStep(contract.getSalaryStep());
|
||||
}
|
||||
|
||||
private String mapContractToAppointmentType(String contractType) {
|
||||
if (contractType == null)
|
||||
return null;
|
||||
return switch (contractType.toUpperCase()) {
|
||||
case "DEFINITIVA", "PERMANENT" -> "DEFINITIVA";
|
||||
case "PROVISORIA", "PROBATION" -> "PROVISORIA";
|
||||
case "FIXED_TERM", "CONTRATO", "TERMO" -> "CONTRATO_TERMO";
|
||||
case "PROVIMENTO" -> "CONTRATO_PROVIMENTO";
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
private void recordAutomaticCareerEvent(Agent agent, AgentContract contract) {
|
||||
CareerEventType eventType = switch (contract.getContractType().toUpperCase()) {
|
||||
case "DEFINITIVA" -> CareerEventType.NOMEACAO_DEFINITIVA;
|
||||
case "PROVISORIA" -> CareerEventType.NOMEACAO_PROVISORIA;
|
||||
default -> CareerEventType.ADMISSAO;
|
||||
};
|
||||
|
||||
careerEventService.recordEvent(agent, eventType,
|
||||
"Ato Administrativo: " + contract.getLegalActReference() + " (" + contract.getContractType() + ")",
|
||||
contract.getLegalActReference(), contract.getStartDate(), LocalDate.now(),
|
||||
contract.getSalaryCategory(), contract.getSalaryGrade(), contract.getSalaryStep(),
|
||||
contract.getOrgUnit(), contract.getPosition(),
|
||||
null, null, null, null, null);
|
||||
}
|
||||
|
||||
private void validateContractOverlap(Agent agent, AgentContract newContract) {
|
||||
List<AgentContract> existingContracts = contractRepository.findByAgentId(agent.getId());
|
||||
for (AgentContract existing : existingContracts) {
|
||||
// Skip if it's the same contract (update case)
|
||||
if (newContract.getId() != null && newContract.getId().equals(existing.getId())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Logic: Overlap if (StartA <= EndB) and (EndA >= StartB)
|
||||
// Handle null endDate as "Infinity"
|
||||
LocalDate startA = existing.getStartDate();
|
||||
LocalDate endA = existing.getEndDate();
|
||||
LocalDate startB = newContract.getStartDate();
|
||||
LocalDate endB = newContract.getEndDate();
|
||||
|
||||
boolean startA_le_endB = (endB == null) || !startA.isAfter(endB);
|
||||
boolean endA_ge_startB = (endA == null) || !endA.isBefore(startB);
|
||||
|
||||
if (startA_le_endB && endA_ge_startB) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Sobreposição detectada com contrato existente (ID: %s, Início: %s, Fim: %s)",
|
||||
existing.getId(), startA, endA));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
package br.gov.sigefp.rh.service;
|
||||
|
||||
import br.gov.sigefp.rh.api.dto.AgentImportDTO;
|
||||
import br.gov.sigefp.rh.api.dto.AgentImportResultDTO;
|
||||
import br.gov.sigefp.rh.domain.Agent;
|
||||
import br.gov.sigefp.rh.domain.AgentBankAccount;
|
||||
import br.gov.sigefp.rh.domain.AgentContract;
|
||||
import br.gov.sigefp.rh.repository.AgentRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.poi.ss.usermodel.*;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class AgentImportService {
|
||||
|
||||
private final AgentRepository agentRepository;
|
||||
private final AgentContractService contractService;
|
||||
// Removed AgentService to avoid nested @PreAuthorize issues
|
||||
|
||||
public AgentImportResultDTO importAgents(MultipartFile file) {
|
||||
AgentImportResultDTO result = AgentImportResultDTO.builder()
|
||||
.errors(new ArrayList<>())
|
||||
.build();
|
||||
|
||||
try (InputStream is = file.getInputStream();
|
||||
Workbook workbook = new XSSFWorkbook(is)) {
|
||||
|
||||
Sheet sheet = workbook.getSheetAt(0);
|
||||
int rowIdx = 0;
|
||||
|
||||
for (Row row : sheet) {
|
||||
if (rowIdx == 0) { // Skip Header
|
||||
rowIdx++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip empty rows
|
||||
if (row.getCell(0) == null || row.getCell(0).getStringCellValue().trim().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
processRow(row);
|
||||
result.setSuccessCount(result.getSuccessCount() + 1);
|
||||
} catch (Exception e) {
|
||||
result.setFailureCount(result.getFailureCount() + 1);
|
||||
String errorMsg = String.format("Linha %d: %s", rowIdx + 1, e.getMessage());
|
||||
result.getErrors().add(errorMsg);
|
||||
log.error("Erro importando linha {}", rowIdx + 1, e);
|
||||
}
|
||||
result.setTotalProcessed(result.getTotalProcessed() + 1);
|
||||
rowIdx++;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Erro ao ler arquivo Excel", e);
|
||||
result.getErrors().add("Erro fatal ao ler arquivo: " + e.getMessage());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
protected void processRow(Row row) {
|
||||
// 1. Parse DTO from Excel row
|
||||
AgentImportDTO dto = parseRowToDTO(row);
|
||||
|
||||
// 2. Determine matricula: use from Excel or default to NIF
|
||||
String matricula = (dto.getMatricula() != null && !dto.getMatricula().isEmpty())
|
||||
? dto.getMatricula()
|
||||
: dto.getNif();
|
||||
|
||||
// 3. Validate unique constraints
|
||||
if (agentRepository.existsByMatricula(matricula)) {
|
||||
throw new IllegalArgumentException("Matrícula já existe: " + matricula);
|
||||
}
|
||||
if (agentRepository.existsByNif(dto.getNif())) {
|
||||
throw new IllegalArgumentException("NIF já existe: " + dto.getNif());
|
||||
}
|
||||
if (agentRepository.existsByBiNumber(dto.getBi())) {
|
||||
throw new IllegalArgumentException("BI já existe: " + dto.getBi());
|
||||
}
|
||||
|
||||
// 4. Build Agent entity directly
|
||||
LocalDate hireDate = parseDate(dto.getDataInicioContrato());
|
||||
if (hireDate == null) {
|
||||
hireDate = LocalDate.now();
|
||||
}
|
||||
|
||||
// Data de nomeação: usa do Excel ou default para hireDate
|
||||
LocalDate appointmentDate = parseDate(dto.getNomeacao());
|
||||
if (appointmentDate == null) {
|
||||
appointmentDate = hireDate;
|
||||
}
|
||||
|
||||
Agent agent = Agent.builder()
|
||||
.fullName(dto.getNome())
|
||||
.nif(dto.getNif())
|
||||
.biNumber(dto.getBi())
|
||||
.matricula(matricula) // Use from Excel or default to NIF
|
||||
.birthDate(parseDate(dto.getDataNascimento()))
|
||||
.status("ACTIVE")
|
||||
.hireDate(hireDate)
|
||||
.posseDate(appointmentDate) // Data de nomeação/posse
|
||||
.literaryQualification(dto.getHabilitacaoLiteraria())
|
||||
.appointmentType("PROVISORIA")
|
||||
.functionalSituation("ATIVIDADE_NO_QUADRO")
|
||||
.build();
|
||||
|
||||
// 5. Save Agent directly (bypassing AgentService.create() @PreAuthorize)
|
||||
Agent savedAgent = agentRepository.save(agent);
|
||||
log.info("Agente importado com sucesso: {} (ID: {})", savedAgent.getFullName(), savedAgent.getId());
|
||||
|
||||
// 6. Create Contract (if present)
|
||||
if (dto.getTipoContrato() != null && !dto.getTipoContrato().isEmpty()) {
|
||||
AgentContract contract = AgentContract.builder()
|
||||
.agent(savedAgent)
|
||||
.contractType(dto.getTipoContrato())
|
||||
.startDate(parseDate(dto.getDataInicioContrato()))
|
||||
.endDate(parseDate(dto.getDataFimContrato()))
|
||||
.isActive(true)
|
||||
.build();
|
||||
|
||||
contractService.saveContract(contract);
|
||||
}
|
||||
|
||||
// 7. Create Bank Account (if IBAN provided)
|
||||
if (dto.getIban() != null && !dto.getIban().isEmpty()) {
|
||||
AgentBankAccount bankAccount = AgentBankAccount.builder()
|
||||
.agent(savedAgent)
|
||||
.bank(dto.getBancoCodigo())
|
||||
.iban(dto.getIban())
|
||||
.isPrimary(true) // First bank account is primary
|
||||
.build();
|
||||
|
||||
savedAgent.getBankAccounts().add(bankAccount);
|
||||
agentRepository.save(savedAgent);
|
||||
}
|
||||
}
|
||||
|
||||
private AgentImportDTO parseRowToDTO(Row row) {
|
||||
// Column order: NOME, NIF, MATRICULA, BI, DATA_NASCIMENTO, SEXO, ESTADO_CIVIL,
|
||||
// HABILITACAO_LITERARIA, CATEGORIA_CODIGO, UNIDADE_ORG_CODIGO,
|
||||
// NOMEACAO, TIPO_CONTRATO, DATA_INICIO_CONTRATO, DATA_FIM_CONTRATO,
|
||||
// BANCO_CODIGO, IBAN
|
||||
return AgentImportDTO.builder()
|
||||
.nome(getCellString(row, 0))
|
||||
.nif(getCellString(row, 1))
|
||||
.matricula(getCellString(row, 2)) // MATRICULA column
|
||||
.bi(getCellString(row, 3))
|
||||
.dataNascimento(getCellString(row, 4))
|
||||
.sexo(getCellString(row, 5))
|
||||
.estadoCivil(getCellString(row, 6))
|
||||
.habilitacaoLiteraria(getCellString(row, 7))
|
||||
.categoriaCodigo(getCellString(row, 8))
|
||||
.unidadeOrgCodigo(getCellString(row, 9))
|
||||
.nomeacao(getCellString(row, 10)) // NOMEACAO (data de nomeação)
|
||||
.tipoContrato(getCellString(row, 11))
|
||||
.dataInicioContrato(getCellString(row, 12))
|
||||
.dataFimContrato(getCellString(row, 13))
|
||||
.bancoCodigo(getCellString(row, 14))
|
||||
.iban(getCellString(row, 15))
|
||||
.build();
|
||||
}
|
||||
|
||||
private String getCellString(Row row, int index) {
|
||||
Cell cell = row.getCell(index);
|
||||
if (cell == null)
|
||||
return null;
|
||||
DataFormatter formatter = new DataFormatter();
|
||||
return formatter.formatCellValue(cell).trim();
|
||||
}
|
||||
|
||||
private LocalDate parseDate(String dateStr) {
|
||||
if (dateStr == null || dateStr.isEmpty())
|
||||
return null;
|
||||
|
||||
// Formatos suportados
|
||||
String[] patterns = {
|
||||
"dd/MM/yyyy",
|
||||
"dd-MM-yyyy",
|
||||
"yyyy-MM-dd",
|
||||
"d/M/yyyy",
|
||||
"d-M-yyyy",
|
||||
"dd/MM/yy",
|
||||
"d/M/yy"
|
||||
};
|
||||
|
||||
for (String pattern : patterns) {
|
||||
try {
|
||||
return LocalDate.parse(dateStr, DateTimeFormatter.ofPattern(pattern));
|
||||
} catch (DateTimeParseException ignored) {
|
||||
// Try next pattern
|
||||
}
|
||||
}
|
||||
|
||||
// Tenta interpretar número serial do Excel (dias desde 1899-12-30)
|
||||
try {
|
||||
double excelDate = Double.parseDouble(dateStr);
|
||||
// Excel date serial number: days since 1899-12-30
|
||||
return LocalDate.of(1899, 12, 30).plusDays((long) excelDate);
|
||||
} catch (NumberFormatException ignored) {
|
||||
// Not a number
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Data inválida: " + dateStr + ". Use dd/MM/yyyy");
|
||||
}
|
||||
}
|
||||
+678
@@ -0,0 +1,678 @@
|
||||
package br.gov.sigefp.rh.service;
|
||||
|
||||
import br.gov.sigefp.org.repository.OrgUnitRepository;
|
||||
import br.gov.sigefp.org.repository.PositionRepository;
|
||||
import br.gov.sigefp.rh.api.dto.*;
|
||||
import br.gov.sigefp.rh.domain.*;
|
||||
import br.gov.sigefp.rh.repository.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Serviço de aplicação para gestão de agentes.
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional
|
||||
@lombok.extern.slf4j.Slf4j
|
||||
public class AgentService {
|
||||
|
||||
private final AgentRepository agentRepository;
|
||||
private final OrgUnitRepository orgUnitRepository;
|
||||
private final PositionRepository positionRepository;
|
||||
private final AgentStatusHistoryRepository statusHistoryRepository;
|
||||
private final CareerEventRepository careerEventRepository;
|
||||
private final PerformanceEvaluationRepository performanceEvaluationRepository;
|
||||
private final CareerEventService careerEventService;
|
||||
private final AgentContractRepository agentContractRepository;
|
||||
private final AgentBankAccountRepository agentBankAccountRepository;
|
||||
|
||||
@org.springframework.security.access.prepost.PreAuthorize("hasRole('HR_ADMIN')")
|
||||
public AgentDTO create(AgentDTO dto) {
|
||||
if (agentRepository.existsByMatricula(dto.getMatricula())) {
|
||||
throw new IllegalArgumentException("Matrícula já existe: " + dto.getMatricula());
|
||||
}
|
||||
if (agentRepository.existsByNif(dto.getNif())) {
|
||||
throw new IllegalArgumentException("NIF já existe: " + dto.getNif());
|
||||
}
|
||||
if (agentRepository.existsByBiNumber(dto.getBiNumber())) {
|
||||
throw new IllegalArgumentException("Número de BI já existe: " + dto.getBiNumber());
|
||||
}
|
||||
|
||||
// Validações cruzadas
|
||||
if (dto.getOrgUnit() != null && !orgUnitRepository.existsById(dto.getOrgUnit())) {
|
||||
throw new IllegalArgumentException("Unidade organizacional não encontrada: " + dto.getOrgUnit());
|
||||
}
|
||||
if (dto.getPosition() != null && !positionRepository.existsById(dto.getPosition())) {
|
||||
throw new IllegalArgumentException("Posição não encontrada: " + dto.getPosition());
|
||||
}
|
||||
|
||||
// P17: Validar Habilitação Literária
|
||||
validateLiteraryQualification(dto.getSalaryCategory(), dto.getLiteraryQualification());
|
||||
|
||||
Agent agent = Agent.builder()
|
||||
.matricula(dto.getMatricula())
|
||||
.nif(dto.getNif())
|
||||
.biNumber(dto.getBiNumber())
|
||||
.fullName(dto.getFullName())
|
||||
.birthDate(dto.getBirthDate())
|
||||
.hireDate(dto.getHireDate())
|
||||
.posseDate(dto.getPosseDate())
|
||||
.terminationDate(dto.getTerminationDate())
|
||||
.appointmentType(dto.getAppointmentType() != null ? dto.getAppointmentType() : "PROVISORIA")
|
||||
.functionalSituation(
|
||||
dto.getFunctionalSituation() != null ? dto.getFunctionalSituation() : "ATIVIDADE_NO_QUADRO")
|
||||
.status(dto.getStatus() != null ? dto.getStatus() : "REGISTERED")
|
||||
.eligibleDependentsCount(dto.getEligibleDependentsCount())
|
||||
.literaryQualification(dto.getLiteraryQualification())
|
||||
.salaryCategory(dto.getSalaryCategory())
|
||||
.salaryGrade(dto.getSalaryGrade())
|
||||
.salaryStep(dto.getSalaryStep())
|
||||
.orgUnit(dto.getOrgUnit())
|
||||
.position(dto.getPosition())
|
||||
.nationality(dto.getNationality())
|
||||
.phone(dto.getPhone())
|
||||
.email(dto.getEmail())
|
||||
.address(dto.getAddress())
|
||||
.build();
|
||||
|
||||
Agent saved = agentRepository.save(agent);
|
||||
careerEventService.recordSimpleEvent(saved, CareerEventType.ADMISSAO, "Admissão inicial no sistema", null,
|
||||
saved.getHireDate());
|
||||
return toDTO(saved);
|
||||
}
|
||||
|
||||
@org.springframework.security.access.prepost.PreAuthorize("hasRole('HR_ADMIN')")
|
||||
public AgentDTO update(UUID id, AgentDTO dto) {
|
||||
// P17: Validar Habilitação Literária (em update também)
|
||||
validateLiteraryQualification(dto.getSalaryCategory(), dto.getLiteraryQualification());
|
||||
|
||||
Agent agent = agentRepository.findById(id)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Agente não encontrado: " + id));
|
||||
|
||||
// Capture previous state for history
|
||||
String previousStatus = agent.getStatus();
|
||||
String previousSituation = agent.getFunctionalSituation();
|
||||
UUID previousCategory = agent.getSalaryCategory();
|
||||
UUID previousGrade = agent.getSalaryGrade();
|
||||
UUID previousStep = agent.getSalaryStep();
|
||||
UUID previousOrgUnit = agent.getOrgUnit();
|
||||
UUID previousPosition = agent.getPosition();
|
||||
|
||||
StringBuilder changeLog = new StringBuilder();
|
||||
|
||||
// Detect Changes for ChangeLog
|
||||
detectAndApplyChanges(agent, dto, changeLog);
|
||||
|
||||
boolean statusChanged = dto.getStatus() != null && !dto.getStatus().equals(previousStatus);
|
||||
boolean situationChanged = dto.getFunctionalSituation() != null
|
||||
&& !dto.getFunctionalSituation().equals(previousSituation);
|
||||
boolean salaryChanged = isSalaryChanged(agent, dto);
|
||||
boolean orgChanged = isOrgChanged(agent, dto);
|
||||
|
||||
CareerEventType careerEventType = null;
|
||||
boolean careerImpact = false;
|
||||
|
||||
if (statusChanged || situationChanged || salaryChanged || orgChanged) {
|
||||
careerImpact = true;
|
||||
careerEventType = determineEventType(dto, agent, situationChanged, salaryChanged, orgChanged);
|
||||
|
||||
if (careerEventType == CareerEventType.PROMOCAO) {
|
||||
validatePromotion(agent);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply changes to entity
|
||||
applyUpdates(agent, dto);
|
||||
|
||||
// Record History
|
||||
if (changeLog.length() > 0 || careerImpact) {
|
||||
AgentStatusHistory history = AgentStatusHistory.builder()
|
||||
.agent(agent)
|
||||
.previousStatus(previousStatus)
|
||||
.newStatus(agent.getStatus())
|
||||
.previousFunctionalSituation(previousSituation)
|
||||
.newFunctionalSituation(agent.getFunctionalSituation())
|
||||
.eventType(careerImpact && careerEventType != null ? careerEventType.name() : "EDIT")
|
||||
.reason(dto.getStatusChangeReason())
|
||||
.changeLog(changeLog.length() > 0 ? changeLog.toString() : "Atualização cadastral.")
|
||||
.changedAt(LocalDateTime.now())
|
||||
.changedBy("system")
|
||||
.build();
|
||||
statusHistoryRepository.save(history);
|
||||
|
||||
if (careerImpact && careerEventType != null) {
|
||||
careerEventService.recordEvent(agent, careerEventType, dto.getStatusChangeReason(),
|
||||
dto.getEventDocumentRef(),
|
||||
dto.getEventEffectiveDate(), dto.getEventPublicationDate(),
|
||||
dto.getSalaryCategory(), dto.getSalaryGrade(), dto.getSalaryStep(),
|
||||
dto.getOrgUnit(), dto.getPosition(),
|
||||
previousCategory, previousGrade, previousStep,
|
||||
previousOrgUnit, previousPosition);
|
||||
}
|
||||
}
|
||||
|
||||
Agent saved = agentRepository.save(agent);
|
||||
return toDTO(saved);
|
||||
}
|
||||
|
||||
@org.springframework.security.access.prepost.PreAuthorize("hasRole('HR_ADMIN')")
|
||||
public void delete(UUID id) {
|
||||
Agent agent = agentRepository.findById(id)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Agente não encontrado: " + id));
|
||||
|
||||
log.info("Excluindo agente: {} (ID: {})", agent.getFullName(), agent.getId());
|
||||
|
||||
// Excluir dados relacionados primeiro (para evitar problemas de FK)
|
||||
// Os contratos, contas bancárias e histórico são deletados em cascata via
|
||||
// orphanRemoval
|
||||
// Mas eventos de carreira e histórico de status precisam ser excluídos
|
||||
// explicitamente
|
||||
|
||||
// Excluir eventos de carreira
|
||||
careerEventRepository.deleteByAgentId(id);
|
||||
|
||||
// Excluir histórico de status
|
||||
statusHistoryRepository.deleteByAgentId(id);
|
||||
|
||||
// Por fim, excluir o agente (contratos e contas bancárias são excluídos em
|
||||
// cascata)
|
||||
agentRepository.delete(agent);
|
||||
|
||||
log.info("Agente excluído com sucesso: {}", id);
|
||||
}
|
||||
|
||||
private void detectAndApplyChanges(Agent agent, AgentDTO dto, StringBuilder log) {
|
||||
compareAndLog(log, "Nome", agent.getFullName(), dto.getFullName());
|
||||
compareAndLog(log, "NIF", agent.getNif(), dto.getNif());
|
||||
compareAndLog(log, "BI", agent.getBiNumber(), dto.getBiNumber());
|
||||
compareAndLog(log, "Email", agent.getEmail(), dto.getEmail());
|
||||
compareAndLog(log, "Telefone", agent.getPhone(), dto.getPhone());
|
||||
compareAndLog(log, "Endereço", agent.getAddress(), dto.getAddress());
|
||||
compareAndLog(log, "Situação Funcional", agent.getFunctionalSituation(), dto.getFunctionalSituation());
|
||||
compareAndLog(log, "Status", agent.getStatus(), dto.getStatus());
|
||||
compareAndLog(log, "Nº Dependentes", agent.getEligibleDependentsCount(), dto.getEligibleDependentsCount());
|
||||
compareAndLog(log, "Habilitação Literária", agent.getLiteraryQualification(), dto.getLiteraryQualification());
|
||||
|
||||
if (dto.getOrgUnit() != null && !Objects.equals(dto.getOrgUnit(), agent.getOrgUnit())) {
|
||||
log.append("Unidade Orgânica: [").append(agent.getOrgUnit()).append("] -> [").append(dto.getOrgUnit())
|
||||
.append("]. ");
|
||||
}
|
||||
if (dto.getPosition() != null && !Objects.equals(dto.getPosition(), agent.getPosition())) {
|
||||
log.append("Cargo/Posição: [").append(agent.getPosition()).append("] -> [").append(dto.getPosition())
|
||||
.append("]. ");
|
||||
}
|
||||
if (isSalaryChanged(agent, dto)) {
|
||||
log.append("Estrutura salarial/carreira alterada. ");
|
||||
}
|
||||
}
|
||||
|
||||
private void compareAndLog(StringBuilder log, String fieldName, Object oldValue, Object newValue) {
|
||||
if (newValue != null && !Objects.equals(oldValue, newValue)) {
|
||||
String oldStr = oldValue != null ? oldValue.toString() : "N/A";
|
||||
String newStr = newValue.toString();
|
||||
log.append(fieldName).append(": [").append(oldStr).append("] -> [").append(newStr).append("]. ");
|
||||
}
|
||||
}
|
||||
|
||||
private void applyUpdates(Agent agent, AgentDTO dto) {
|
||||
if (dto.getMatricula() != null)
|
||||
agent.setMatricula(dto.getMatricula());
|
||||
if (dto.getNif() != null)
|
||||
agent.setNif(dto.getNif());
|
||||
if (dto.getBiNumber() != null)
|
||||
agent.setBiNumber(dto.getBiNumber());
|
||||
if (dto.getFullName() != null)
|
||||
agent.setFullName(dto.getFullName());
|
||||
if (dto.getBirthDate() != null)
|
||||
agent.setBirthDate(dto.getBirthDate());
|
||||
if (dto.getHireDate() != null)
|
||||
agent.setHireDate(dto.getHireDate());
|
||||
if (dto.getPosseDate() != null)
|
||||
agent.setPosseDate(dto.getPosseDate());
|
||||
if (dto.getTerminationDate() != null)
|
||||
agent.setTerminationDate(dto.getTerminationDate());
|
||||
if (dto.getAppointmentType() != null)
|
||||
agent.setAppointmentType(dto.getAppointmentType());
|
||||
if (dto.getFunctionalSituation() != null)
|
||||
agent.setFunctionalSituation(dto.getFunctionalSituation());
|
||||
if (dto.getEligibleDependentsCount() != null)
|
||||
agent.setEligibleDependentsCount(dto.getEligibleDependentsCount());
|
||||
if (dto.getStatus() != null)
|
||||
agent.setStatus(dto.getStatus());
|
||||
if (dto.getOrgUnit() != null)
|
||||
agent.setOrgUnit(dto.getOrgUnit());
|
||||
if (dto.getPosition() != null)
|
||||
agent.setPosition(dto.getPosition());
|
||||
if (dto.getNationality() != null)
|
||||
agent.setNationality(dto.getNationality());
|
||||
if (dto.getPhone() != null)
|
||||
agent.setPhone(dto.getPhone());
|
||||
if (dto.getEmail() != null)
|
||||
agent.setEmail(dto.getEmail());
|
||||
if (dto.getAddress() != null)
|
||||
agent.setAddress(dto.getAddress());
|
||||
if (dto.getLiteraryQualification() != null)
|
||||
agent.setLiteraryQualification(dto.getLiteraryQualification());
|
||||
if (dto.getSalaryCategory() != null)
|
||||
agent.setSalaryCategory(dto.getSalaryCategory());
|
||||
if (dto.getSalaryGrade() != null)
|
||||
agent.setSalaryGrade(dto.getSalaryGrade());
|
||||
if (dto.getSalaryStep() != null)
|
||||
agent.setSalaryStep(dto.getSalaryStep());
|
||||
}
|
||||
|
||||
private boolean isSalaryChanged(Agent agent, AgentDTO dto) {
|
||||
return (dto.getSalaryCategory() != null && !Objects.equals(dto.getSalaryCategory(), agent.getSalaryCategory()))
|
||||
|| (dto.getSalaryGrade() != null && !Objects.equals(dto.getSalaryGrade(), agent.getSalaryGrade()))
|
||||
|| (dto.getSalaryStep() != null && !Objects.equals(dto.getSalaryStep(), agent.getSalaryStep()));
|
||||
}
|
||||
|
||||
private boolean isOrgChanged(Agent agent, AgentDTO dto) {
|
||||
return (dto.getOrgUnit() != null && !Objects.equals(dto.getOrgUnit(), agent.getOrgUnit()))
|
||||
|| (dto.getPosition() != null && !Objects.equals(dto.getPosition(), agent.getPosition()));
|
||||
}
|
||||
|
||||
private CareerEventType determineEventType(AgentDTO dto, Agent agent, boolean situationChanged,
|
||||
boolean salaryChanged, boolean orgChanged) {
|
||||
if (salaryChanged) {
|
||||
if (dto.getSalaryCategory() != null
|
||||
&& !Objects.equals(dto.getSalaryCategory(), agent.getSalaryCategory())) {
|
||||
return CareerEventType.PROMOCAO;
|
||||
}
|
||||
return CareerEventType.PROGRESSAO;
|
||||
}
|
||||
|
||||
if (situationChanged) {
|
||||
return switch (dto.getFunctionalSituation()) {
|
||||
case "SUBSTITUICAO" -> CareerEventType.SUBSTITUICAO;
|
||||
case "TRANSFERENCIA" -> CareerEventType.TRANSFERENCIA;
|
||||
case "COMISSAO_SERVICO" -> CareerEventType.COMISSAO_SERVICO;
|
||||
case "RECLASSIFICACAO" -> CareerEventType.RECLASSIFICACAO;
|
||||
case "DEFINITIVA" -> CareerEventType.NOMEACAO_DEFINITIVA;
|
||||
default -> CareerEventType.TRANSFERENCIA; // Fallback para outros movimentos
|
||||
};
|
||||
}
|
||||
|
||||
if (orgChanged)
|
||||
return CareerEventType.TRANSFERENCIA;
|
||||
|
||||
return CareerEventType.PROGRESSAO; // Default
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public AgentDTO findById(UUID id) {
|
||||
Agent agent = agentRepository.findById(id)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Agente não encontrado: " + id));
|
||||
return toDTO(agent);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public Page<AgentDTO> findAll(Pageable pageable) {
|
||||
return agentRepository.findAll(pageable).map(this::toDTO);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public Page<AgentDTO> search(String query, Pageable pageable) {
|
||||
if (query == null || query.isBlank()) {
|
||||
return findAll(pageable);
|
||||
}
|
||||
return agentRepository.findByFullNameContainingIgnoreCaseOrMatriculaContainingIgnoreCase(
|
||||
query, query, pageable).map(this::toDTO);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public Page<AgentDTO> findAllWithFilters(
|
||||
String query,
|
||||
String status,
|
||||
UUID ministryId,
|
||||
UUID orgUnitId,
|
||||
UUID positionId,
|
||||
String functionalSituation,
|
||||
String appointmentType,
|
||||
Pageable pageable) {
|
||||
|
||||
Specification<Agent> spec = buildSpecification(query, status, ministryId, orgUnitId, positionId,
|
||||
functionalSituation, appointmentType);
|
||||
Page<Agent> agentsPage = agentRepository.findAll(spec, pageable);
|
||||
|
||||
// Otimização N+1: Carregar todos os contratos ativos e contas bancárias
|
||||
// principais em lote para os agentes da página
|
||||
List<UUID> agentIds = agentsPage.getContent().stream().map(Agent::getId).collect(Collectors.toList());
|
||||
|
||||
if (!agentIds.isEmpty()) {
|
||||
// Isso aciona o carregamento das coleções para a sessão atual (Hibernate Batch
|
||||
// Fetching deve estar configurado no yml ou na entidade)
|
||||
// Como as relações estão mapeadas com @OneToMany, o Hibernate pode usar
|
||||
// batch-size se configurado.
|
||||
// Alternativamente, podemos fazer uma query explícita para "aquecer" o cache de
|
||||
// primeiro nível.
|
||||
agentContractRepository.findAllByAgentIdInAndIsActiveTrue(agentIds);
|
||||
agentBankAccountRepository.findAllByAgentIdInAndIsPrimaryTrue(agentIds);
|
||||
}
|
||||
|
||||
return agentsPage.map(this::toDTO);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public br.gov.sigefp.rh.api.dto.AgentStatsDTO getStats(
|
||||
String query,
|
||||
String status,
|
||||
UUID ministryId,
|
||||
UUID orgUnitId,
|
||||
UUID positionId,
|
||||
String functionalSituation,
|
||||
String appointmentType) {
|
||||
|
||||
log.info("Calculando estatísticas otimizadas com filtros - Ministério: {}, OrgUnit: {}, Query: {}", ministryId,
|
||||
orgUnitId,
|
||||
query);
|
||||
|
||||
Specification<Agent> baseSpec = buildSpecification(query, null, ministryId, orgUnitId, positionId,
|
||||
functionalSituation, appointmentType);
|
||||
|
||||
// Usar a nova consulta agregada para obter todos os status de uma vez
|
||||
// Nota: Infelizmente a Specification não é trivial de passar para uma Query
|
||||
// customizada com GROUP BY
|
||||
// sem usar CriteriaBuilder complexo. Para manter a performance, vamos usar o
|
||||
// repositório para o total
|
||||
// e otimizar as consultas individuais se a especificação for complexa, ou usar
|
||||
// uma abordagem simplificada
|
||||
// se possível.
|
||||
|
||||
// Por enquanto, vamos reduzir as chamadas mantendo a lógica de Specification
|
||||
// mas consolidando em uma abordagem mais eficiente
|
||||
// se buildSpecification for nulo (ou seja, sem filtros extras).
|
||||
|
||||
long total = agentRepository.count(baseSpec);
|
||||
|
||||
br.gov.sigefp.rh.api.dto.AgentStatsDTO stats = br.gov.sigefp.rh.api.dto.AgentStatsDTO.builder()
|
||||
.total(total)
|
||||
.build();
|
||||
|
||||
if (total > 0) {
|
||||
// Executar consultas de contagem por status usando a mesma especificação
|
||||
stats.setActive(agentRepository
|
||||
.count(baseSpec.and(br.gov.sigefp.rh.specification.AgentSpecifications.hasStatus("ACTIVE"))));
|
||||
stats.setInactive(agentRepository
|
||||
.count(baseSpec.and(br.gov.sigefp.rh.specification.AgentSpecifications.hasStatus("INACTIVE"))));
|
||||
stats.setSuspended(agentRepository
|
||||
.count(baseSpec.and(br.gov.sigefp.rh.specification.AgentSpecifications.hasStatus("SUSPENDED"))));
|
||||
stats.setTerminated(agentRepository
|
||||
.count(baseSpec.and(br.gov.sigefp.rh.specification.AgentSpecifications.hasStatus("TERMINATED"))));
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
private Specification<Agent> buildSpecification(
|
||||
String query,
|
||||
String status,
|
||||
UUID ministryId,
|
||||
UUID orgUnitId,
|
||||
UUID positionId,
|
||||
String functionalSituation,
|
||||
String appointmentType) {
|
||||
|
||||
Specification<Agent> spec = Specification.where(null);
|
||||
|
||||
if (query != null && !query.isBlank()) {
|
||||
spec = spec.and(br.gov.sigefp.rh.specification.AgentSpecifications.searchByQuery(query));
|
||||
}
|
||||
if (status != null && !status.isBlank()) {
|
||||
spec = spec.and(br.gov.sigefp.rh.specification.AgentSpecifications.hasStatus(status));
|
||||
}
|
||||
|
||||
// Se um ministério for selecionado, filtramos por todas as unidades
|
||||
// subordinadas a ele
|
||||
if (ministryId != null) {
|
||||
List<UUID> ministryOrgUnits = orgUnitRepository.findByMinistryId(ministryId)
|
||||
.stream().map(br.gov.sigefp.org.domain.OrgUnit::getId).collect(Collectors.toList());
|
||||
|
||||
// Garantir que a unidade do próprio ministério esteja na lista
|
||||
if (!ministryOrgUnits.contains(ministryId)) {
|
||||
ministryOrgUnits.add(ministryId);
|
||||
}
|
||||
|
||||
if (!ministryOrgUnits.isEmpty()) {
|
||||
spec = spec.and(br.gov.sigefp.rh.specification.AgentSpecifications.hasOrgUnitIn(ministryOrgUnits));
|
||||
} else {
|
||||
spec = spec.and((root, q, cb) -> cb.disjunction());
|
||||
}
|
||||
}
|
||||
|
||||
if (orgUnitId != null) {
|
||||
spec = spec.and(br.gov.sigefp.rh.specification.AgentSpecifications.hasOrgUnit(orgUnitId));
|
||||
}
|
||||
if (positionId != null) {
|
||||
spec = spec.and(br.gov.sigefp.rh.specification.AgentSpecifications.hasPosition(positionId));
|
||||
}
|
||||
if (functionalSituation != null && !functionalSituation.isBlank()) {
|
||||
spec = spec.and(
|
||||
br.gov.sigefp.rh.specification.AgentSpecifications.hasFunctionalSituation(functionalSituation));
|
||||
}
|
||||
if (appointmentType != null && !appointmentType.isBlank()) {
|
||||
spec = spec.and(br.gov.sigefp.rh.specification.AgentSpecifications.hasAppointmentType(appointmentType));
|
||||
}
|
||||
|
||||
return spec;
|
||||
}
|
||||
|
||||
private void validatePromotion(Agent agent) {
|
||||
// Regra do Decreto 12-A/94: Avaliação de desempenho de, no mínimo, "Bom" nos
|
||||
// últimos três anos.
|
||||
int currentYear = LocalDate.now().getYear();
|
||||
List<PerformanceEvaluation> evals = performanceEvaluationRepository
|
||||
.findByAgentIdAndReferenceYearBetweenOrderByReferenceYearDesc(agent.getId(), currentYear - 3,
|
||||
currentYear - 1);
|
||||
|
||||
if (evals.size() < 3) {
|
||||
throw new IllegalStateException(
|
||||
"O agente deve ter pelo menos 3 anos de avaliações de desempenho para ser promovido.");
|
||||
}
|
||||
|
||||
for (PerformanceEvaluation eval : evals) {
|
||||
if (eval.getScore() < 14) { // 14 é o mínimo para "Bom" na escala 0-20
|
||||
throw new IllegalStateException(
|
||||
"O agente não cumpre o requisito de avaliação 'Bom' (mínimo 14 pontos) no ano "
|
||||
+ eval.getReferenceYear());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<CareerTimelineDTO> getTimeline(UUID agentId) {
|
||||
// Ordenar por data de eficácia ascendente (mais antigo primeiro) para timeline
|
||||
// cronológica
|
||||
return careerEventRepository.findByAgentIdOrderByEffectiveDateAsc(agentId).stream()
|
||||
.map(e -> CareerTimelineDTO.builder().date(e.getEffectiveDate()).eventType(e.getEventType().name())
|
||||
.eventTypeName(translateEventType(e.getEventType()))
|
||||
.reason(e.getReason())
|
||||
.documentRef(e.getDocumentRef())
|
||||
.totalBaseAmount(e.getTotalBaseAmount())
|
||||
.cargoAmount(e.getCargoAmount())
|
||||
.exercicioAmount(e.getExercicioAmount())
|
||||
.changeSummary(buildChangeSummary(e))
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<AgentStatusHistoryDTO> getStatusHistory(UUID agentId) {
|
||||
return statusHistoryRepository.findByAgentIdOrderByChangedAtDesc(agentId).stream()
|
||||
.map(h -> AgentStatusHistoryDTO.builder()
|
||||
.id(h.getId())
|
||||
.previousStatus(h.getPreviousStatus())
|
||||
.newStatus(h.getNewStatus())
|
||||
.previousFunctionalSituation(h.getPreviousFunctionalSituation())
|
||||
.newFunctionalSituation(h.getNewFunctionalSituation())
|
||||
.eventType(h.getEventType())
|
||||
.reason(h.getReason())
|
||||
.changeLog(h.getChangeLog())
|
||||
.changedAt(h.getChangedAt())
|
||||
.changedBy(h.getChangedBy())
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private String translateEventType(CareerEventType type) {
|
||||
return switch (type) {
|
||||
case ADMISSAO -> "Admissão";
|
||||
case NOMEACAO_PROVISORIA -> "Nomeação Provisória";
|
||||
case NOMEACAO_DEFINITIVA -> "Nomeação Definitiva";
|
||||
case PROMOCAO -> "Promoção";
|
||||
case PROGRESSAO -> "Progressão";
|
||||
case SUBSTITUICAO -> "Substituição";
|
||||
case TRANSFERENCIA -> "Transferência";
|
||||
case RECLASSIFICACAO -> "Reclassificação";
|
||||
case COMISSAO_SERVICO -> "Comissão de Serviço";
|
||||
case TERMINATION -> "Cessação de Funções";
|
||||
default -> type.name();
|
||||
};
|
||||
}
|
||||
|
||||
private String buildChangeSummary(CareerEvent e) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
// Verificar mudanças na estrutura salarial
|
||||
if (e.getNewCategory() != null && !e.getNewCategory().equals(e.getPreviousCategory())) {
|
||||
sb.append("Mudança de Categoria. ");
|
||||
}
|
||||
if (e.getNewGrade() != null && !e.getNewGrade().equals(e.getPreviousGrade())) {
|
||||
sb.append("Mudança de Grau/Escalão. ");
|
||||
}
|
||||
if (e.getNewStep() != null && !e.getNewStep().equals(e.getPreviousStep())) {
|
||||
sb.append("Mudança de Nível/Step. ");
|
||||
}
|
||||
|
||||
// Verificar mudanças organizacionais
|
||||
if (e.getNewOrgUnit() != null && !e.getNewOrgUnit().equals(e.getPreviousOrgUnit())) {
|
||||
sb.append("Transferência de Unidade Orgânica. ");
|
||||
}
|
||||
if (e.getNewPosition() != null && !e.getNewPosition().equals(e.getPreviousPosition())) {
|
||||
sb.append("Mudança de Cargo/Posição. ");
|
||||
}
|
||||
|
||||
// Se não houver mudanças específicas, retornar mensagem genérica baseada no
|
||||
// tipo de evento
|
||||
if (sb.length() == 0) {
|
||||
sb.append("Evento registrado conforme documentação legal.");
|
||||
}
|
||||
|
||||
return sb.toString().trim();
|
||||
}
|
||||
|
||||
private AgentDTO toDTO(Agent agent) {
|
||||
// Otimização: Os contratos e contas bancárias já devem estar no cache de
|
||||
// primeiro nível
|
||||
// devido à busca em lote feita no findAllWithFilters.
|
||||
|
||||
// Buscar contrato ativo de forma eficiente
|
||||
AgentContractDTO activeContractDTO = agent.getContracts().stream()
|
||||
.filter(c -> Boolean.TRUE.equals(c.getIsActive()))
|
||||
.findFirst()
|
||||
.map(this::contractToDTO)
|
||||
.orElse(null);
|
||||
|
||||
// Buscar conta bancária principal de forma eficiente
|
||||
AgentBankAccountDTO primaryBankAccountDTO = agent.getBankAccounts().stream()
|
||||
.filter(acc -> Boolean.TRUE.equals(acc.getIsPrimary()))
|
||||
.findFirst()
|
||||
.map(this::bankAccountToDTO)
|
||||
.orElse(null);
|
||||
|
||||
// Preencher orgUnit e position do contrato ativo se existir, caso contrário
|
||||
// usar do agente
|
||||
UUID orgUnit = activeContractDTO != null && activeContractDTO.getOrgUnit() != null
|
||||
? activeContractDTO.getOrgUnit()
|
||||
: agent.getOrgUnit();
|
||||
UUID position = activeContractDTO != null && activeContractDTO.getPosition() != null
|
||||
? activeContractDTO.getPosition()
|
||||
: agent.getPosition();
|
||||
|
||||
// Preencher salaryCategory, salaryGrade e salaryStep do contrato ativo se
|
||||
// existir
|
||||
UUID salaryCategory = activeContractDTO != null && activeContractDTO.getSalaryCategory() != null
|
||||
? activeContractDTO.getSalaryCategory()
|
||||
: agent.getSalaryCategory();
|
||||
UUID salaryGrade = activeContractDTO != null && activeContractDTO.getSalaryGrade() != null
|
||||
? activeContractDTO.getSalaryGrade()
|
||||
: agent.getSalaryGrade();
|
||||
UUID salaryStep = activeContractDTO != null && activeContractDTO.getSalaryStep() != null
|
||||
? activeContractDTO.getSalaryStep()
|
||||
: agent.getSalaryStep();
|
||||
|
||||
return AgentDTO.builder()
|
||||
.id(agent.getId())
|
||||
.matricula(agent.getMatricula())
|
||||
.nif(agent.getNif())
|
||||
.biNumber(agent.getBiNumber())
|
||||
.fullName(agent.getFullName())
|
||||
.birthDate(agent.getBirthDate())
|
||||
.hireDate(agent.getHireDate())
|
||||
.posseDate(agent.getPosseDate())
|
||||
.terminationDate(agent.getTerminationDate())
|
||||
.appointmentType(agent.getAppointmentType())
|
||||
.functionalSituation(agent.getFunctionalSituation())
|
||||
.status(agent.getStatus())
|
||||
.eligibleDependentsCount(agent.getEligibleDependentsCount())
|
||||
.literaryQualification(agent.getLiteraryQualification())
|
||||
.salaryCategory(salaryCategory)
|
||||
.salaryGrade(salaryGrade)
|
||||
.salaryStep(salaryStep)
|
||||
.orgUnit(orgUnit)
|
||||
.position(position)
|
||||
.nationality(agent.getNationality())
|
||||
.phone(agent.getPhone())
|
||||
.email(agent.getEmail())
|
||||
.address(agent.getAddress())
|
||||
.activeContract(activeContractDTO)
|
||||
.primaryBankAccount(primaryBankAccountDTO)
|
||||
.build();
|
||||
}
|
||||
|
||||
private AgentContractDTO contractToDTO(AgentContract contract) {
|
||||
return AgentContractDTO.builder()
|
||||
.id(contract.getId())
|
||||
.contractType(contract.getContractType())
|
||||
.startDate(contract.getStartDate())
|
||||
.endDate(contract.getEndDate())
|
||||
.weeklyHours(contract.getWeeklyHours())
|
||||
.baseSalaryRef(contract.getBaseSalaryRef())
|
||||
.orgUnit(contract.getOrgUnit())
|
||||
.position(contract.getPosition())
|
||||
.salaryCategory(contract.getSalaryCategory())
|
||||
.salaryGrade(contract.getSalaryGrade())
|
||||
.salaryStep(contract.getSalaryStep())
|
||||
.legalActReference(contract.getLegalActReference())
|
||||
.isActive(contract.getIsActive())
|
||||
.build();
|
||||
}
|
||||
|
||||
private AgentBankAccountDTO bankAccountToDTO(AgentBankAccount account) {
|
||||
return AgentBankAccountDTO.builder()
|
||||
.id(account.getId())
|
||||
.bank(account.getBank())
|
||||
.branchCode(account.getBranchCode())
|
||||
.accountNumber(account.getAccountNumber())
|
||||
.isPrimary(account.getIsPrimary())
|
||||
.build();
|
||||
}
|
||||
|
||||
private void validateLiteraryQualification(UUID salaryCategory, String literaryQualification) {
|
||||
if (salaryCategory != null && (literaryQualification == null || literaryQualification.trim().isEmpty())) {
|
||||
throw new IllegalArgumentException(
|
||||
"Habilitação Literária é obrigatória quando há Categoria Salarial definida.");
|
||||
}
|
||||
}
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
package br.gov.sigefp.rh.service;
|
||||
|
||||
import br.gov.sigefp.common.exception.BusinessException;
|
||||
import br.gov.sigefp.rh.domain.*;
|
||||
import br.gov.sigefp.rh.repository.AttendanceRecordRepository;
|
||||
import br.gov.sigefp.rh.repository.MonthlyAttendanceSheetRepository;
|
||||
import br.gov.sigefp.rh.repository.AgentRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.poi.ss.usermodel.*;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional
|
||||
@Slf4j
|
||||
public class AttendanceService {
|
||||
|
||||
private final MonthlyAttendanceSheetRepository sheetRepository;
|
||||
private final AttendanceRecordRepository recordRepository;
|
||||
private final AgentRepository agentRepository;
|
||||
|
||||
public MonthlyAttendanceSheet getOrCreateSheet(UUID orgUnitId, int month, int year) {
|
||||
return sheetRepository.findByOrgUnitIdAndMonthAndYear(orgUnitId, month, year)
|
||||
.orElseGet(() -> {
|
||||
MonthlyAttendanceSheet sheet = MonthlyAttendanceSheet.builder()
|
||||
.orgUnitId(orgUnitId)
|
||||
.month(month)
|
||||
.year(year)
|
||||
.status(AttendanceSheetStatus.DRAFT)
|
||||
.build();
|
||||
return sheetRepository.save(sheet);
|
||||
});
|
||||
}
|
||||
|
||||
public void approveSheet(UUID sheetId, String approverName) {
|
||||
MonthlyAttendanceSheet sheet = sheetRepository.findById(sheetId)
|
||||
.orElseThrow(() -> new BusinessException("Folha não encontrada", "SHEET_NOT_FOUND",
|
||||
org.springframework.http.HttpStatus.NOT_FOUND));
|
||||
|
||||
sheet.setStatus(AttendanceSheetStatus.APPROVED);
|
||||
sheet.setApprovedBy(approverName);
|
||||
sheetRepository.save(sheet);
|
||||
log.info("Folha de Ponto {} aprovada por {}", sheetId, approverName);
|
||||
}
|
||||
|
||||
public void reopenSheet(UUID sheetId) {
|
||||
MonthlyAttendanceSheet sheet = sheetRepository.findById(sheetId)
|
||||
.orElseThrow(() -> new BusinessException("Folha não encontrada", "SHEET_NOT_FOUND",
|
||||
org.springframework.http.HttpStatus.NOT_FOUND));
|
||||
|
||||
if (AttendanceSheetStatus.CLOSED.equals(sheet.getStatus())) {
|
||||
throw new BusinessException("Folha já processada pela contabilidade não pode ser reaberta.", "SHEET_CLOSED",
|
||||
org.springframework.http.HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
sheet.setStatus(AttendanceSheetStatus.DRAFT);
|
||||
sheetRepository.save(sheet);
|
||||
}
|
||||
|
||||
public void importExcel(UUID sheetId, MultipartFile file) {
|
||||
MonthlyAttendanceSheet sheet = sheetRepository.findById(sheetId)
|
||||
.orElseThrow(() -> new BusinessException("Folha não encontrada", "SHEET_NOT_FOUND",
|
||||
org.springframework.http.HttpStatus.NOT_FOUND));
|
||||
|
||||
if (!AttendanceSheetStatus.DRAFT.equals(sheet.getStatus())) {
|
||||
throw new BusinessException("A folha deve estar em Rascunho para importação.", "INVALID_STATUS",
|
||||
org.springframework.http.HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
try (Workbook workbook = new XSSFWorkbook(file.getInputStream())) {
|
||||
Sheet excelSheet = workbook.getSheetAt(0);
|
||||
|
||||
// Clear existing records to avoid duplication or stale data
|
||||
recordRepository.deleteBySheetId(sheetId);
|
||||
|
||||
for (Row row : excelSheet) {
|
||||
if (row.getRowNum() < 1)
|
||||
continue; // Skip Header
|
||||
|
||||
Cell matriculaCell = row.getCell(0);
|
||||
if (matriculaCell == null)
|
||||
continue;
|
||||
|
||||
String matricula = getCellValueAsString(matriculaCell);
|
||||
Agent agent = agentRepository.findByMatricula(matricula)
|
||||
.orElse(null);
|
||||
|
||||
if (agent == null)
|
||||
continue; // Skip unknown agents
|
||||
|
||||
// Iterate Days (Cols 1 to 31)
|
||||
for (int day = 1; day <= 31; day++) {
|
||||
Cell dayCell = row.getCell(day);
|
||||
String code = getCellValueAsString(dayCell);
|
||||
|
||||
if (code != null && !code.isEmpty()) {
|
||||
AttendanceType type = parseType(code);
|
||||
if (type != null) {
|
||||
try {
|
||||
LocalDate date = LocalDate.of(sheet.getYear(), sheet.getMonth(), day);
|
||||
AttendanceRecord record = AttendanceRecord.builder()
|
||||
.sheet(sheet)
|
||||
.agent(agent)
|
||||
.date(date)
|
||||
.type(type)
|
||||
.observation("Importado via Excel")
|
||||
.build();
|
||||
recordRepository.save(record);
|
||||
} catch (Exception e) {
|
||||
// Invalid date (e.g., Feb 30), ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new BusinessException("Erro ao ler arquivo Excel", "IO_ERROR",
|
||||
org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
private String getCellValueAsString(Cell cell) {
|
||||
if (cell == null)
|
||||
return null;
|
||||
return switch (cell.getCellType()) {
|
||||
case STRING -> cell.getStringCellValue();
|
||||
case NUMERIC -> String.valueOf((int) cell.getNumericCellValue());
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
private AttendanceType parseType(String code) {
|
||||
return switch (code.toUpperCase().trim()) {
|
||||
case "F" -> AttendanceType.ABSENCE_UNJUSTIFIED;
|
||||
case "J" -> AttendanceType.ABSENCE_JUSTIFIED;
|
||||
case "M" -> AttendanceType.SICK_LEAVE;
|
||||
case "V" -> AttendanceType.VACATION;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package br.gov.sigefp.rh.service;
|
||||
|
||||
import br.gov.sigefp.rh.domain.*;
|
||||
import br.gov.sigefp.rh.repository.CareerEventRepository;
|
||||
import br.gov.sigefp.rh.repository.SalaryGridRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Serviço centralizado para registro de eventos estruturados na vida laboral
|
||||
* (carreira).
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional
|
||||
public class CareerEventService {
|
||||
|
||||
private final CareerEventRepository careerEventRepository;
|
||||
private final SalaryGridRepository salaryGridRepository;
|
||||
|
||||
/**
|
||||
* Registra um evento de carreira com snapshots e cálculos financeiros.
|
||||
*/
|
||||
public void recordEvent(Agent agent, CareerEventType type, String reason, String docRef,
|
||||
LocalDate effectiveDate, LocalDate publicationDate,
|
||||
UUID newCategory, UUID newGrade, UUID newStep, UUID newOrg, UUID newPos,
|
||||
UUID prevCategory, UUID prevGrade, UUID prevStep, UUID prevOrg, UUID prevPos) {
|
||||
|
||||
CareerEvent event = CareerEvent.builder()
|
||||
.agent(agent)
|
||||
.eventType(type)
|
||||
.effectiveDate(effectiveDate != null ? effectiveDate : LocalDate.now())
|
||||
.publicationDate(publicationDate)
|
||||
.documentRef(docRef)
|
||||
.reason(reason)
|
||||
// Snapshots atuais
|
||||
.previousCategory(prevCategory)
|
||||
.newCategory(newCategory != null ? newCategory : agent.getSalaryCategory())
|
||||
.previousGrade(prevGrade)
|
||||
.newGrade(newGrade != null ? newGrade : agent.getSalaryGrade())
|
||||
.previousStep(prevStep)
|
||||
.newStep(newStep != null ? newStep : agent.getSalaryStep())
|
||||
// Snapshots organizacionais
|
||||
.previousOrgUnit(prevOrg)
|
||||
.newOrgUnit(newOrg != null ? newOrg : agent.getOrgUnit())
|
||||
.previousPosition(prevPos)
|
||||
.newPosition(newPos != null ? newPos : agent.getPosition())
|
||||
.createdByUser("system")
|
||||
.build();
|
||||
|
||||
// Cálculo financeiro retroativo ou atual
|
||||
UUID finalStepId = event.getNewStep();
|
||||
if (finalStepId != null) {
|
||||
SalaryGrid grid = salaryGridRepository.findByStepIdAndDate(finalStepId, event.getEffectiveDate());
|
||||
if (grid != null) {
|
||||
event.calculateFinancialSplit(grid.getBaseAmount());
|
||||
}
|
||||
}
|
||||
|
||||
careerEventRepository.save(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Versão simplificada para eventos que não alteram carreira (ex: apenas mudança
|
||||
* de referência legal ou situação funcional).
|
||||
*/
|
||||
public void recordSimpleEvent(Agent agent, CareerEventType type, String reason, String docRef,
|
||||
LocalDate effectiveDate) {
|
||||
recordEvent(agent, type, reason, docRef, effectiveDate, null,
|
||||
null, null, null, null, null,
|
||||
agent.getSalaryCategory(), agent.getSalaryGrade(), agent.getSalaryStep(),
|
||||
agent.getOrgUnit(), agent.getPosition());
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package br.gov.sigefp.rh.service;
|
||||
|
||||
import br.gov.sigefp.rh.api.dto.CareerRegimeDTO;
|
||||
import br.gov.sigefp.rh.domain.CareerRegime;
|
||||
import br.gov.sigefp.rh.repository.CareerRegimeRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class CareerRegimeService {
|
||||
|
||||
private final CareerRegimeRepository repository;
|
||||
|
||||
public List<CareerRegimeDTO> findAll() {
|
||||
return repository.findAll().stream()
|
||||
.map(this::toDTO)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public CareerRegimeDTO create(CareerRegimeDTO dto) {
|
||||
CareerRegime entity = CareerRegime.builder()
|
||||
.code(dto.getCode())
|
||||
.name(dto.getName())
|
||||
.description(dto.getDescription())
|
||||
.build();
|
||||
return toDTO(repository.save(entity));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public CareerRegimeDTO update(java.util.UUID id, CareerRegimeDTO dto) {
|
||||
CareerRegime entity = repository.findById(id)
|
||||
.orElseThrow(() -> new RuntimeException("Regime não encontrado"));
|
||||
entity.setCode(dto.getCode());
|
||||
entity.setName(dto.getName());
|
||||
entity.setDescription(dto.getDescription());
|
||||
return toDTO(repository.save(entity));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void delete(java.util.UUID id) {
|
||||
repository.deleteById(id);
|
||||
}
|
||||
|
||||
private CareerRegimeDTO toDTO(CareerRegime entity) {
|
||||
return CareerRegimeDTO.builder()
|
||||
.id(entity.getId())
|
||||
.code(entity.getCode())
|
||||
.name(entity.getName())
|
||||
.description(entity.getDescription())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+1144
File diff suppressed because it is too large
Load Diff
+104
@@ -0,0 +1,104 @@
|
||||
package br.gov.sigefp.rh.service;
|
||||
|
||||
import br.gov.sigefp.common.exception.BusinessException;
|
||||
import br.gov.sigefp.rh.api.dto.PerformanceEvaluationDTO;
|
||||
import br.gov.sigefp.rh.domain.Agent;
|
||||
import br.gov.sigefp.rh.domain.CareerEventType;
|
||||
import br.gov.sigefp.rh.domain.PerformanceEvaluation;
|
||||
import br.gov.sigefp.rh.repository.PerformanceEvaluationRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Serviço para gestão de avaliações de desempenho e automação de carreira.
|
||||
* Implementa regras do Decreto nº 12-A/94.
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@Transactional
|
||||
public class PerformanceEvaluationService {
|
||||
|
||||
private final PerformanceEvaluationRepository evaluationRepository;
|
||||
private final CareerEventService careerEventService;
|
||||
|
||||
/**
|
||||
* Finaliza uma avaliação e verifica elegibilidade para promoção.
|
||||
*/
|
||||
public void finalizeEvaluation(UUID evaluationId) {
|
||||
PerformanceEvaluation evaluation = evaluationRepository.findById(evaluationId)
|
||||
.orElseThrow(() -> new BusinessException("Avaliação não encontrada", "NOT_FOUND",
|
||||
org.springframework.http.HttpStatus.NOT_FOUND));
|
||||
|
||||
if (!"DRAFT".equals(evaluation.getStatus())) {
|
||||
throw new BusinessException("Apenas avaliações em DRAFT podem ser finalizadas", "INVALID_STATUS",
|
||||
org.springframework.http.HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
evaluation.setStatus("FINAL");
|
||||
evaluation.updateMentionFromScore();
|
||||
evaluationRepository.save(evaluation);
|
||||
|
||||
// Verifica requisitos para promoção (3 anos de BOM ou MUITO_BOM)
|
||||
checkPromotionEligibility(evaluation.getAgent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Busca todas as avaliações com paginação.
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public Page<PerformanceEvaluationDTO> findAll(Pageable pageable) {
|
||||
return evaluationRepository.findAll(pageable)
|
||||
.map(this::toDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converte entidade para DTO.
|
||||
*/
|
||||
private PerformanceEvaluationDTO toDTO(PerformanceEvaluation evaluation) {
|
||||
Agent agent = evaluation.getAgent();
|
||||
return PerformanceEvaluationDTO.builder()
|
||||
.id(evaluation.getId())
|
||||
.agentId(agent != null ? agent.getId() : null)
|
||||
.agentName(agent != null ? agent.getFullName() : null)
|
||||
.agentMatricula(agent != null ? agent.getMatricula() : null)
|
||||
.referenceYear(evaluation.getReferenceYear())
|
||||
.score(evaluation.getScore())
|
||||
.status(evaluation.getStatus())
|
||||
.mention(evaluation.getMention())
|
||||
.observations(evaluation.getObservations())
|
||||
.evaluationDate(evaluation.getEvaluationDate())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica se o agente atingiu os requisitos para promoção automática.
|
||||
*/
|
||||
private void checkPromotionEligibility(Agent agent) {
|
||||
// Busca as últimas 3 avaliações
|
||||
List<PerformanceEvaluation> lastEvaluations = evaluationRepository
|
||||
.findByAgentIdOrderByReferenceYearDesc(agent.getId());
|
||||
|
||||
if (lastEvaluations.size() < 3) {
|
||||
return; // Requer pelo menos 3 anos
|
||||
}
|
||||
|
||||
boolean eligible = lastEvaluations.stream()
|
||||
.limit(3)
|
||||
.allMatch(e -> "BOM".equals(e.getMention()) || "MUITO_BOM".equals(e.getMention()));
|
||||
|
||||
if (eligible) {
|
||||
log.info("Agente {} elegível para promoção automática conforme Decreto 12-A/94", agent.getMatricula());
|
||||
// Aqui poderíamos disparar um evento ou criar um CareerEvent sugerido
|
||||
// Por enquanto, apenas registramos no log ou enviamos notificação
|
||||
}
|
||||
}
|
||||
}
|
||||
+257
@@ -0,0 +1,257 @@
|
||||
package br.gov.sigefp.rh.service;
|
||||
|
||||
import br.gov.sigefp.rh.api.dto.*;
|
||||
import br.gov.sigefp.rh.domain.SalaryCategory;
|
||||
import br.gov.sigefp.rh.domain.SalaryGrade;
|
||||
import br.gov.sigefp.rh.domain.SalaryGrid;
|
||||
import br.gov.sigefp.rh.domain.SalaryStep;
|
||||
import br.gov.sigefp.rh.repository.AgentRepository;
|
||||
import br.gov.sigefp.rh.repository.SalaryCategoryRepository;
|
||||
import br.gov.sigefp.rh.repository.SalaryGradeRepository;
|
||||
import br.gov.sigefp.rh.repository.SalaryGridRepository;
|
||||
import br.gov.sigefp.rh.repository.SalaryStepRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional
|
||||
public class SalaryStructureService {
|
||||
|
||||
private final SalaryCategoryRepository categoryRepository;
|
||||
private final SalaryGradeRepository gradeRepository;
|
||||
private final SalaryStepRepository stepRepository;
|
||||
private final SalaryGridRepository gridRepository;
|
||||
private final AgentRepository agentRepository;
|
||||
private final br.gov.sigefp.rh.repository.CareerRegimeRepository careerRegimeRepository;
|
||||
|
||||
public SalaryStructureFullDTO getFullStructure() {
|
||||
List<SalaryCategory> categories = categoryRepository.findAll();
|
||||
LocalDate today = LocalDate.now();
|
||||
|
||||
List<SalaryStructureFullDTO.CategoryGroupDTO> categoryDTOs = categories.stream().map(cat -> {
|
||||
List<SalaryGrade> grades = gradeRepository.findByCategoryId(cat.getId());
|
||||
|
||||
List<SalaryStructureFullDTO.GradeRowDTO> gradeDTOs = grades.stream().map(grade -> {
|
||||
List<SalaryStructureFullDTO.StepDetailDTO> stepDTOs = grade.getSteps().stream()
|
||||
.sorted((a, b) -> a.getStepNumber() - b.getStepNumber())
|
||||
.map(step -> {
|
||||
// Find the grid entry valid for today
|
||||
SalaryGrid activeGrid = step.getGrids().stream()
|
||||
.filter(g -> !today.isBefore(g.getValidFrom()) &&
|
||||
(g.getValidTo() == null || !today.isAfter(g.getValidTo())))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
return SalaryStructureFullDTO.StepDetailDTO.builder()
|
||||
.id(step.getId())
|
||||
.stepNumber(step.getStepNumber())
|
||||
.currentValue(activeGrid != null ? activeGrid.getBaseAmount().doubleValue() : null)
|
||||
.subsidyAmount(activeGrid != null && activeGrid.getSubsidyAmount() != null
|
||||
? activeGrid.getSubsidyAmount().doubleValue()
|
||||
: null)
|
||||
.grossAmount(activeGrid != null && activeGrid.getGrossAmount() != null
|
||||
? activeGrid.getGrossAmount().doubleValue()
|
||||
: null)
|
||||
.build();
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
return SalaryStructureFullDTO.GradeRowDTO.builder()
|
||||
.id(grade.getId())
|
||||
.code(grade.getCode())
|
||||
.name(grade.getName())
|
||||
.steps(stepDTOs)
|
||||
.build();
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
return SalaryStructureFullDTO.CategoryGroupDTO.builder()
|
||||
.id(cat.getId())
|
||||
.code(cat.getCode())
|
||||
.name(cat.getName())
|
||||
.regimeName(cat.getRegime() != null ? cat.getRegime().getName() : null)
|
||||
.grades(gradeDTOs)
|
||||
.build();
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
return SalaryStructureFullDTO.builder().categories(categoryDTOs).build();
|
||||
}
|
||||
|
||||
// --- Category methods ---
|
||||
public List<SalaryCategoryDTO> getAllCategories() {
|
||||
return categoryRepository.findAll().stream()
|
||||
.map(this::toCategoryDTO)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public SalaryCategoryDTO createCategory(SalaryCategoryDTO dto) {
|
||||
if (categoryRepository.existsByCode(dto.getCode())) {
|
||||
throw new IllegalArgumentException("Categoria com código " + dto.getCode() + " já existe");
|
||||
}
|
||||
SalaryCategory entity = new SalaryCategory();
|
||||
entity.setCode(dto.getCode());
|
||||
entity.setName(dto.getName());
|
||||
|
||||
if (dto.getRegimeId() != null) {
|
||||
br.gov.sigefp.rh.domain.CareerRegime regime = careerRegimeRepository.findById(dto.getRegimeId())
|
||||
.orElseThrow(() -> new IllegalArgumentException("Regime não encontrado"));
|
||||
entity.setRegime(regime);
|
||||
}
|
||||
|
||||
return toCategoryDTO(categoryRepository.save(entity));
|
||||
}
|
||||
|
||||
public SalaryCategoryDTO updateCategory(UUID id, SalaryCategoryDTO dto) {
|
||||
SalaryCategory category = categoryRepository.findById(id)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Categoria não encontrada"));
|
||||
|
||||
if (!category.getCode().equals(dto.getCode()) && categoryRepository.existsByCode(dto.getCode())) {
|
||||
throw new IllegalArgumentException("Categoria com código " + dto.getCode() + " já existe");
|
||||
}
|
||||
|
||||
category.setCode(dto.getCode());
|
||||
category.setName(dto.getName());
|
||||
|
||||
if (dto.getRegimeId() != null) {
|
||||
br.gov.sigefp.rh.domain.CareerRegime regime = careerRegimeRepository.findById(dto.getRegimeId())
|
||||
.orElseThrow(() -> new IllegalArgumentException("Regime não encontrado"));
|
||||
category.setRegime(regime);
|
||||
} else {
|
||||
category.setRegime(null);
|
||||
}
|
||||
|
||||
return toCategoryDTO(categoryRepository.save(category));
|
||||
}
|
||||
|
||||
public void deleteCategory(UUID id) {
|
||||
if (agentRepository.existsBySalaryCategory(id)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Não é possível excluir a categoria pois existem agentes vinculados a ela");
|
||||
}
|
||||
categoryRepository.deleteById(id);
|
||||
}
|
||||
|
||||
private SalaryCategoryDTO toCategoryDTO(SalaryCategory category) {
|
||||
return SalaryCategoryDTO.builder()
|
||||
.id(category.getId())
|
||||
.code(category.getCode())
|
||||
.name(category.getName())
|
||||
.regimeId(category.getRegime() != null ? category.getRegime().getId() : null)
|
||||
.regimeName(category.getRegime() != null ? category.getRegime().getName() : null)
|
||||
.build();
|
||||
}
|
||||
|
||||
// --- Grade methods ---
|
||||
public List<SalaryGradeDTO> getGradesByCategory(UUID categoryId) {
|
||||
return gradeRepository.findByCategoryId(categoryId).stream()
|
||||
.map(this::toGradeDTO)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public SalaryGradeDTO createGrade(SalaryGradeDTO dto) {
|
||||
SalaryCategory category = categoryRepository.findById(dto.getCategoryId())
|
||||
.orElseThrow(() -> new IllegalArgumentException("Categoria não encontrada"));
|
||||
|
||||
SalaryGrade entity = new SalaryGrade();
|
||||
entity.setCategory(category);
|
||||
entity.setCode(dto.getCode());
|
||||
entity.setName(dto.getName());
|
||||
return toGradeDTO(gradeRepository.save(entity));
|
||||
}
|
||||
|
||||
public SalaryGradeDTO updateGrade(UUID id, SalaryGradeDTO dto) {
|
||||
SalaryGrade grade = gradeRepository.findById(id)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Grau não encontrado"));
|
||||
|
||||
grade.setCode(dto.getCode());
|
||||
grade.setName(dto.getName());
|
||||
return toGradeDTO(gradeRepository.save(grade));
|
||||
}
|
||||
|
||||
public void deleteGrade(UUID id) {
|
||||
if (agentRepository.existsBySalaryGrade(id)) {
|
||||
throw new IllegalArgumentException("Não é possível excluir o grau pois existem agentes vinculados a ele");
|
||||
}
|
||||
gradeRepository.deleteById(id);
|
||||
}
|
||||
|
||||
private SalaryGradeDTO toGradeDTO(SalaryGrade grade) {
|
||||
return SalaryGradeDTO.builder()
|
||||
.id(grade.getId())
|
||||
.categoryId(grade.getCategory().getId())
|
||||
.code(grade.getCode())
|
||||
.name(grade.getName())
|
||||
.build();
|
||||
}
|
||||
|
||||
// --- Step methods ---
|
||||
public List<SalaryStepDTO> getStepsByGrade(UUID gradeId) {
|
||||
return stepRepository.findByGradeIdOrderByStepNumber(gradeId).stream()
|
||||
.map(this::toStepDTO)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public SalaryStepDTO createStep(SalaryStepDTO dto) {
|
||||
SalaryGrade grade = gradeRepository.findById(dto.getGradeId())
|
||||
.orElseThrow(() -> new IllegalArgumentException("Grade não encontrada"));
|
||||
|
||||
SalaryStep entity = new SalaryStep();
|
||||
entity.setGrade(grade);
|
||||
entity.setStepNumber(dto.getStepNumber());
|
||||
return toStepDTO(stepRepository.save(entity));
|
||||
}
|
||||
|
||||
public void deleteStep(UUID id) {
|
||||
if (agentRepository.existsBySalaryStep(id)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Não é possível excluir o escalão pois existem agentes vinculados a ele");
|
||||
}
|
||||
stepRepository.deleteById(id);
|
||||
}
|
||||
|
||||
private SalaryStepDTO toStepDTO(SalaryStep step) {
|
||||
return SalaryStepDTO.builder()
|
||||
.id(step.getId())
|
||||
.gradeId(step.getGrade().getId())
|
||||
.stepNumber(step.getStepNumber())
|
||||
.build();
|
||||
}
|
||||
|
||||
// --- Grid methods ---
|
||||
public List<SalaryGridDTO> getGridByStep(UUID stepId) {
|
||||
return gridRepository.findByStepId(stepId).stream()
|
||||
.map(this::toGridDTO)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public SalaryGridDTO createGridEntry(SalaryGridDTO dto) {
|
||||
SalaryStep step = stepRepository.findById(dto.getStepId())
|
||||
.orElseThrow(() -> new IllegalArgumentException("Step não encontrado"));
|
||||
|
||||
SalaryGrid entity = new SalaryGrid();
|
||||
entity.setStep(step);
|
||||
entity.setValidFrom(dto.getValidFrom());
|
||||
entity.setValidTo(dto.getValidTo());
|
||||
entity.setBaseAmount(dto.getBaseAmount());
|
||||
|
||||
return toGridDTO(gridRepository.save(entity));
|
||||
}
|
||||
|
||||
private SalaryGridDTO toGridDTO(SalaryGrid grid) {
|
||||
return SalaryGridDTO.builder()
|
||||
.id(grid.getId())
|
||||
.stepId(grid.getStep().getId())
|
||||
.validFrom(grid.getValidFrom())
|
||||
.validTo(grid.getValidTo())
|
||||
.baseAmount(grid.getBaseAmount())
|
||||
.subsidyAmount(grid.getSubsidyAmount())
|
||||
.grossAmount(grid.getGrossAmount())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
package br.gov.sigefp.rh.service;
|
||||
|
||||
import br.gov.sigefp.rh.domain.GlobalDeductionRule;
|
||||
import br.gov.sigefp.rh.domain.TaxBracket;
|
||||
import br.gov.sigefp.rh.repository.GlobalDeductionRuleRepository;
|
||||
import br.gov.sigefp.rh.repository.TaxBracketRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import br.gov.sigefp.rh.repository.DeductionTypeRepository;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* Serviço para gestão de regras tributárias e escalões.
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional
|
||||
public class TaxService {
|
||||
|
||||
private final GlobalDeductionRuleRepository globalDeductionRuleRepository;
|
||||
private final TaxBracketRepository taxBracketRepository;
|
||||
private final DeductionTypeRepository deductionTypeRepository;
|
||||
|
||||
// --- Deduction Types ---
|
||||
|
||||
@org.springframework.security.access.prepost.PreAuthorize("hasRole('HR_CONFIG')")
|
||||
public br.gov.sigefp.rh.domain.DeductionType saveDeductionType(br.gov.sigefp.rh.domain.DeductionType type) {
|
||||
if (type.getId() == null) {
|
||||
// New: Ensure Code Unique (Repository should handle or we check)
|
||||
}
|
||||
return deductionTypeRepository.save(type);
|
||||
}
|
||||
|
||||
public void toggleDeductionTypeStatus(UUID id) {
|
||||
br.gov.sigefp.rh.domain.DeductionType type = deductionTypeRepository.findById(id)
|
||||
.orElseThrow(() -> new br.gov.sigefp.common.exception.ResourceNotFoundException("Tipo não encontrado"));
|
||||
type.setActive(!Boolean.TRUE.equals(type.getActive()));
|
||||
deductionTypeRepository.save(type);
|
||||
}
|
||||
|
||||
public List<GlobalDeductionRule> findAllRules() {
|
||||
return globalDeductionRuleRepository.findAll();
|
||||
}
|
||||
|
||||
@org.springframework.security.access.prepost.PreAuthorize("hasRole('HR_CONFIG')")
|
||||
public GlobalDeductionRule saveRule(GlobalDeductionRule rule) {
|
||||
// P13: Validação de Sobreposição de Regras Globais
|
||||
if (rule.getDeductionType() == null) {
|
||||
throw new IllegalArgumentException("O tipo de desconto é obrigatório.");
|
||||
}
|
||||
|
||||
// Data fim padrão se nula (opcional, mas ajuda na consulta)
|
||||
LocalDate start = rule.getValidFrom();
|
||||
LocalDate end = rule.getValidTo() != null ? rule.getValidTo() : LocalDate.of(2099, 12, 31);
|
||||
|
||||
List<GlobalDeductionRule> overlaps = globalDeductionRuleRepository.findOverlappingRules(
|
||||
rule.getDeductionType().getId(), start, end);
|
||||
|
||||
for (GlobalDeductionRule existing : overlaps) {
|
||||
if (!existing.getId().equals(rule.getId())) {
|
||||
throw new br.gov.sigefp.common.exception.BusinessException(
|
||||
"Sobreposição de datas detectada com a regra ID: " + existing.getId(),
|
||||
"RULE_OVERLAP", org.springframework.http.HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
return globalDeductionRuleRepository.save(rule);
|
||||
}
|
||||
|
||||
public void deleteRule(UUID id) {
|
||||
globalDeductionRuleRepository.deleteById(id);
|
||||
}
|
||||
|
||||
public List<TaxBracket> findAllBrackets() {
|
||||
return taxBracketRepository.findAll();
|
||||
}
|
||||
|
||||
@org.springframework.security.access.prepost.PreAuthorize("hasRole('HR_CONFIG')")
|
||||
public TaxBracket saveBracket(TaxBracket bracket) {
|
||||
// P14: Validação de Sobreposição de Escalões
|
||||
if (bracket.getDeductionType() == null) {
|
||||
throw new IllegalArgumentException("O tipo de desconto é obrigatório.");
|
||||
}
|
||||
|
||||
LocalDate start = bracket.getValidFrom();
|
||||
LocalDate end = bracket.getValidTo() != null ? bracket.getValidTo() : LocalDate.of(2099, 12, 31);
|
||||
java.math.BigDecimal lower = bracket.getLowerLimit();
|
||||
// Se upper for null, tratar como infinito para validação
|
||||
java.math.BigDecimal upper = bracket.getUpperLimit() != null ? bracket.getUpperLimit()
|
||||
: new java.math.BigDecimal("999999999");
|
||||
|
||||
List<TaxBracket> overlaps = taxBracketRepository.findOverlappingBrackets(
|
||||
bracket.getDeductionType().getId(), start, end, lower, upper);
|
||||
|
||||
for (TaxBracket existing : overlaps) {
|
||||
if (!existing.getId().equals(bracket.getId())) {
|
||||
throw new br.gov.sigefp.common.exception.BusinessException(
|
||||
"Sobreposição de escalões detectada com o ID: " + existing.getId(),
|
||||
"BRACKET_OVERLAP", org.springframework.http.HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
return taxBracketRepository.save(bracket);
|
||||
}
|
||||
|
||||
public void deleteBracket(UUID id) {
|
||||
taxBracketRepository.deleteById(id);
|
||||
}
|
||||
|
||||
public List<br.gov.sigefp.rh.domain.DeductionType> findAllDeductionTypes() {
|
||||
return deductionTypeRepository.findAll();
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package br.gov.sigefp.rh.specification;
|
||||
|
||||
import br.gov.sigefp.rh.domain.Agent;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Specifications para filtrar agentes de forma dinâmica.
|
||||
*/
|
||||
public class AgentSpecifications {
|
||||
|
||||
public static Specification<Agent> hasStatus(String status) {
|
||||
return (root, query, cb) -> status == null ? null : cb.equal(root.get("status"), status);
|
||||
}
|
||||
|
||||
public static Specification<Agent> hasOrgUnit(UUID orgUnitId) {
|
||||
return (root, query, cb) -> {
|
||||
if (orgUnitId == null)
|
||||
return null;
|
||||
System.out.println("DEBUG SPEC: Filtering by orgUnit ID: " + orgUnitId);
|
||||
return cb.equal(root.get("orgUnit"), orgUnitId);
|
||||
};
|
||||
}
|
||||
|
||||
public static Specification<Agent> hasOrgUnitIn(java.util.List<UUID> orgUnitIds) {
|
||||
return (root, query, cb) -> {
|
||||
if (orgUnitIds == null || orgUnitIds.isEmpty())
|
||||
return null;
|
||||
System.out.println("DEBUG SPEC: Filtering by orgUnit LIST: " + orgUnitIds.size() + " units");
|
||||
return root.get("orgUnit").in(orgUnitIds);
|
||||
};
|
||||
}
|
||||
|
||||
public static Specification<Agent> hasPosition(UUID positionId) {
|
||||
return (root, query, cb) -> positionId == null ? null : cb.equal(root.get("position"), positionId);
|
||||
}
|
||||
|
||||
public static Specification<Agent> hasFunctionalSituation(String functionalSituation) {
|
||||
return (root, query, cb) -> functionalSituation == null ? null
|
||||
: cb.equal(root.get("functionalSituation"), functionalSituation);
|
||||
}
|
||||
|
||||
public static Specification<Agent> hasAppointmentType(String appointmentType) {
|
||||
return (root, query, cb) -> appointmentType == null ? null
|
||||
: cb.equal(root.get("appointmentType"), appointmentType);
|
||||
}
|
||||
|
||||
public static Specification<Agent> searchByQuery(String query) {
|
||||
return (root, criteriaQuery, cb) -> {
|
||||
if (query == null || query.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String searchPattern = "%" + query.toLowerCase() + "%";
|
||||
return cb.or(
|
||||
cb.like(cb.lower(root.get("fullName")), searchPattern),
|
||||
cb.like(cb.lower(root.get("matricula")), searchPattern),
|
||||
cb.like(cb.lower(root.get("nif")), searchPattern),
|
||||
cb.like(cb.lower(root.get("biNumber")), searchPattern));
|
||||
};
|
||||
}
|
||||
}
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
package br.gov.sigefp.rh.service;
|
||||
|
||||
import br.gov.sigefp.budget.integration.BudgetIntegrationService;
|
||||
import br.gov.sigefp.rh.domain.*;
|
||||
import br.gov.sigefp.rh.repository.*; // Assume this covers FamilyAllowanceTableRepository if it's in the same package
|
||||
import br.gov.sigefp.common.exception.BusinessException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PayrollServiceTest {
|
||||
|
||||
@Mock
|
||||
private PayrollPeriodRepository payrollPeriodRepository;
|
||||
@Mock
|
||||
private PayrollRunRepository payrollRunRepository;
|
||||
@Mock
|
||||
private PayrollItemRepository payrollItemRepository;
|
||||
@Mock
|
||||
private AgentRepository agentRepository;
|
||||
@Mock
|
||||
private SalaryGridRepository salaryGridRepository;
|
||||
@Mock
|
||||
private EarningTypeRepository earningTypeRepository;
|
||||
@Mock
|
||||
private GlobalDeductionRuleRepository globalDeductionRuleRepository;
|
||||
@Mock
|
||||
private TaxBracketRepository taxBracketRepository;
|
||||
@Mock
|
||||
private AttendanceRecordRepository attendanceRecordRepository;
|
||||
@Mock
|
||||
private AbsenceRepository absenceRepository;
|
||||
@Mock
|
||||
private br.gov.sigefp.budget.repository.BudgetLineRepository budgetLineRepository;
|
||||
@Mock
|
||||
private br.gov.sigefp.budget.repository.FiscalYearRepository fiscalYearRepository;
|
||||
@Mock
|
||||
private BudgetIntegrationService budgetIntegrationService;
|
||||
|
||||
@Mock
|
||||
private AgentContractRepository agentContractRepository;
|
||||
@Mock
|
||||
private FamilyAllowanceTableRepository familyAllowanceTableRepository;
|
||||
@Mock
|
||||
private br.gov.sigefp.common.service.PaymentGenerator paymentGenerator;
|
||||
@Mock
|
||||
private DeductionTypeRepository deductionTypeRepository;
|
||||
|
||||
@InjectMocks
|
||||
private PayrollService payrollService;
|
||||
|
||||
private UUID payrollRunId;
|
||||
private PayrollRun payrollRun;
|
||||
private Agent agent;
|
||||
private EarningType baseSalaryType;
|
||||
private EarningType familyAllowanceType;
|
||||
private EarningType subsidyType;
|
||||
private AgentContract contract;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
payrollRunId = UUID.randomUUID();
|
||||
PayrollPeriod period = PayrollPeriod.builder()
|
||||
.fiscalYear(2024)
|
||||
.month(1)
|
||||
.startDate(LocalDate.of(2024, 1, 1))
|
||||
.endDate(LocalDate.of(2024, 1, 31))
|
||||
.build();
|
||||
|
||||
payrollRun = PayrollRun.builder()
|
||||
.id(payrollRunId)
|
||||
.period(period)
|
||||
.status("PENDING")
|
||||
.build();
|
||||
|
||||
agent = Agent.builder()
|
||||
.id(UUID.randomUUID())
|
||||
.fullName("Test Agent")
|
||||
.status("ACTIVE")
|
||||
.salaryStep(UUID.randomUUID())
|
||||
.posseDate(LocalDate.of(2020, 1, 1))
|
||||
.eligibleDependentsCount(2)
|
||||
.build();
|
||||
|
||||
contract = AgentContract.builder()
|
||||
.agent(agent)
|
||||
.isActive(true)
|
||||
.startDate(LocalDate.of(2020, 1, 1))
|
||||
.build();
|
||||
|
||||
baseSalaryType = EarningType.builder()
|
||||
.id(UUID.randomUUID())
|
||||
.code("SALARIO_BASE")
|
||||
.name("Vencimento Base")
|
||||
.build();
|
||||
|
||||
familyAllowanceType = EarningType.builder()
|
||||
.id(UUID.randomUUID())
|
||||
.code("ABONO_FAMILIA")
|
||||
.name("Abono de Família")
|
||||
.build();
|
||||
|
||||
subsidyType = EarningType.builder()
|
||||
.id(UUID.randomUUID())
|
||||
.code("SUBSIDIO")
|
||||
.name("Subsídio")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Deve gerar itens de folha com cálculos de INPS e IRPS corretos")
|
||||
void generatePayrollItems_WithSuccess() {
|
||||
// Mocking
|
||||
when(payrollRunRepository.findById(payrollRunId)).thenReturn(Optional.of(payrollRun));
|
||||
when(earningTypeRepository.findByCode("SALARIO_BASE")).thenReturn(Optional.of(baseSalaryType));
|
||||
when(earningTypeRepository.findByCode("ABONO_FAMILIA")).thenReturn(Optional.of(familyAllowanceType));
|
||||
when(earningTypeRepository.findByCode("SUBSIDIO")).thenReturn(Optional.of(subsidyType));
|
||||
when(agentRepository.findByStatus("ACTIVE")).thenReturn(List.of(agent));
|
||||
|
||||
// MOCK BATCH FETCHES
|
||||
when(agentContractRepository.findByAgentIdIn(anyList())).thenReturn(List.of(contract));
|
||||
when(attendanceRecordRepository.findByAgentIdInAndDateRange(any(), any(), any())).thenReturn(List.of());
|
||||
when(absenceRepository.findByAgentIdInAndDateRange(any(), any(), any())).thenReturn(List.of());
|
||||
// when(budgetLineRepository.findByFiscalYearId(any())).thenReturn(List.of());
|
||||
// // Not strictly needed if returns empty by default mock? better explicit.
|
||||
|
||||
// Salário Base: 100.000
|
||||
BigDecimal baseAmount = new BigDecimal("100000");
|
||||
SalaryStep step = SalaryStep.builder().build();
|
||||
step.setId(agent.getSalaryStep()); // Ensure ID matches agent
|
||||
|
||||
SalaryGrid salaryGrid = SalaryGrid.builder()
|
||||
.baseAmount(baseAmount)
|
||||
.step(step)
|
||||
.validFrom(LocalDate.of(2020, 1, 1))
|
||||
.build();
|
||||
|
||||
// MOCK findAll for Batch Fetch
|
||||
when(salaryGridRepository.findAll()).thenReturn(List.of(salaryGrid));
|
||||
// when(salaryGridRepository.findByStepIdAndDate(any(),
|
||||
// any())).thenReturn(salaryGrid); // Removing old mock
|
||||
|
||||
// Regras Globais: INPS (7%) e Selo (0.3%)
|
||||
DeductionType inpsType = DeductionType.builder().code("INPS").name("INPS").build();
|
||||
GlobalDeductionRule inpsRule = GlobalDeductionRule.builder()
|
||||
.deductionType(inpsType)
|
||||
.percentage(new BigDecimal("0.07"))
|
||||
.build();
|
||||
|
||||
// MOCK FISCAL YEAR & BUDGET LINE
|
||||
br.gov.sigefp.budget.domain.FiscalYear fy = br.gov.sigefp.budget.domain.FiscalYear.builder()
|
||||
.id(UUID.randomUUID())
|
||||
.year(2024)
|
||||
.build();
|
||||
when(fiscalYearRepository.findByYear(2024)).thenReturn(Optional.of(fy));
|
||||
when(budgetLineRepository.findByFiscalYearId(any())).thenReturn(List.of());
|
||||
|
||||
DeductionType seloType = DeductionType.builder().code("SELO").name("Imposto de Selo").build();
|
||||
GlobalDeductionRule seloRule = GlobalDeductionRule.builder()
|
||||
.deductionType(seloType)
|
||||
.percentage(new BigDecimal("0.003"))
|
||||
.build();
|
||||
|
||||
when(globalDeductionRuleRepository.findActiveRules(any())).thenReturn(List.of(inpsRule, seloRule));
|
||||
|
||||
// IRPS: Faixa de 10% para base > 50.000 (Exemplo simples)
|
||||
DeductionType irpsType = DeductionType.builder().code("IRPS").name("IRPS").build();
|
||||
TaxBracket irpsBracket = TaxBracket.builder()
|
||||
.deductionType(irpsType)
|
||||
.lowerLimit(new BigDecimal("50001"))
|
||||
.upperLimit(new BigDecimal("150000"))
|
||||
.ratePercentage(new BigDecimal("0.10"))
|
||||
.excessDeduction(new BigDecimal("5000"))
|
||||
.build();
|
||||
|
||||
when(taxBracketRepository.findActiveBrackets(any())).thenReturn(List.of(irpsBracket));
|
||||
|
||||
// Execute
|
||||
payrollService.generatePayrollItems(payrollRunId);
|
||||
|
||||
// Verify Items Saved
|
||||
// 1. Salário Base (100.000)
|
||||
// 2. Abono Família (2000)
|
||||
// Bruto = 102.000
|
||||
// 3. INPS (102.000 * 0.07 = 7.140)
|
||||
// 4. Selo (102.000 * 0.003 = 306)
|
||||
// Base IRPS = 102.000 - 7.140 = 94.860
|
||||
// 5. IRPS (94.860 * 0.10 - 5.000 = 9.486 - 5.000 = 4.486)
|
||||
|
||||
verify(payrollItemRepository, atLeast(5)).save(any(PayrollItem.class));
|
||||
|
||||
// Vamos verificar os valores específicos se possível via capture (opcional aqui
|
||||
// para brevidade)
|
||||
verify(payrollRunRepository).save(argThat(run -> "GENERATED".equals(run.getStatus())));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Deve falhar ao processar folha sem itens orçamentados")
|
||||
void processPayrollRun_FailsWhenBudgetLineMissing() {
|
||||
payrollRun.setStatus("GENERATED");
|
||||
when(payrollRunRepository.findById(payrollRunId)).thenReturn(Optional.of(payrollRun));
|
||||
|
||||
PayrollItem itemWithoutBudget = PayrollItem.builder()
|
||||
.id(UUID.randomUUID())
|
||||
.totalAmount(new BigDecimal("1000"))
|
||||
.budgetLine(null) // Erro proposital
|
||||
.build();
|
||||
|
||||
when(payrollItemRepository.findByPayrollRunId(payrollRunId)).thenReturn(List.of(itemWithoutBudget));
|
||||
|
||||
assertThrows(BusinessException.class, () -> payrollService.processPayrollRun(payrollRunId));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user