Commit b7ffff5a by Mauro Paolo Josue Zuñiga Mallqui

merge: integrar mzuniga preservando gratificacion por contrato

Conservar reglas de master y fechaInicioContrato, usar el mismo calculo en la matriz mensual y mantener tipoDocumento en el JSON. Separar boletas antes de ajustar utilidades y distinguir DOC1/DOC2 en la retencion anterior.
parents 41b66e29 f34d5c7b
This diff is collapsed. Click to expand it.
-- Catálogo del filtro Periodo de renta de quinta categoría.
BEGIN;
CREATE TABLE IF NOT EXISTS planilla.pl_periodo (
id_periodo INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
mes INTEGER NOT NULL,
anio INTEGER NOT NULL,
CONSTRAINT ck_pl_periodo_mes CHECK (mes BETWEEN 1 AND 12),
CONSTRAINT ck_pl_periodo_anio CHECK (anio BETWEEN 1000 AND 9999),
CONSTRAINT uq_pl_periodo_mes_anio UNIQUE (mes, anio)
);
-- Incluir los doce meses permite consultar también los meses proyectados.
INSERT INTO planilla.pl_periodo (mes, anio)
SELECT meses.mes, periodos.anio
FROM (
SELECT DISTINCT boleta_anio AS anio
FROM planilla.pl_planilla
WHERE boleta_anio BETWEEN 1000 AND 9999
) periodos
CROSS JOIN generate_series(1, 12) AS meses(mes)
ORDER BY periodos.anio, meses.mes
ON CONFLICT (mes, anio) DO NOTHING;
COMMIT;
# Integración de renta quinta con jQuery DataTables
> Propuesta pendiente de implementación: esta rama incluye el DTO de respuesta, pero aún no
> publica `/api/v1/renta-quinta/listado/datatables`. La ruta disponible es `/listado`, que
> recibe `page` y `size` y devuelve una página Spring. El ejemplo siguiente requiere implementar
> primero el adaptador DataTables.
## Endpoint
`GET /api/v1/renta-quinta/listado/datatables`
Usar `serverSide: true`: cada cambio de página consulta al backend y solo calcula las filas solicitadas. El endpoint anterior `/listado` sigue devolviendo una página Spring y conserva su contrato.
## Parámetros GET
| Parámetro | Descripción |
|---|---|
| `anio` | Obligatorio, por ejemplo `2026` |
| `mes` | Obligatorio, de 1 a 12 |
| `tipoPlanilla` | Opcional: `ADM`, `DOC1`, `DOC2` |
| `draw` | Contador de solicitud generado por DataTables, entero >= 0 |
| `start` | Posición inicial desde 0; debe ser múltiplo de `length` |
| `length` | Tamaño de página, de 1 a 100; por defecto 20 |
| `search[value]` | Búsqueda por documento o apellidos y nombres |
| `sede`, `areaGeneral`, `subArea`, `cargo` | Filtros numéricos opcionales; omitir cuando no se seleccionen |
No enviar `page` ni `size` a esta ruta. No ofrecer la opción «Todos» (`length=-1`). No se admite ordenamiento dinámico ni búsqueda por columna: configurar `ordering: false`. El orden es por apellido paterno, materno y nombres.
Primera página:
```text
/api/v1/renta-quinta/listado/datatables?anio=2026&mes=8&tipoPlanilla=ADM&draw=1&start=0&length=20
```
Segunda página: `start=20&length=20`. La siguiente solicitud usa el `draw` que genera DataTables.
## Respuesta
```json
{
"draw": 1,
"recordsTotal": 350,
"recordsFiltered": 12,
"data": []
}
```
Los números son ilustrativos. `data` contiene las filas de la página con los campos de `RentaQuintaResumenDTO`. `recordsTotal` cuenta los trabajadores dentro del contexto seleccionado (año, mes, tipo de planilla y filtros de catálogo), antes de aplicar la búsqueda de texto. `recordsFiltered` cuenta todos los resultados de esa búsqueda, no solo los de la página. Sin búsqueda ambos totales coinciden. Solo se carga el historial y se calcula la página; la búsqueda SQL y los conteos todavía pueden necesitar recorrer más registros.
## JavaScript
El ejemplo supone que existe una tabla `#tablaRenta` con ocho encabezados y controles `#anio`, `#mes`, `#tipoPlanilla`, `#sede`, `#areaGeneral`, `#subArea`, `#cargo`. Adaptar los selectores a la pantalla. Si frontend y backend tienen distinta dirección, cambiar `url` y configurar el acceso correspondiente.
```javascript
const tabla = $('#tablaRenta').DataTable({
processing: true,
serverSide: true,
ordering: false,
pageLength: 20,
lengthMenu: [10, 20, 50, 100],
searchDelay: 500,
ajax: {
url: '/api/v1/renta-quinta/listado/datatables',
type: 'GET',
data: function (d) {
// Conservar draw, start, length y search que genera DataTables.
d.anio = $('#anio').val();
d.mes = $('#mes').val();
for (const campo of ['tipoPlanilla', 'sede', 'areaGeneral', 'subArea', 'cargo']) {
const valor = $('#' + campo).val();
if (valor !== null && valor !== undefined && valor !== '') {
d[campo] = valor;
} else {
delete d[campo];
}
}
},
dataSrc: 'data'
},
columns: [
{ data: 'numeroDocumento', render: $.fn.dataTable.render.text() },
{ data: 'nombreCompleto', render: $.fn.dataTable.render.text() },
{ data: 'historialReal' },
{ data: 'planillaCargada' },
{ data: 'proyeccionFutura' },
{ data: 'rentaBrutaAnual' },
{ data: 'impuestoAnual' },
{ data: 'retencionDelMes' }
]
});
$('#anio, #mes, #tipoPlanilla, #sede, #areaGeneral, #subArea, #cargo')
.on('change', function () {
tabla.ajax.reload(null, true); // Volver a la primera página al cambiar filtros.
});
```
La búsqueda global de DataTables envía `search[value]` automáticamente. No usar `tabla.clear().rows.add(...)` para cargar todo el listado ni reemplazar el objeto `d` por uno que pierda los parámetros de DataTables. Los valores inválidos de paginación responden 400.
## Validación manual
1. Abrir la pantalla: verificar en Network `start=0`, `length=20` y hasta 20 filas en `data`.
2. Ir a la segunda página: verificar `start=20` y una nueva solicitud.
3. Buscar un documento: verificar `search[value]`, las filas y `recordsFiltered`.
4. Cambiar mes o tipo: verificar que vuelva a `start=0`.
5. Revisar el log `Listado renta quinta` para comparar tiempos por etapa. Una página menor reduce la carga del historial y del cálculo, pero no garantiza que los conteos sean más rápidos.
Referencia del protocolo: [DataTables: Server-side processing](https://datatables.net/manual/server-side).
......@@ -11,4 +11,7 @@ public interface RetencionOtrosEmpleadoresRepository
List<RetencionOtrosEmpleadores> findByDocumentoTrabajadorAndAnioOrderByMes(
String documentoTrabajador, Integer anio);
/** Declaraciones del año de varios trabajadores en una sola consulta (grilla / grabado masivo). */
List<RetencionOtrosEmpleadores> findByAnioAndDocumentoTrabajadorIn(Integer anio, List<String> documentosTrabajador);
}
......@@ -15,6 +15,10 @@ public interface ProyeccionDocenteHoraRepository extends JpaRepository<Proyeccio
List<ProyeccionDocenteHora> findByNumeroDocumentoAndProyeccionAnioOrderByProyeccionMesAscBoletaTipoPersonalAsc(
String numeroDocumento, Integer proyeccionAnio);
/** Filas del año de varios docentes en una sola consulta (grilla / grabado masivo). */
List<ProyeccionDocenteHora> findByProyeccionAnioAndNumeroDocumentoIn(Integer proyeccionAnio,
List<String> numerosDocumento);
/** Clave lógica (la tabla solo tiene PK sobre id_proyeccion). */
Optional<ProyeccionDocenteHora> findFirstByNumeroDocumentoAndBoletaTipoPersonalAndProyeccionAnioAndProyeccionMes(
String numeroDocumento, String boletaTipoPersonal, Integer proyeccionAnio, Integer proyeccionMes);
......
......@@ -172,6 +172,16 @@ public class RentaQuintaCalculadora {
return total;
}
/** Importe por mes para la matriz, con las mismas reglas de contrato y modalidad del cálculo. */
public BigDecimal gratificacionDelMes(DatosCalculo datos, int mes) {
if (mes != 7 && mes != 12) {
return BigDecimal.ZERO;
}
return escala(datos.modalidad() == ModalidadCalculo.DOCENTE_POR_HORA
? gratificacionDocenteHora(datos, mes)
: gratificacionProporcional(datos, orZero(datos.basicoReferencia()), mes));
}
/**
* CONDICIÓN FECHA DE CONTRATO (ADM y DOC2) — gratificación de julio (semestre enero-junio) o
* de diciembre (julio-diciembre) según cuánto del semestre cubre el contrato:
......
......@@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import so.planilla_be.rentaquinta.dto.FiltroItemDTO;
import so.planilla_be.rentaquinta.dto.TipoFiltro;
import so.planilla_be.rentaquinta.dto.PeriodoFiltroDTO;
import so.planilla_be.rentaquinta.service.FiltrosService;
import java.util.List;
......@@ -28,4 +29,10 @@ public class FiltrosController {
public List<FiltroItemDTO> listar(@RequestParam TipoFiltro tipo) {
return service.listar(tipo);
}
@Operation(summary = "Lista periodos registrados en pl_periodo, del más reciente al más antiguo")
@GetMapping("/periodos")
public List<PeriodoFiltroDTO> listarPeriodos() {
return service.listarPeriodos();
}
}
......@@ -35,11 +35,12 @@ public class RentaQuintaController {
/** Parámetro que envía el cliente (C#) para elegir las reglas del cálculo por tipo de planilla. */
private static final String TIPO_PLANILLA_DOC = "ADM = administrativo (cálculo normal); DOC1 = docente "
+ "costo por hora (proyección desde pl_proyeccion_renta_docentehora); DOC2 = docente monto fijo "
+ "(mismo cálculo que ADM, con sueldo bruto y gratificación de diciembre ÷ 30 × 16). Filtra por boleta_tipo_personal y tipo_pago. Si no se envía, se "
+ "(gratificación por fecha de contrato, diciembre de 16 días salvo excepción registrada; "
+ "sueldo bruto de diciembre ÷ 30 × 16). Filtra por boleta_tipo_personal y tipo_pago. Si no se envía, se "
+ "toma de la boleta del trabajador.";
private static final String TIPO_PLANILLA_GRABAR_DOC = "Obligatorio. ADM, DOC1 (costo por hora) o DOC2 "
+ "(monto fijo). En pl_renta_quinta se graba boleta_tipo_personal (ADM/DOC) y tipo_pago (1/2, null en ADM).";
+ "(monto fijo). En pl_renta_quinta se graba boleta_tipo_personal (ADM/DOC) y tipo_pago (1/2, vacío en ADM).";
private final RentaQuintaService service;
......@@ -58,13 +59,15 @@ public class RentaQuintaController {
@RequestParam(required = false) Integer areaGeneral,
@RequestParam(required = false) Integer subArea,
@RequestParam(required = false) Integer cargo,
@RequestParam(required = false) Boolean tieneVariacion,
@RequestParam(required = false) Boolean tieneRetencion,
@RequestParam(required = false) String documentoOApellidos,
@RequestParam(defaultValue = "0") Integer page,
@RequestParam(defaultValue = "20") Integer size) {
TipoPlanilla tipo = TipoPlanilla.desdeParametro(tipoPlanilla);
return service.buscar(new RentaQuintaFiltroDTO(
anio, mes, sede, tipo, TipoPago.desdeParametro(tipoPlanilla), areaGeneral, subArea, cargo,
documentoOApellidos, page, size));
tieneVariacion, tieneRetencion, documentoOApellidos, page, size));
}
@Operation(summary = "Detalle completo por id_renta_quinta")
......@@ -74,8 +77,8 @@ public class RentaQuintaController {
}
@Operation(summary = "Detalle completo del cálculo por número de documento + año + mes "
+ "(boleta del mes + puntos 1 a 5: historial de ingresos, parámetros SUNAT, "
+ "liquidación anual, escala por tramos y control de retenciones)")
+ "(boleta del mes + puntos 1 a 6: historial de ingresos, parámetros SUNAT, "
+ "liquidación anual, escala por tramos, control de retenciones y matriz mensual)")
@GetMapping("/detallado")
public RentaQuintaDetalleCompletoDTO detallado(
@RequestParam String numeroDocumento,
......@@ -127,11 +130,13 @@ public class RentaQuintaController {
@RequestParam(required = false) Integer areaGeneral,
@RequestParam(required = false) Integer subArea,
@RequestParam(required = false) Integer cargo,
@RequestParam(required = false) Boolean tieneVariacion,
@RequestParam(required = false) Boolean tieneRetencion,
@RequestParam(required = false) String documentoOApellidos) {
TipoPlanilla tipo = TipoPlanilla.obligatorio(tipoPlanilla);
int grabados = service.grabarMasivo(new RentaQuintaFiltroDTO(
anio, mes, sede, tipo, TipoPago.desdeParametro(tipoPlanilla), areaGeneral, subArea, cargo,
documentoOApellidos, null, null));
tieneVariacion, tieneRetencion, documentoOApellidos, null, null));
return new GrabadoMasivoResultado(grabados);
}
......
package so.planilla_be.rentaquinta.dto;
import java.util.List;
public record DataTableResponse<T>(int draw, long recordsTotal, long recordsFiltered, List<T> data) {}
package so.planilla_be.rentaquinta.dto;
public record PeriodoFiltroDTO(Integer idPeriodo, Integer mes, Integer anio) {}
......@@ -2,12 +2,13 @@ package so.planilla_be.rentaquinta.dto;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
/**
* Detalle completo de renta de quinta de un trabajador en un mes (puntos 1 a 5 del Excel de
* Detalle completo de renta de quinta de un trabajador en un mes (puntos 1 a 6 del Excel de
* referencia "Ver Historial - Cálculo"), en un solo JSON: la boleta del mes, historial de
* ingresos, parámetros SUNAT, liquidación del impuesto anual proyectado, escala por tramos y
* control de retenciones.
* control de retenciones y matriz mensual de ingresos.
*/
public record RentaQuintaDetalleCompletoDTO(
RentaQuintaDetalleDTO boleta,
......@@ -15,9 +16,14 @@ public record RentaQuintaDetalleCompletoDTO(
ParametrosSistemaDTO parametrosSistema,
LiquidacionImpuestoAnualDTO liquidacionImpuestoAnual,
EscalaImpositivaDTO escalaImpositiva,
ControlRetencionesDTO controlRetenciones
ControlRetencionesDTO controlRetenciones,
List<MesIngresosDTO> matrizMensualIngresos
) {
/** Punto 6: importes por concepto y mes; retención grabada o, en su ausencia, de la boleta. */
public record MesIngresosDTO(int mes, boolean proyectado, Map<String, BigDecimal> conceptos,
BigDecimal totalBruto, BigDecimal retencionEfectuada) {}
/** Punto 1: registro e historial de ingresos consolidados, en 3 columnas de periodo. */
public record RegistroHistorialIngresosDTO(
BloqueIngresosDTO pasadoRealRealizado,
......
......@@ -10,9 +10,8 @@ import so.planilla_be.rentaquinta.calculo.TipoPlanilla;
* {@code tipoPlanilla} es el código de {@code boleta_tipo_personal} (ADM / DOC), que además de
* filtrar decide las reglas del cálculo; {@code tipoPago} (sufijo de DOC1 / DOC2: 1 = por hora,
* 2 = monto fijo) filtra por {@code tipo_pago} y decide la proyección. El resto de filtros de catálogo
* (sede, área, subárea, cargo) llegan como Integer y se comparan contra columnas TEXT convirtiendo
* el número a su
* representación en texto.
* llegan como Integer. Área, subárea y cargo se resuelven desde nuevo.personal y sus catálogos
* foráneos. Los estados de variación y retención se evalúan sobre el cálculo antes de paginar.
*/
public record RentaQuintaFiltroDTO(
Integer periodo,
......@@ -23,6 +22,8 @@ public record RentaQuintaFiltroDTO(
Integer areaGeneral,
Integer subArea,
Integer cargo,
Boolean tieneVariacion,
Boolean tieneRetencion,
String documentoOApellidos,
Integer page,
Integer size
......
......@@ -16,6 +16,13 @@ public record RentaQuintaResumenDTO(
String numeroDocumento,
String nombreCompleto,
LocalDate fechaInicioContrato,
Integer idUnidadOrganizativa,
Integer idArea,
String area,
Integer idSubArea,
String subArea,
Integer idCargo,
String cargo,
Integer mesProceso,
BigDecimal historialReal,
BigDecimal planillaCargada,
......
......@@ -46,8 +46,8 @@ public class RentaQuinta {
@Column(name = "unidad_organizativa")
private Integer unidadOrganizativa;
@Column(name = "tipo_documento", length = 2, columnDefinition = "bpchar")
private String tipoDocumento;
@Column(name = "tipo_planilla", length = 2, columnDefinition = "bpchar")
private String tipoPlanilla;
@Column(name = "apellido_paterno")
private String apellidoPaterno;
......@@ -378,10 +378,10 @@ public class RentaQuinta {
@Column(name = "faltas")
private Integer faltas;
@Column(name = "pago", precision = 15, scale = 2)
@Column(name = "neto_pagado", precision = 15, scale = 2)
private BigDecimal pago;
@Column(name = "pendiente", precision = 15, scale = 2)
@Column(name = "diferencia", precision = 15, scale = 2)
private BigDecimal pendiente;
@Column(name = "tipo_labor")
......
......@@ -14,9 +14,13 @@ import so.planilla_be.rentaquinta.dto.RentaQuintaDetalleCompletoDTO.RegistroHist
import so.planilla_be.rentaquinta.dto.RentaQuintaDetalleCompletoDTO.TramoDTO;
import so.planilla_be.rentaquinta.dto.RentaQuintaResumenDTO;
import so.planilla_be.rentaquinta.entity.RentaQuinta;
import so.planilla_be.rentaquinta.repository.OrganizacionPersonalRepository.Organizacion;
import java.math.BigDecimal;
import java.util.List;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
@Component
......@@ -24,13 +28,21 @@ public class RentaQuintaMapper {
public RentaQuintaResumenDTO toResumen(RentaQuinta referencia, Integer codper, Integer periodo, Integer mesCorte,
BigDecimal historialReal, BigDecimal planillaCargada,
BigDecimal retenidoAnterior, CalculoRentaQuinta calculo,BigDecimal retencionAnteriorGrabada, BigDecimal diferencia) {
BigDecimal retenidoAnterior, CalculoRentaQuinta calculo,BigDecimal retencionAnteriorGrabada, BigDecimal diferencia,
Organizacion organizacion) {
return new RentaQuintaResumenDTO(
codper,
periodo,
referencia.getNumeroDocumento(),
nombreCompleto(referencia.getApellidoPaterno(), referencia.getApellidoMaterno(), referencia.getNombres()),
referencia.getFechaInicioContrato(),
organizacion == null ? null : organizacion.idUnidadOrganizativa(),
organizacion == null ? null : organizacion.idArea(),
organizacion == null ? null : organizacion.area(),
organizacion == null ? null : organizacion.idSubArea(),
organizacion == null ? null : organizacion.subArea(),
organizacion == null ? null : organizacion.idCargo(),
organizacion == null ? null : organizacion.cargo(),
mesCorte,
orZero(historialReal),
orZero(planillaCargada),
......@@ -76,11 +88,12 @@ public class RentaQuintaMapper {
}
/**
* Imagen detallada de la boleta (puntos 1 a 5 del Excel "Ver Historial - Cálculo"): historial
* Imagen detallada de la boleta (puntos 1 a 6 del Excel "Ver Historial - Cálculo"): historial
* de ingresos por columna de periodo, parámetros SUNAT, liquidación anual, escala por tramos
* y control de retenciones.
*/
public RentaQuintaDetalleCompletoDTO toDetalleCompleto(RentaQuinta boleta, List<RentaQuinta> historialAnio, int mesCorte, CalculoRentaQuinta calculo) {
public RentaQuintaDetalleCompletoDTO toDetalleCompleto(RentaQuinta boleta, List<RentaQuinta> historialAnio, int mesCorte, CalculoRentaQuinta calculo,
List<RentaQuintaDetalleCompletoDTO.MesIngresosDTO> matrizMensual) {
// El total del bloque "pasado" usa el mismo historialReal que ya entra a la base
// imponible en RentaQuintaCalculadora, en vez de recalcularlo aparte: así el punto 1
// siempre queda consistente con lo que efectivamente se usó para el impuesto anual.
......@@ -165,7 +178,42 @@ public class RentaQuintaMapper {
retencionOtrosEmpleadoresTotal,
saldoPendiente, calculo.divisorMes(), calculo.retencionDelMes());
return new RentaQuintaDetalleCompletoDTO(toDetalle(boleta), registro, parametros, liquidacion, escala, control);
return new RentaQuintaDetalleCompletoDTO(toDetalle(boleta), registro, parametros, liquidacion, escala, control, matrizMensual);
}
public Map<String, BigDecimal> conceptosMensuales(List<RentaQuinta> boletas) {
Map<String, BigDecimal> conceptos = new LinkedHashMap<>();
conceptos.put("basico", sumar(boletas, RentaQuinta::getBasico));
conceptos.put("asignacionFamiliar", sumar(boletas, RentaQuinta::getAsignacionFamiliar));
conceptos.put("movilidad", sumar(boletas, RentaQuinta::getMovilidad));
conceptos.put("vacacionesGozadas", sumar(boletas, RentaQuinta::getVacacionesGozadasRemVaca));
conceptos.put("licencias", sumar(boletas, RentaQuinta::getLicenciaGoceHaber, RentaQuinta::getLicenciaSaludFamiliarLey30012));
conceptos.put("horasExtras", sumar(boletas, RentaQuinta::getHorasExtras25, RentaQuinta::getHorasExtras35));
conceptos.put("dominical", sumar(boletas, RentaQuinta::getDominical));
conceptos.put("bonos", sumar(boletas, RentaQuinta::getBonoExtraordinario, RentaQuinta::getBonoProductividad,
RentaQuinta::getBonoDesempeno, RentaQuinta::getBonoAniversario));
conceptos.put("apoyoAsignaciones", sumar(boletas, RentaQuinta::getApoyoSalud, RentaQuinta::getAsignacionEstudios,
RentaQuinta::getAsignacionFallecimientoFamiliar));
conceptos.put("reintegro", sumar(boletas, RentaQuinta::getReintegro));
conceptos.put("canastaObsequiosVales", sumar(boletas, RentaQuinta::getCanastaNavidad, RentaQuinta::getValeConsumo,
RentaQuinta::getObsequios, RentaQuinta::getCanastaNavidad2, RentaQuinta::getValeConsumoDescuento));
conceptos.put("compraVacaciones", sumar(boletas, RentaQuinta::getCompensacionVacacional));
conceptos.put("vacacionesTruncas", sumar(boletas, RentaQuinta::getVacacionesTruncas));
conceptos.put("gratificacionesTruncas", sumar(boletas, RentaQuinta::getGratificacionProporcional));
conceptos.put("bonificacionTrunca", sumar(boletas, RentaQuinta::getBonificacionExtraordinaria));
conceptos.put("sumaGraciosa", sumar(boletas, RentaQuinta::getSumaGraciosa));
conceptos.put("gratificacion", sumar(boletas, RentaQuinta::getGratificacionJulioDiciembre, RentaQuinta::getBonificacionExtraordinaria2));
conceptos.put("utilidades", sumar(boletas, RentaQuinta::getUtilidades));
return conceptos;
}
@SafeVarargs
private final BigDecimal sumar(List<RentaQuinta> boletas, Function<RentaQuinta, BigDecimal>... campos) {
BigDecimal total = BigDecimal.ZERO;
for (RentaQuinta boleta : boletas) {
for (Function<RentaQuinta, BigDecimal> campo : campos) total = total.add(orZero(campo.apply(boleta)));
}
return total;
}
/**
......@@ -286,7 +334,7 @@ public class RentaQuintaMapper {
e.getCodper(),
e.getCodlocal(),
e.getUnidadOrganizativa(),
e.getTipoDocumento(),
e.getTipoPlanilla(),
e.getApellidoPaterno(),
e.getApellidoMaterno(),
e.getNombres(),
......
package so.planilla_be.rentaquinta.repository;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/** Consulta FDW en lote: las unidades organizativas de pl_planilla vienen vacías. */
@Repository
public class OrganizacionPersonalRepository {
private final NamedParameterJdbcTemplate jdbc;
public OrganizacionPersonalRepository(NamedParameterJdbcTemplate jdbc) {
this.jdbc = jdbc;
}
public Map<String, Organizacion> buscar(List<String> documentos) {
if (documentos.isEmpty()) return Map.of();
return jdbc.query("""
select distinct on (trim(p.numero_documento))
trim(p.numero_documento) as documento, p.id_unidad_organizativa,
p.codarea, a.desarea, p.id_subarea, s.nombre, p.codcargo, c.descargo
from nuevo.personal p
left join nuevo.area a on a.codarea = p.codarea
left join nuevo.subarea s on s.id_subarea = p.id_subarea
left join nuevo.cargo c on c.codcargo = p.codcargo
where trim(p.numero_documento) in (:documentos)
order by trim(p.numero_documento), p.codper desc
""", Map.of("documentos", documentos.stream().map(String::trim).distinct().toList()),
(rs, row) -> new Organizacion(rs.getString("documento"),
rs.getObject("id_unidad_organizativa", Integer.class),
rs.getObject("codarea", Integer.class), rs.getString("desarea"),
rs.getObject("id_subarea", Integer.class), rs.getString("nombre"),
rs.getObject("codcargo", Integer.class), rs.getString("descargo")))
.stream().collect(Collectors.toMap(Organizacion::documento, Function.identity()));
}
public record Organizacion(String documento, Integer idUnidadOrganizativa,
Integer idArea, String area, Integer idSubArea, String subArea,
Integer idCargo, String cargo) {}
}
......@@ -9,6 +9,18 @@ import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface RentaQuintaGrabadaRepository extends JpaRepository<RentaQuintaGrabada, RentaQuintaGrabadaId> {
@Query("""
select r from RentaQuintaGrabada r
where r.anio = :anio and r.numeroDocumento = :numeroDocumento
and trim(r.boletaTipoPersonal) = :tipoPersonal
and coalesce(trim(r.tipoPago), '') = :tipoPago
""")
List<RentaQuintaGrabada> retencionesAnuales(
@Param("anio") Integer anio,
@Param("numeroDocumento") String numeroDocumento,
@Param("tipoPersonal") String tipoPersonal,
@Param("tipoPago") String tipoPago);
@Query("select r from RentaQuintaGrabada r where r.anio = :anio and r.mes = :mes and r.numeroDocumento in :numerosDocumento")
List<RentaQuintaGrabada> findByAnioAndMesAndNumeroDocumentoIn(
@Param("anio") Integer anio,
......
......@@ -18,14 +18,22 @@ public interface RentaQuintaRepository extends JpaRepository<RentaQuinta, Long>
*/
String FILTROS = """
and (:sede is null or trim(r.codlocal) = :sede)
and (:tipoPlanilla is null or trim(r.boletaTipoPersonal) = :tipoPlanilla)
and (:tipoPago is null or coalesce(trim(r.tipoPago), '') = :tipoPago)
and (:areaGeneral is null or r.areaGeneral = :areaGeneral)
and (:subArea is null or r.subArea = :subArea)
and (:cargo is null or r.cargo = :cargo)
and (:tipoPlanilla is null or trim(r.boleta_tipo_personal) = :tipoPlanilla)
and (:tipoPago is null or coalesce(trim(r.tipo_pago), '') = :tipoPago)
and ((:areaGeneral is null and :subArea is null and :cargo is null) or trim(r.numero_documento) in (
select p.documento from (
select distinct on (trim(numero_documento)) trim(numero_documento) as documento,
codarea, id_subarea, codcargo
from nuevo.personal
order by trim(numero_documento), codper desc
) p
where (:areaGeneral is null or cast(p.codarea as text) = :areaGeneral)
and (:subArea is null or cast(p.id_subarea as text) = :subArea)
and (:cargo is null or cast(p.codcargo as text) = :cargo)
))
and (:texto is null
or upper(r.numeroDocumento) like :texto
or upper(concat(r.apellidoPaterno, ' ', r.apellidoMaterno, ' ', r.nombres)) like :texto)
or upper(r.numero_documento) like :texto
or upper(concat(r.apellido_paterno, ' ', r.apellido_materno, ' ', r.nombres)) like :texto)
""";
/** Meses de un trabajador en un año, ordenados cronológicamente (historial anual). */
......@@ -37,27 +45,28 @@ public interface RentaQuintaRepository extends JpaRepository<RentaQuinta, Long>
Integer codper, Integer boletaAnio, Integer boletaMes);
/**
* Documentos que matchean los filtros de la grilla, paginados y ordenados por apellidos +
* Documentos únicos que matchean los filtros de la grilla, paginados y ordenados por apellidos +
* nombres. Solo trae la clave (numeroDocumento) — el service arma la fila agregando en Java
* sobre {@link #historialHastaMes}, no en SQL.
*/
@Query(value = """
select distinct r.numeroDocumento as numeroDocumento,
r.apellidoPaterno as apellidoPaterno,
r.apellidoMaterno as apellidoMaterno,
r.nombres as nombres
from RentaQuinta r
where r.boletaAnio = :periodo
and r.boletaMes <= :mesCorte
select r.numero_documento as "numeroDocumento",
min(r.apellido_paterno) as "apellidoPaterno",
min(r.apellido_materno) as "apellidoMaterno",
min(r.nombres) as nombres
from planilla.pl_planilla r
where r.boleta_anio = :periodo
and r.boleta_mes <= :mesCorte
""" + FILTROS + """
order by r.apellidoPaterno asc, r.apellidoMaterno asc, r.nombres asc
group by r.numero_documento
order by "apellidoPaterno" asc, "apellidoMaterno" asc, nombres asc, r.numero_documento asc
""",
countQuery = """
select count(distinct r.numeroDocumento)
from RentaQuinta r
where r.boletaAnio = :periodo
and r.boletaMes <= :mesCorte
""" + FILTROS)
select count(distinct r.numero_documento)
from planilla.pl_planilla r
where r.boleta_anio = :periodo
and r.boleta_mes <= :mesCorte
""" + FILTROS, nativeQuery = true)
Page<DocumentoResumenProjection> buscarDocumentos(
@Param("periodo") Integer periodo,
@Param("mesCorte") Integer mesCorte,
......
......@@ -2,10 +2,12 @@ package so.planilla_be.rentaquinta.service;
import so.planilla_be.rentaquinta.dto.FiltroItemDTO;
import so.planilla_be.rentaquinta.dto.TipoFiltro;
import so.planilla_be.rentaquinta.dto.PeriodoFiltroDTO;
import java.util.List;
public interface FiltrosService {
List<FiltroItemDTO> listar(TipoFiltro tipo);
List<PeriodoFiltroDTO> listarPeriodos();
}
......@@ -4,6 +4,7 @@ import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import so.planilla_be.rentaquinta.dto.FiltroItemDTO;
import so.planilla_be.rentaquinta.dto.TipoFiltro;
import so.planilla_be.rentaquinta.dto.PeriodoFiltroDTO;
import so.planilla_be.rentaquinta.service.FiltrosService;
import java.util.List;
......@@ -23,6 +24,12 @@ public class FiltrosServiceImpl implements FiltrosService {
}
@Override
public List<PeriodoFiltroDTO> listarPeriodos() {
return jdbcTemplate.query("select id_periodo, mes, anio from planilla.pl_periodo order by anio desc, mes desc",
(rs, rowNum) -> new PeriodoFiltroDTO(rs.getInt("id_periodo"), rs.getInt("mes"), rs.getInt("anio")));
}
@Override
public List<FiltroItemDTO> listar(TipoFiltro tipo) {
String sql = switch (tipo) {
case AREA -> "select codarea as id, desarea as text from nuevo.area order by desarea";
......
......@@ -7,6 +7,8 @@ spring:
password: ${DB_PASSWORD}
hikari:
maximum-pool-size: 10
data-source-properties:
reWriteBatchedInserts: true
jpa:
hibernate:
ddl-auto: validate # nunca 'update'/'create' sobre esta tabla
......@@ -15,6 +17,10 @@ spring:
hibernate:
default_schema: planilla
format_sql: true
jdbc:
batch_size: 200 # grabado masivo: inserts/updates de pl_renta_quinta en lote
order_inserts: true
order_updates: true
hbm2ddl:
# nuevo.personal es una foreign table (postgres_fdw): el driver la reporta como
# TABLE_TYPE "FOREIGN TABLE", no "TABLE", y sin esto ddl-auto:validate la da como
......
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