Commit 4bf1eb8a by Vanesa Puma Yamo

feat: migrar renta de quinta a pl_planilla, pl_renta_basico y pl_utilidades

La tabla planilla.renta_quinta fue reemplazada por planilla.pl_planilla; el
basico y las utilidades ahora viven en tablas aparte (pl_renta_basico y
pl_utilidades, PK compuesta numero_documento+anio+mes).

- RentaQuinta pasa a mapear pl_planilla (id_pl_planilla); utilidades, campo1
  y campo2 quedan @Transient porque ya no son columnas de esa tabla.
- Nuevos modulos rentabasico y utilidades con sus entities/repositories,
  siguiendo el patron de otrosempleadores.
- RentaQuintaServiceImpl aplica pl_renta_basico.monto_basico como reemplazo
  del basico de pl_planilla cuando existe registro para el mes (si no, se
  conserva el de pl_planilla), y carga pl_utilidades.monto_utilidad como el
  campo "utilidades" que antes vivia en la propia renta_quinta.
- docs/fk-pl-planilla-renta-basico-utilidades.sql: DDL para las FK
  compuestas de pl_planilla hacia las 2 tablas nuevas (las PK compuestas ya
  existen en ellas); no se pudo ejecutar porque el MCP de Postgres
  configurado es de solo lectura.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
