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 ...@@ -11,4 +11,7 @@ public interface RetencionOtrosEmpleadoresRepository
List<RetencionOtrosEmpleadores> findByDocumentoTrabajadorAndAnioOrderByMes( List<RetencionOtrosEmpleadores> findByDocumentoTrabajadorAndAnioOrderByMes(
String documentoTrabajador, Integer anio); 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 ...@@ -15,6 +15,10 @@ public interface ProyeccionDocenteHoraRepository extends JpaRepository<Proyeccio
List<ProyeccionDocenteHora> findByNumeroDocumentoAndProyeccionAnioOrderByProyeccionMesAscBoletaTipoPersonalAsc( List<ProyeccionDocenteHora> findByNumeroDocumentoAndProyeccionAnioOrderByProyeccionMesAscBoletaTipoPersonalAsc(
String numeroDocumento, Integer proyeccionAnio); 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). */ /** Clave lógica (la tabla solo tiene PK sobre id_proyeccion). */
Optional<ProyeccionDocenteHora> findFirstByNumeroDocumentoAndBoletaTipoPersonalAndProyeccionAnioAndProyeccionMes( Optional<ProyeccionDocenteHora> findFirstByNumeroDocumentoAndBoletaTipoPersonalAndProyeccionAnioAndProyeccionMes(
String numeroDocumento, String boletaTipoPersonal, Integer proyeccionAnio, Integer proyeccionMes); String numeroDocumento, String boletaTipoPersonal, Integer proyeccionAnio, Integer proyeccionMes);
......
...@@ -172,6 +172,16 @@ public class RentaQuintaCalculadora { ...@@ -172,6 +172,16 @@ public class RentaQuintaCalculadora {
return total; 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 * 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: * de diciembre (julio-diciembre) según cuánto del semestre cubre el contrato:
......
...@@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.RequestParam; ...@@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import so.planilla_be.rentaquinta.dto.FiltroItemDTO; import so.planilla_be.rentaquinta.dto.FiltroItemDTO;
import so.planilla_be.rentaquinta.dto.TipoFiltro; import so.planilla_be.rentaquinta.dto.TipoFiltro;
import so.planilla_be.rentaquinta.dto.PeriodoFiltroDTO;
import so.planilla_be.rentaquinta.service.FiltrosService; import so.planilla_be.rentaquinta.service.FiltrosService;
import java.util.List; import java.util.List;
...@@ -28,4 +29,10 @@ public class FiltrosController { ...@@ -28,4 +29,10 @@ public class FiltrosController {
public List<FiltroItemDTO> listar(@RequestParam TipoFiltro tipo) { public List<FiltroItemDTO> listar(@RequestParam TipoFiltro tipo) {
return service.listar(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 { ...@@ -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. */ /** 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 " 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 " + "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."; + "toma de la boleta del trabajador.";
private static final String TIPO_PLANILLA_GRABAR_DOC = "Obligatorio. ADM, DOC1 (costo por hora) o DOC2 " 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; private final RentaQuintaService service;
...@@ -58,13 +59,15 @@ public class RentaQuintaController { ...@@ -58,13 +59,15 @@ public class RentaQuintaController {
@RequestParam(required = false) Integer areaGeneral, @RequestParam(required = false) Integer areaGeneral,
@RequestParam(required = false) Integer subArea, @RequestParam(required = false) Integer subArea,
@RequestParam(required = false) Integer cargo, @RequestParam(required = false) Integer cargo,
@RequestParam(required = false) Boolean tieneVariacion,
@RequestParam(required = false) Boolean tieneRetencion,
@RequestParam(required = false) String documentoOApellidos, @RequestParam(required = false) String documentoOApellidos,
@RequestParam(defaultValue = "0") Integer page, @RequestParam(defaultValue = "0") Integer page,
@RequestParam(defaultValue = "20") Integer size) { @RequestParam(defaultValue = "20") Integer size) {
TipoPlanilla tipo = TipoPlanilla.desdeParametro(tipoPlanilla); TipoPlanilla tipo = TipoPlanilla.desdeParametro(tipoPlanilla);
return service.buscar(new RentaQuintaFiltroDTO( return service.buscar(new RentaQuintaFiltroDTO(
anio, mes, sede, tipo, TipoPago.desdeParametro(tipoPlanilla), areaGeneral, subArea, cargo, 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") @Operation(summary = "Detalle completo por id_renta_quinta")
...@@ -74,8 +77,8 @@ public class RentaQuintaController { ...@@ -74,8 +77,8 @@ public class RentaQuintaController {
} }
@Operation(summary = "Detalle completo del cálculo por número de documento + año + mes " @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, " + "(boleta del mes + puntos 1 a 6: historial de ingresos, parámetros SUNAT, "
+ "liquidación anual, escala por tramos y control de retenciones)") + "liquidación anual, escala por tramos, control de retenciones y matriz mensual)")
@GetMapping("/detallado") @GetMapping("/detallado")
public RentaQuintaDetalleCompletoDTO detallado( public RentaQuintaDetalleCompletoDTO detallado(
@RequestParam String numeroDocumento, @RequestParam String numeroDocumento,
...@@ -127,11 +130,13 @@ public class RentaQuintaController { ...@@ -127,11 +130,13 @@ public class RentaQuintaController {
@RequestParam(required = false) Integer areaGeneral, @RequestParam(required = false) Integer areaGeneral,
@RequestParam(required = false) Integer subArea, @RequestParam(required = false) Integer subArea,
@RequestParam(required = false) Integer cargo, @RequestParam(required = false) Integer cargo,
@RequestParam(required = false) Boolean tieneVariacion,
@RequestParam(required = false) Boolean tieneRetencion,
@RequestParam(required = false) String documentoOApellidos) { @RequestParam(required = false) String documentoOApellidos) {
TipoPlanilla tipo = TipoPlanilla.obligatorio(tipoPlanilla); TipoPlanilla tipo = TipoPlanilla.obligatorio(tipoPlanilla);
int grabados = service.grabarMasivo(new RentaQuintaFiltroDTO( int grabados = service.grabarMasivo(new RentaQuintaFiltroDTO(
anio, mes, sede, tipo, TipoPago.desdeParametro(tipoPlanilla), areaGeneral, subArea, cargo, anio, mes, sede, tipo, TipoPago.desdeParametro(tipoPlanilla), areaGeneral, subArea, cargo,
documentoOApellidos, null, null)); tieneVariacion, tieneRetencion, documentoOApellidos, null, null));
return new GrabadoMasivoResultado(grabados); 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; ...@@ -2,12 +2,13 @@ package so.planilla_be.rentaquinta.dto;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.List; 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 * 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 * 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( public record RentaQuintaDetalleCompletoDTO(
RentaQuintaDetalleDTO boleta, RentaQuintaDetalleDTO boleta,
...@@ -15,9 +16,14 @@ public record RentaQuintaDetalleCompletoDTO( ...@@ -15,9 +16,14 @@ public record RentaQuintaDetalleCompletoDTO(
ParametrosSistemaDTO parametrosSistema, ParametrosSistemaDTO parametrosSistema,
LiquidacionImpuestoAnualDTO liquidacionImpuestoAnual, LiquidacionImpuestoAnualDTO liquidacionImpuestoAnual,
EscalaImpositivaDTO escalaImpositiva, 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. */ /** Punto 1: registro e historial de ingresos consolidados, en 3 columnas de periodo. */
public record RegistroHistorialIngresosDTO( public record RegistroHistorialIngresosDTO(
BloqueIngresosDTO pasadoRealRealizado, BloqueIngresosDTO pasadoRealRealizado,
......
...@@ -10,9 +10,8 @@ import so.planilla_be.rentaquinta.calculo.TipoPlanilla; ...@@ -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 * {@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, * 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 * 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 * llegan como Integer. Área, subárea y cargo se resuelven desde nuevo.personal y sus catálogos
* el número a su * foráneos. Los estados de variación y retención se evalúan sobre el cálculo antes de paginar.
* representación en texto.
*/ */
public record RentaQuintaFiltroDTO( public record RentaQuintaFiltroDTO(
Integer periodo, Integer periodo,
...@@ -23,6 +22,8 @@ public record RentaQuintaFiltroDTO( ...@@ -23,6 +22,8 @@ public record RentaQuintaFiltroDTO(
Integer areaGeneral, Integer areaGeneral,
Integer subArea, Integer subArea,
Integer cargo, Integer cargo,
Boolean tieneVariacion,
Boolean tieneRetencion,
String documentoOApellidos, String documentoOApellidos,
Integer page, Integer page,
Integer size Integer size
......
...@@ -16,6 +16,13 @@ public record RentaQuintaResumenDTO( ...@@ -16,6 +16,13 @@ public record RentaQuintaResumenDTO(
String numeroDocumento, String numeroDocumento,
String nombreCompleto, String nombreCompleto,
LocalDate fechaInicioContrato, LocalDate fechaInicioContrato,
Integer idUnidadOrganizativa,
Integer idArea,
String area,
Integer idSubArea,
String subArea,
Integer idCargo,
String cargo,
Integer mesProceso, Integer mesProceso,
BigDecimal historialReal, BigDecimal historialReal,
BigDecimal planillaCargada, BigDecimal planillaCargada,
......
...@@ -46,8 +46,8 @@ public class RentaQuinta { ...@@ -46,8 +46,8 @@ public class RentaQuinta {
@Column(name = "unidad_organizativa") @Column(name = "unidad_organizativa")
private Integer unidadOrganizativa; private Integer unidadOrganizativa;
@Column(name = "tipo_documento", length = 2, columnDefinition = "bpchar") @Column(name = "tipo_planilla", length = 2, columnDefinition = "bpchar")
private String tipoDocumento; private String tipoPlanilla;
@Column(name = "apellido_paterno") @Column(name = "apellido_paterno")
private String apellidoPaterno; private String apellidoPaterno;
...@@ -378,10 +378,10 @@ public class RentaQuinta { ...@@ -378,10 +378,10 @@ public class RentaQuinta {
@Column(name = "faltas") @Column(name = "faltas")
private Integer faltas; private Integer faltas;
@Column(name = "pago", precision = 15, scale = 2) @Column(name = "neto_pagado", precision = 15, scale = 2)
private BigDecimal pago; private BigDecimal pago;
@Column(name = "pendiente", precision = 15, scale = 2) @Column(name = "diferencia", precision = 15, scale = 2)
private BigDecimal pendiente; private BigDecimal pendiente;
@Column(name = "tipo_labor") @Column(name = "tipo_labor")
......
...@@ -14,9 +14,13 @@ import so.planilla_be.rentaquinta.dto.RentaQuintaDetalleCompletoDTO.RegistroHist ...@@ -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.RentaQuintaDetalleCompletoDTO.TramoDTO;
import so.planilla_be.rentaquinta.dto.RentaQuintaResumenDTO; import so.planilla_be.rentaquinta.dto.RentaQuintaResumenDTO;
import so.planilla_be.rentaquinta.entity.RentaQuinta; import so.planilla_be.rentaquinta.entity.RentaQuinta;
import so.planilla_be.rentaquinta.repository.OrganizacionPersonalRepository.Organizacion;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.List; import java.util.List;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
@Component @Component
...@@ -24,13 +28,21 @@ public class RentaQuintaMapper { ...@@ -24,13 +28,21 @@ public class RentaQuintaMapper {
public RentaQuintaResumenDTO toResumen(RentaQuinta referencia, Integer codper, Integer periodo, Integer mesCorte, public RentaQuintaResumenDTO toResumen(RentaQuinta referencia, Integer codper, Integer periodo, Integer mesCorte,
BigDecimal historialReal, BigDecimal planillaCargada, 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( return new RentaQuintaResumenDTO(
codper, codper,
periodo, periodo,
referencia.getNumeroDocumento(), referencia.getNumeroDocumento(),
nombreCompleto(referencia.getApellidoPaterno(), referencia.getApellidoMaterno(), referencia.getNombres()), nombreCompleto(referencia.getApellidoPaterno(), referencia.getApellidoMaterno(), referencia.getNombres()),
referencia.getFechaInicioContrato(), 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, mesCorte,
orZero(historialReal), orZero(historialReal),
orZero(planillaCargada), orZero(planillaCargada),
...@@ -76,11 +88,12 @@ public class RentaQuintaMapper { ...@@ -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 * de ingresos por columna de periodo, parámetros SUNAT, liquidación anual, escala por tramos
* y control de retenciones. * 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 // 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 // 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. // siempre queda consistente con lo que efectivamente se usó para el impuesto anual.
...@@ -165,7 +178,42 @@ public class RentaQuintaMapper { ...@@ -165,7 +178,42 @@ public class RentaQuintaMapper {
retencionOtrosEmpleadoresTotal, retencionOtrosEmpleadoresTotal,
saldoPendiente, calculo.divisorMes(), calculo.retencionDelMes()); 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 { ...@@ -286,7 +334,7 @@ public class RentaQuintaMapper {
e.getCodper(), e.getCodper(),
e.getCodlocal(), e.getCodlocal(),
e.getUnidadOrganizativa(), e.getUnidadOrganizativa(),
e.getTipoDocumento(), e.getTipoPlanilla(),
e.getApellidoPaterno(), e.getApellidoPaterno(),
e.getApellidoMaterno(), e.getApellidoMaterno(),
e.getNombres(), 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; ...@@ -9,6 +9,18 @@ import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
public interface RentaQuintaGrabadaRepository extends JpaRepository<RentaQuintaGrabada, RentaQuintaGrabadaId> { 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") @Query("select r from RentaQuintaGrabada r where r.anio = :anio and r.mes = :mes and r.numeroDocumento in :numerosDocumento")
List<RentaQuintaGrabada> findByAnioAndMesAndNumeroDocumentoIn( List<RentaQuintaGrabada> findByAnioAndMesAndNumeroDocumentoIn(
@Param("anio") Integer anio, @Param("anio") Integer anio,
......
...@@ -18,14 +18,22 @@ public interface RentaQuintaRepository extends JpaRepository<RentaQuinta, Long> ...@@ -18,14 +18,22 @@ public interface RentaQuintaRepository extends JpaRepository<RentaQuinta, Long>
*/ */
String FILTROS = """ String FILTROS = """
and (:sede is null or trim(r.codlocal) = :sede) and (:sede is null or trim(r.codlocal) = :sede)
and (:tipoPlanilla is null or trim(r.boletaTipoPersonal) = :tipoPlanilla) and (:tipoPlanilla is null or trim(r.boleta_tipo_personal) = :tipoPlanilla)
and (:tipoPago is null or coalesce(trim(r.tipoPago), '') = :tipoPago) and (:tipoPago is null or coalesce(trim(r.tipo_pago), '') = :tipoPago)
and (:areaGeneral is null or r.areaGeneral = :areaGeneral) and ((:areaGeneral is null and :subArea is null and :cargo is null) or trim(r.numero_documento) in (
and (:subArea is null or r.subArea = :subArea) select p.documento from (
and (:cargo is null or r.cargo = :cargo) 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 and (:texto is null
or upper(r.numeroDocumento) like :texto or upper(r.numero_documento) like :texto
or upper(concat(r.apellidoPaterno, ' ', r.apellidoMaterno, ' ', r.nombres)) 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). */ /** Meses de un trabajador en un año, ordenados cronológicamente (historial anual). */
...@@ -37,27 +45,28 @@ public interface RentaQuintaRepository extends JpaRepository<RentaQuinta, Long> ...@@ -37,27 +45,28 @@ public interface RentaQuintaRepository extends JpaRepository<RentaQuinta, Long>
Integer codper, Integer boletaAnio, Integer boletaMes); 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 * nombres. Solo trae la clave (numeroDocumento) — el service arma la fila agregando en Java
* sobre {@link #historialHastaMes}, no en SQL. * sobre {@link #historialHastaMes}, no en SQL.
*/ */
@Query(value = """ @Query(value = """
select distinct r.numeroDocumento as numeroDocumento, select r.numero_documento as "numeroDocumento",
r.apellidoPaterno as apellidoPaterno, min(r.apellido_paterno) as "apellidoPaterno",
r.apellidoMaterno as apellidoMaterno, min(r.apellido_materno) as "apellidoMaterno",
r.nombres as nombres min(r.nombres) as nombres
from RentaQuinta r from planilla.pl_planilla r
where r.boletaAnio = :periodo where r.boleta_anio = :periodo
and r.boletaMes <= :mesCorte and r.boleta_mes <= :mesCorte
""" + FILTROS + """ """ + 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 = """ countQuery = """
select count(distinct r.numeroDocumento) select count(distinct r.numero_documento)
from RentaQuinta r from planilla.pl_planilla r
where r.boletaAnio = :periodo where r.boleta_anio = :periodo
and r.boletaMes <= :mesCorte and r.boleta_mes <= :mesCorte
""" + FILTROS) """ + FILTROS, nativeQuery = true)
Page<DocumentoResumenProjection> buscarDocumentos( Page<DocumentoResumenProjection> buscarDocumentos(
@Param("periodo") Integer periodo, @Param("periodo") Integer periodo,
@Param("mesCorte") Integer mesCorte, @Param("mesCorte") Integer mesCorte,
......
...@@ -2,10 +2,12 @@ package so.planilla_be.rentaquinta.service; ...@@ -2,10 +2,12 @@ package so.planilla_be.rentaquinta.service;
import so.planilla_be.rentaquinta.dto.FiltroItemDTO; import so.planilla_be.rentaquinta.dto.FiltroItemDTO;
import so.planilla_be.rentaquinta.dto.TipoFiltro; import so.planilla_be.rentaquinta.dto.TipoFiltro;
import so.planilla_be.rentaquinta.dto.PeriodoFiltroDTO;
import java.util.List; import java.util.List;
public interface FiltrosService { public interface FiltrosService {
List<FiltroItemDTO> listar(TipoFiltro tipo); List<FiltroItemDTO> listar(TipoFiltro tipo);
List<PeriodoFiltroDTO> listarPeriodos();
} }
...@@ -4,6 +4,7 @@ import org.springframework.jdbc.core.JdbcTemplate; ...@@ -4,6 +4,7 @@ import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import so.planilla_be.rentaquinta.dto.FiltroItemDTO; import so.planilla_be.rentaquinta.dto.FiltroItemDTO;
import so.planilla_be.rentaquinta.dto.TipoFiltro; import so.planilla_be.rentaquinta.dto.TipoFiltro;
import so.planilla_be.rentaquinta.dto.PeriodoFiltroDTO;
import so.planilla_be.rentaquinta.service.FiltrosService; import so.planilla_be.rentaquinta.service.FiltrosService;
import java.util.List; import java.util.List;
...@@ -23,6 +24,12 @@ public class FiltrosServiceImpl implements FiltrosService { ...@@ -23,6 +24,12 @@ public class FiltrosServiceImpl implements FiltrosService {
} }
@Override @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) { public List<FiltroItemDTO> listar(TipoFiltro tipo) {
String sql = switch (tipo) { String sql = switch (tipo) {
case AREA -> "select codarea as id, desarea as text from nuevo.area order by desarea"; case AREA -> "select codarea as id, desarea as text from nuevo.area order by desarea";
......
...@@ -7,6 +7,8 @@ spring: ...@@ -7,6 +7,8 @@ spring:
password: ${DB_PASSWORD} password: ${DB_PASSWORD}
hikari: hikari:
maximum-pool-size: 10 maximum-pool-size: 10
data-source-properties:
reWriteBatchedInserts: true
jpa: jpa:
hibernate: hibernate:
ddl-auto: validate # nunca 'update'/'create' sobre esta tabla ddl-auto: validate # nunca 'update'/'create' sobre esta tabla
...@@ -15,6 +17,10 @@ spring: ...@@ -15,6 +17,10 @@ spring:
hibernate: hibernate:
default_schema: planilla default_schema: planilla
format_sql: true format_sql: true
jdbc:
batch_size: 200 # grabado masivo: inserts/updates de pl_renta_quinta en lote
order_inserts: true
order_updates: true
hbm2ddl: hbm2ddl:
# nuevo.personal es una foreign table (postgres_fdw): el driver la reporta como # 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 # 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