parent 22d44d40
-- DDL propuesto para relacionar planilla.pl_planilla con las tablas nuevas
-- planilla.pl_renta_basico y planilla.pl_utilidades.
--
-- No se agregan columnas nuevas en pl_planilla: numero_documento, boleta_anio
-- y boleta_mes ya existen y coinciden con la primary key compuesta de ambas
-- tablas nuevas (numero_documento, anio, mes), así que las FK son compuestas
-- sobre esas mismas columnas.
--
-- Ejecutar en el esquema planilla. Requiere permisos DDL que el MCP de
-- Postgres configurado (solo lectura) no tiene.
-- 1) Opcional: alinear el tipo de numero_documento en pl_planilla (hoy text)
-- con el de las tablas nuevas (varchar(15)), si el equipo prefiere evitar
-- la comparación cross-type en la FK. No es obligatorio: Postgres admite
-- FK entre text y varchar.
-- ALTER TABLE planilla.pl_planilla
-- ALTER COLUMN numero_documento TYPE varchar(15);
-- 2) Foreign key hacia pl_renta_basico
ALTER TABLE planilla.pl_planilla
ADD CONSTRAINT fk_pl_planilla_renta_basico
FOREIGN KEY (numero_documento, boleta_anio, boleta_mes)
REFERENCES planilla.pl_renta_basico (numero_documento, anio, mes);
-- 3) Foreign key hacia pl_utilidades
ALTER TABLE planilla.pl_planilla
ADD CONSTRAINT fk_pl_planilla_utilidades
FOREIGN KEY (numero_documento, boleta_anio, boleta_mes)
REFERENCES planilla.pl_utilidades (numero_documento, anio, mes);
-- Notas:
-- * pl_renta_basico y pl_utilidades ya tienen su PK compuesta creada
-- (pl_gratificacion_pkey y pl_utilidades_pkey respectivamente), así que no
-- hace falta generarlas.
-- * Estas FK exigen que exista una fila en pl_renta_basico/pl_utilidades para
-- cada combinación (numero_documento, boleta_anio, boleta_mes) que ya tenga
-- pl_planilla. Si la carga de esas tablas es posterior o parcial, correr
-- antes un SELECT de validación (LEFT JOIN buscando NULL) para no romper el
-- ALTER TABLE con datos huérfanos.
package so.planilla_be.rentabasico.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.IdClass;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.Setter;
import java.math.BigDecimal;
/**
* Básico mensual declarado en planilla.pl_renta_basico. Cuando existe un registro para
* (numeroDocumento, anio, mes) reemplaza al básico de planilla.pl_planilla en el cálculo de renta
* de quinta; si no existe, se usa el de pl_planilla tal cual.
*/
@Getter
@Setter
@Entity
@Table(name = "pl_renta_basico")
@IdClass(RentaBasicoId.class)
public class RentaBasico {
@Id
@Column(name = "numero_documento", length = 15)
private String numeroDocumento;
@Id
@Column(name = "anio")
private Integer anio;
@Id
@Column(name = "mes")
private Integer mes;
@Column(name = "monto_basico", precision = 12, scale = 2, nullable = false)
private BigDecimal montoBasico;
}
package so.planilla_be.rentabasico.entity;
import java.io.Serializable;
import java.util.Objects;
public class RentaBasicoId implements Serializable {
private String numeroDocumento;
private Integer anio;
private Integer mes;
public RentaBasicoId() {
}
public RentaBasicoId(String numeroDocumento, Integer anio, Integer mes) {
this.numeroDocumento = numeroDocumento;
this.anio = anio;
this.mes = mes;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof RentaBasicoId that)) {
return false;
}
return Objects.equals(numeroDocumento, that.numeroDocumento)
&& Objects.equals(anio, that.anio)
&& Objects.equals(mes, that.mes);
}
@Override
public int hashCode() {
return Objects.hash(numeroDocumento, anio, mes);
}
}
package so.planilla_be.rentabasico.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import so.planilla_be.rentabasico.entity.RentaBasico;
import so.planilla_be.rentabasico.entity.RentaBasicoId;
import java.util.List;
public interface RentaBasicoRepository extends JpaRepository<RentaBasico, RentaBasicoId> {
List<RentaBasico> findByAnioAndNumeroDocumentoIn(Integer anio, List<String> numerosDocumento);
}
......@@ -6,6 +6,7 @@ import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;
import lombok.Getter;
import lombok.Setter;
......@@ -16,12 +17,12 @@ import java.time.LocalDateTime;
@Getter
@Setter
@Entity
@Table(name = "renta_quinta")
@Table(name = "pl_planilla")
public class RentaQuinta {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id_renta_quinta")
@Column(name = "id_pl_planilla")
private Long idRentaQuinta;
@Column(name = "boleta_anio")
......@@ -163,7 +164,8 @@ public class RentaQuinta {
@Column(name = "gratificacion_julio_diciembre", precision = 15, scale = 2)
private BigDecimal gratificacionJulioDiciembre;
@Column(name = "utilidades", precision = 15, scale = 2)
/** Ya no vive en pl_planilla: la carga el service desde planilla.pl_utilidades. */
@Transient
private BigDecimal utilidades;
/* VACACIONES Y LIQUIDACIÓN */
......@@ -376,9 +378,10 @@ public class RentaQuinta {
@Column(name = "tipo_labor")
private String tipoLabor;
@Column(name = "campo_1")
/** No existen en pl_planilla; se conservan para no romper el contrato del API. */
@Transient
private Integer campo1;
@Column(name = "campo_2")
@Transient
private Integer campo2;
}
......@@ -21,6 +21,10 @@ import so.planilla_be.rentaquinta.repository.RentaQuintaRepository;
import so.planilla_be.rentaquinta.service.RentaQuintaService;
import so.planilla_be.otrosempleadores.entity.RetencionOtrosEmpleadores;
import so.planilla_be.otrosempleadores.repository.RetencionOtrosEmpleadoresRepository;
import so.planilla_be.rentabasico.entity.RentaBasico;
import so.planilla_be.rentabasico.repository.RentaBasicoRepository;
import so.planilla_be.utilidades.entity.Utilidad;
import so.planilla_be.utilidades.repository.UtilidadRepository;
import java.math.BigDecimal;
import java.util.Comparator;
......@@ -42,14 +46,20 @@ public class RentaQuintaServiceImpl implements RentaQuintaService {
private final RentaQuintaMapper mapper;
private final RentaQuintaCalculadora calculadora;
private final RetencionOtrosEmpleadoresRepository otrosEmpleadoresRepository;
private final RentaBasicoRepository rentaBasicoRepository;
private final UtilidadRepository utilidadRepository;
public RentaQuintaServiceImpl(RentaQuintaRepository repository, RentaQuintaMapper mapper,
RentaQuintaCalculadora calculadora,
RetencionOtrosEmpleadoresRepository otrosEmpleadoresRepository) {
RetencionOtrosEmpleadoresRepository otrosEmpleadoresRepository,
RentaBasicoRepository rentaBasicoRepository,
UtilidadRepository utilidadRepository) {
this.repository = repository;
this.mapper = mapper;
this.calculadora = calculadora;
this.otrosEmpleadoresRepository = otrosEmpleadoresRepository;
this.rentaBasicoRepository = rentaBasicoRepository;
this.utilidadRepository = utilidadRepository;
}
@Override
......@@ -68,8 +78,10 @@ public class RentaQuintaServiceImpl implements RentaQuintaService {
List<String> numeros = documentos.getContent().stream()
.map(DocumentoResumenProjection::getNumeroDocumento)
.toList();
Map<String, List<RentaQuinta>> porDocumento = numeros.isEmpty() ? Map.of()
: repository.historialHastaMes(numeros, filtro.periodo(), filtro.mesCorte()).stream()
List<RentaQuinta> historialCompleto = numeros.isEmpty() ? List.of()
: repository.historialHastaMes(numeros, filtro.periodo(), filtro.mesCorte());
aplicarBasicoYUtilidades(historialCompleto, filtro.periodo());
Map<String, List<RentaQuinta>> porDocumento = historialCompleto.stream()
.collect(Collectors.groupingBy(RentaQuinta::getNumeroDocumento));
return documentos.map(d -> {
......@@ -115,23 +127,26 @@ public class RentaQuintaServiceImpl implements RentaQuintaService {
@Override
public RentaQuintaDetalleDTO obtenerPorId(Long id) {
return repository.findById(id)
.map(mapper::toDetalle)
RentaQuinta boleta = repository.findById(id)
.orElseThrow(() -> new RentaQuintaNoEncontradaException(
"No existe boleta de renta de quinta con id %d".formatted(id)));
aplicarBasicoYUtilidades(List.of(boleta), boleta.getBoletaAnio());
return mapper.toDetalle(boleta);
}
@Override
public RentaQuintaDetalleDTO obtenerBoleta(Integer codper, Integer anio, Integer mes) {
return repository.findByCodperAndBoletaAnioAndBoletaMes(codper, anio, mes)
.map(mapper::toDetalle)
RentaQuinta boleta = repository.findByCodperAndBoletaAnioAndBoletaMes(codper, anio, mes)
.orElseThrow(() -> new RentaQuintaNoEncontradaException(
"No existe boleta para codper %d, periodo %d-%02d".formatted(codper, anio, mes)));
aplicarBasicoYUtilidades(List.of(boleta), anio);
return mapper.toDetalle(boleta);
}
@Override
public RentaQuintaDetalleCompletoDTO obtenerDetalleCompleto(String numeroDocumento, Integer anio, Integer mes) {
List<RentaQuinta> historialAnio = repository.historialAnualPorDocumento(numeroDocumento, anio);
aplicarBasicoYUtilidades(historialAnio, anio);
int mesCorte = mes;
Agregado a = agregar(historialAnio, mesCorte);
......@@ -229,6 +244,42 @@ public class RentaQuintaServiceImpl implements RentaQuintaService {
retencionAnterior, retencionMesActual, retencionProyeccion);
}
/**
* planilla.pl_renta_basico reemplaza el básico de pl_planilla cuando existe un registro para
* (numeroDocumento, anio, mes); si no, se conserva el de pl_planilla. planilla.pl_utilidades
* pasa a ser el campo "utilidades" que antes vivía en la propia renta_quinta (0 si no hay
* declaración para ese mes).
*/
private void aplicarBasicoYUtilidades(List<RentaQuinta> historial, Integer anio) {
if (historial.isEmpty()) {
return;
}
List<String> documentos = historial.stream()
.map(RentaQuinta::getNumeroDocumento)
.distinct()
.toList();
Map<String, Map<Integer, BigDecimal>> basicoPorDocumento = rentaBasicoRepository
.findByAnioAndNumeroDocumentoIn(anio, documentos).stream()
.collect(Collectors.groupingBy(RentaBasico::getNumeroDocumento,
Collectors.toMap(RentaBasico::getMes, RentaBasico::getMontoBasico)));
Map<String, Map<Integer, BigDecimal>> utilidadPorDocumento = utilidadRepository
.findByAnioAndNumeroDocumentoIn(anio, documentos).stream()
.collect(Collectors.groupingBy(Utilidad::getNumeroDocumento,
Collectors.toMap(Utilidad::getMes, Utilidad::getMontoUtilidad)));
for (RentaQuinta r : historial) {
BigDecimal basico = valorDelMes(basicoPorDocumento, r.getNumeroDocumento(), r.getBoletaMes());
if (basico != null) {
r.setBasico(basico);
}
r.setUtilidades(orZero(valorDelMes(utilidadPorDocumento, r.getNumeroDocumento(), r.getBoletaMes())));
}
}
private BigDecimal valorDelMes(Map<String, Map<Integer, BigDecimal>> porDocumento, String numeroDocumento, Integer mes) {
Map<Integer, BigDecimal> porMes = porDocumento.get(numeroDocumento);
return porMes == null ? null : porMes.get(mes);
}
private Predicate<RentaQuinta> esMesCorte(int mesCorte) {
return r -> r.getBoletaMes().equals(mesCorte);
}
......@@ -247,7 +298,9 @@ public class RentaQuintaServiceImpl implements RentaQuintaService {
@Override
public List<RentaQuintaHistorialDTO> historialAnual(Integer codper, Integer anio) {
List<RentaQuintaHistorialDTO> historial = repository.historialAnual(codper, anio).stream()
List<RentaQuinta> boletas = repository.historialAnual(codper, anio);
aplicarBasicoYUtilidades(boletas, anio);
List<RentaQuintaHistorialDTO> historial = boletas.stream()
.map(mapper::toHistorial)
.toList();
if (historial.isEmpty()) {
......
package so.planilla_be.utilidades.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.IdClass;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.Setter;
import java.math.BigDecimal;
/**
* Utilidades mensuales declaradas en planilla.pl_utilidades. Reemplaza a la antigua columna
* "utilidades" de renta_quinta en el cálculo de renta de quinta.
*/
@Getter
@Setter
@Entity
@Table(name = "pl_utilidades")
@IdClass(UtilidadId.class)
public class Utilidad {
@Id
@Column(name = "numero_documento", length = 15)
private String numeroDocumento;
@Id
@Column(name = "anio")
private Integer anio;
@Id
@Column(name = "mes")
private Integer mes;
@Column(name = "monto_utilidad", precision = 12, scale = 2, nullable = false)
private BigDecimal montoUtilidad;
}
package so.planilla_be.utilidades.entity;
import java.io.Serializable;
import java.util.Objects;
public class UtilidadId implements Serializable {
private String numeroDocumento;
private Integer anio;
private Integer mes;
public UtilidadId() {
}
public UtilidadId(String numeroDocumento, Integer anio, Integer mes) {
this.numeroDocumento = numeroDocumento;
this.anio = anio;
this.mes = mes;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof UtilidadId that)) {
return false;
}
return Objects.equals(numeroDocumento, that.numeroDocumento)
&& Objects.equals(anio, that.anio)
&& Objects.equals(mes, that.mes);
}
@Override
public int hashCode() {
return Objects.hash(numeroDocumento, anio, mes);
}
}
package so.planilla_be.utilidades.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import so.planilla_be.utilidades.entity.Utilidad;
import so.planilla_be.utilidades.entity.UtilidadId;
import java.util.List;
public interface UtilidadRepository extends JpaRepository<Utilidad, UtilidadId> {
List<Utilidad> findByAnioAndNumeroDocumentoIn(Integer anio, List<String> numerosDocumento);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment