Usar pl_gratificacion para diciembre completo DOC2 y desactivar mantenimiento anterior

parent b7ffff5a
......@@ -153,12 +153,12 @@ que la duplicaba en la grilla.
- **DOC2**: igual que ADM (fecha de su boleta DOC), pero en la gratificación de diciembre,
diciembre cuenta 16 días: semestre completo = `((base/6 × 5) + (base/6/30 × 16)) × 1.09`. La tabla la crea
`docs/create-calendario.sql` (2026): correrlo antes de desplegar o `validate` no arranca.
Excepción: si el DNI está en `planilla.pl_docente_grati_diciembre_completa` para el año
(PK `dni + anio`), su gratificación de diciembre DOC2 va con diciembre completo (30 días,
igual que ADM). Solo la gratificación: el sueldo bruto de diciembre sigue ÷ 30 × 16. La crea
`docs/create-docente-grati-diciembre-completa.sql` (correr antes de desplegar). Mantenimiento
Excepción: si el documento tiene un registro de diciembre del año en `planilla.pl_gratificacion`
(filtrando por `mes = 12`, sin usar `monto_basico`), su gratificación de diciembre DOC2 va con diciembre completo (30 días,
igual que ADM). Solo la gratificación: el sueldo bruto de diciembre sigue ÷ 30 × 16. El mantenimiento
en `/api/v1/docente-grati-diciembre-completa` (paquete `docentegratidiciembre`, ver
`docs/api-docente-grati-diciembre-completa.md`).
`docs/api-docente-grati-diciembre-completa.md`) aún usa la tabla anterior y no determina la excepción;
su adaptación está pendiente de definición.
2. Renta bruta anual = historial + planilla cargada + proyección
3. Renta neta = bruta − **7 UIT**
4. Impuesto anual = tramos progresivos 8% / 14% / 17% / 20% / 30% (5, 20, 35 y 45 UIT)
......
......@@ -136,12 +136,12 @@ que la duplicaba en la grilla.
- **DOC2**: igual que ADM (fecha de su boleta DOC), pero en la gratificación de diciembre,
diciembre cuenta 16 días: semestre completo = `((base/6 × 5) + (base/6/30 × 16)) × 1.09`. La tabla la crea
`docs/create-calendario.sql` (2026): correrlo antes de desplegar o `validate` no arranca.
Excepción: si el DNI está en `planilla.pl_docente_grati_diciembre_completa` para el año
(PK `dni + anio`), su gratificación de diciembre DOC2 va con diciembre completo (30 días,
igual que ADM). Solo la gratificación: el sueldo bruto de diciembre sigue ÷ 30 × 16. La crea
`docs/create-docente-grati-diciembre-completa.sql` (correr antes de desplegar). Mantenimiento
Excepción: si el documento tiene un registro de diciembre del año en `planilla.pl_gratificacion`
(filtrando por `mes = 12`, sin usar `monto_basico`), su gratificación de diciembre DOC2 va con diciembre completo (30 días,
igual que ADM). Solo la gratificación: el sueldo bruto de diciembre sigue ÷ 30 × 16. El mantenimiento
en `/api/v1/docente-grati-diciembre-completa` (paquete `docentegratidiciembre`, ver
`docs/api-docente-grati-diciembre-completa.md`).
`docs/api-docente-grati-diciembre-completa.md`) aún usa la tabla anterior y no determina la excepción;
su adaptación está pendiente de definición.
2. Renta bruta anual = historial + planilla cargada + proyección
3. Renta neta = bruta − **7 UIT**
4. Impuesto anual = tramos progresivos 8% / 14% / 17% / 20% / 30% (5, 20, 35 y 45 UIT)
......
-- Tabla externa de MySQL. Las fechas se exponen como texto porque el origen admite 0000-00-00.
CREATE FOREIGN TABLE nuevo.periodo_asistencia (
codper_asistencia integer NOT NULL,
nombre_periodo varchar(100) NOT NULL,
mes smallint NOT NULL,
anio integer NOT NULL,
fecha_inicio text NOT NULL,
fecha_cierre text NOT NULL,
fecha_cierre_extras text,
fecha_migracion text,
fecha_devolucion_cts text,
cod_user integer NOT NULL,
migracion_mensual integer,
migracion_quincenal integer,
cod_user_quincenal integer
) SERVER srv_mysql_nuevo
OPTIONS (dbname 'nuevo', table_name 'periodo_asistencia');
# API de docentes con gratificación de diciembre completa (DOC2)
> Módulo desactivado temporalmente: código comentado y endpoints no publicados. La tabla ya no se valida al arrancar. El contrato siguiente se conserva como referencia.
Mantenimiento para **grabar** docentes en `planilla.pl_docente_grati_diciembre_completa`. Solo
escribe en esa tabla. La renta de quinta la lee para decidir cómo calcula la gratificación de
diciembre en la planilla **DOC2**.
# API de mantenimiento de docentes
Paquete: `so.planilla_be.docentegratidiciembre`.
Tabla: `planilla.pl_docente_grati_diciembre_completa`.
Clave primaria: `numero_documento` + `anio` + `mes`.
Estado obligatorio: `0` inactivo, `1` activo. Mes: 1–12; año: 2000–2100.
## Para qué sirve
En DOC2 la gratificación de diciembre se calcula con **diciembre a 16 días**: el semestre completo
es 5 meses (julio a noviembre) + 16 días. Si el DNI del trabajador está en esta tabla **para el año
consultado**, diciembre va **completo (30 días)** y la gratificación se calcula igual que ADM:
semestre completo = 6 meses.
| | No está en la tabla | Está en la tabla (mismo año) |
|---|---|---|
| Semestre completo | `((base/6 × 5) + (base/6/30 × 16)) × 1.09` | `(base/6 × 6) × 1.09` = `base × 1.09` |
| Ingreso en junio después del día 1 | 5 meses + días de junio + 16 días | 6 meses + días de junio |
| Ingreso en diciembre | 16 − día + 1 días | 30 días del mes − día + 1 (si entra el día 1, mes completo) |
- **Solo DOC2.** ADM y DOC1 no miran esta tabla.
- **Solo la gratificación.** El sueldo bruto de diciembre en DOC2 sigue entrando ÷ 30 × 16.
- **Por año.** Un DNI registrado en 2026 no aplica a 2027; hay que registrarlo en cada año.
- `seleccion` no interviene en el cálculo: basta que exista la fila DNI + año.
Ejemplo (DNI 73180595, ingreso 02/06/2026, base 3,852.18): sin registro la gratificación de
diciembre es **4,548.78**; registrado en 2026 es (3,852.18 + 29 × 21.401) × 1.09 = **4,875.36**.
El cálculo DOC2 continúa consultando registros de diciembre en `planilla.pl_gratificacion`.
Este mantenimiento no determina la excepción del cálculo.
## Endpoints
| Método | Ruta | Descripción |
|---|---|---|
| GET | `/api/v1/docente-grati-diciembre-completa/{anio}` | Docentes registrados en el año |
| POST | `/api/v1/docente-grati-diciembre-completa` | Graba una lista de docentes |
| DELETE | `/api/v1/docente-grati-diciembre-completa/{dni}/{anio}` | Elimina un docente del año |
Swagger: `/docs`, tag **Docente gratificación diciembre completa**.
- `GET /api/v1/docente-grati-diciembre-completa/{anio}`: lista todos los estados, ordenados por documento y mes.
- `POST /api/v1/docente-grati-diciembre-completa`: inserta o actualiza el estado por documento, año y mes. Lista obligatoria, operación transaccional.
- `DELETE /api/v1/docente-grati-diciembre-completa/{numeroDocumento}/{anio}/{mes}`: elimina por clave; 204 o 404 si no existe.
## Estructura de la tabla
| Columna | Tipo | Nulo | Notas |
|---|---|---|---|
| `dni` | varchar(15) | NO | PK (con `anio`) |
| `anio` | integer | NO | PK (con `dni`). Año de la planilla al que aplica |
| `apellido_paterno` | varchar(100) | SÍ | |
| `apellido_materno` | varchar(100) | SÍ | |
| `nombres` | varchar(150) | SÍ | |
| `seleccion` | integer | SÍ | Campo libre del mantenimiento |
La crea `docs/create-docente-grati-diciembre-completa.sql`. **Correrlo antes de desplegar**: con
`ddl-auto: validate` la app no arranca si falta la tabla.
## GET `/{anio}`
Llena la grilla de la pantalla. Ordena por apellido paterno, materno y nombres. Sin filas devuelve
`[]` (200).
## Cuerpo del POST y respuesta
```json
[
{
"numeroDocumento": "73180595",
"anio": 2026,
"dni": "73180595",
"apellidoPaterno": "ACEVEDO",
"apellidoMaterno": "CUITO",
"nombres": "MAURICIO JUNIO",
"seleccion": 1
"mes": 12,
"estado": 1
}
]
```
## POST
Body: arreglo JSON, no vacío.
Se recortan espacios del documento. Los campos anteriores `dni`, apellidos, nombres y `seleccion`
se sustituyen por el contrato anterior. El DELETE ahora requiere mes.
| Campo | Tipo | Obligatorio | Regla |
|---|---|---|---|
| `anio` | Integer | sí | 2000 a 2100 |
| `dni` | String | sí | no vacío, máx. 15 (se hace `trim`) |
| `apellidoPaterno` | String | no | máx. 100 |
| `apellidoMaterno` | String | no | máx. 100 |
| `nombres` | String | no | máx. 150 |
| `seleccion` | Integer | no | |
Apellidos y nombres se graban con `trim` y en **mayúsculas**; vacíos se graban como `null`.
```json
[
{
"anio": 2026,
"dni": "73180595",
"apellidoPaterno": "Acevedo",
"apellidoMaterno": "Cuito",
"nombres": "Mauricio Junio",
"seleccion": 1
}
]
```
### Comportamiento
- **Insertar o actualizar:** si ya existe la fila `dni + anio`, se actualizan apellidos, nombres y
`seleccion`. Si no existe, se inserta. No se duplica.
- **Todo o nada:** una sola transacción. Si una fila falla, no se graba ninguna.
- **Solo esta tabla:** no toca `pl_renta_quinta` ni ninguna otra. La retención ya grabada en
`pl_renta_quinta` no se recalcula sola: hay que volver a consultar/grabar la renta de quinta.
### Response `200`
Lista de las filas grabadas, en el mismo orden del request (mismo formato que el GET).
## DELETE `/{dni}/{anio}`
`204` sin cuerpo si se elimina; `404` si no existe la fila; `400` si `anio` no es numérico.
## Errores
Formato `{timestamp, status, message}`, igual que el resto del API.
| Status | Causa |
|---|---|
| 400 | Lista vacía, `dni` o `anio` ausente, `anio` fuera de 2000–2100, texto más largo que la columna, `anio` no numérico en la ruta |
| 404 | `DELETE` de un DNI + año que no existe |
## Ejemplo
```bash
curl -X POST http://localhost:8080/api/v1/docente-grati-diciembre-completa \
-H "Content-Type: application/json" \
-d '[{"anio":2026,"dni":"73180595","apellidoPaterno":"ACEVEDO","apellidoMaterno":"CUITO","nombres":"MAURICIO JUNIO","seleccion":1}]'
curl http://localhost:8080/api/v1/docente-grati-diciembre-completa/2026
curl -X DELETE http://localhost:8080/api/v1/docente-grati-diciembre-completa/73180595/2026
```
## Archivos
```
docentegratidiciembre/
├── controller/DocenteGratiDiciembreCompletaController
├── dto/DocenteGratiDiciembreCompletaRequestDTO, DocenteGratiDiciembreCompletaDTO
├── entity/DocenteGratiDiciembreCompleta + DocenteGratiDiciembreCompletaId PK dni + anio
├── exception/DocenteGratiDiciembreCompletaNoEncontradaException
├── repository/DocenteGratiDiciembreCompletaRepository existsByDniAndAnio (lo usa renta de quinta)
└── service/ + service/impl/
```
## DDL
En renta de quinta: `RentaQuintaServiceImpl#calcular` arma `gratificacionDiciembreCompleta` (DOC2 +
fila DNI + año) y `RentaQuintaCalculadora#gratificacionProporcional` deja de aplicar los 16 días.
Para una instalación nueva ejecutar `docs/create-docente-grati-diciembre-completa.sql` antes de
arrancar. Conservar `ddl-auto: validate`. El script de creación no migra una tabla anterior existente.
-- Docentes DOC2 (DOC + tipo_pago 2) cuya gratificación de diciembre del año se calcula con
-- diciembre completo (30 días) en vez de 16 días. Si el DNI está en esta tabla para el año, la
-- gratificación de diciembre de su planilla DOC2 se prorratea igual que ADM (semestre completo =
-- 6 meses). No cambia el sueldo bruto de diciembre (sigue ÷ 30 × 16) ni afecta a ADM ni a DOC1.
-- anio año de la planilla al que aplica (una fila por DNI + año)
-- seleccion campo numérico libre para el mantenimiento; el cálculo solo mira si existe la fila.
--
-- Ejecutar ANTES de desplegar el backend: con ddl-auto: validate, la app no arranca si falta la
-- tabla. Se graba desde POST /api/v1/docente-grati-diciembre-completa.
-- Mantenimiento mensual. El cálculo DOC2 consulta pl_gratificacion.
CREATE TABLE planilla.pl_docente_grati_diciembre_completa (
dni varchar(15) NOT NULL,
anio integer NOT NULL,
apellido_paterno varchar(100),
apellido_materno varchar(100),
nombres varchar(150),
seleccion integer,
PRIMARY KEY (dni, anio)
numero_documento varchar(15) NOT NULL,
anio integer NOT NULL,
mes integer NOT NULL,
estado integer NOT NULL,
PRIMARY KEY (numero_documento, anio, mes),
CONSTRAINT ck_docente_grati_mes CHECK (mes BETWEEN 1 AND 12),
CONSTRAINT ck_docente_grati_estado CHECK (estado IN (0, 1)),
CONSTRAINT ck_docente_grati_anio CHECK (anio BETWEEN 2000 AND 2100),
CONSTRAINT ck_docente_grati_documento CHECK (btrim(numero_documento) <> '')
);
COMMENT ON TABLE planilla.pl_docente_grati_diciembre_completa IS
'Docentes DOC2 con gratificación de diciembre a 30 días (no 16) por año. Lo usa el cálculo de renta de quinta.';
-- Ejemplo:
-- INSERT INTO planilla.pl_docente_grati_diciembre_completa (dni, anio, apellido_paterno, apellido_materno, nombres, seleccion)
-- VALUES ('73180595', 2026, 'ACEVEDO', 'CUITO', 'MAURICIO JUNIO', 1);
'Mantenimiento mensual de docentes: estado 0 inactivo, 1 activo.';
package so.planilla_be.docentegratidiciembre.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import so.planilla_be.docentegratidiciembre.dto.DocenteGratiDiciembreCompletaDTO;
import so.planilla_be.docentegratidiciembre.dto.DocenteGratiDiciembreCompletaRequestDTO;
import so.planilla_be.docentegratidiciembre.service.DocenteGratiDiciembreCompletaService;
import java.util.List;
@Tag(name = "Docente gratificación diciembre completa",
description = "Grabado de pl_docente_grati_diciembre_completa (DOC2 con diciembre a 30 días)")
@Validated
@RestController
@RequestMapping("/api/v1/docente-grati-diciembre-completa")
public class DocenteGratiDiciembreCompletaController {
private final DocenteGratiDiciembreCompletaService service;
public DocenteGratiDiciembreCompletaController(DocenteGratiDiciembreCompletaService service) {
this.service = service;
}
@Operation(summary = "Docentes registrados en el año")
@GetMapping("/{anio}")
public List<DocenteGratiDiciembreCompletaDTO> listarPorAnio(@PathVariable Integer anio) {
return service.listarPorAnio(anio);
}
@Operation(summary = "Grabar docentes (inserta o actualiza por DNI + año)")
@PostMapping
public List<DocenteGratiDiciembreCompletaDTO> grabar(
@RequestBody @NotEmpty List<@Valid DocenteGratiDiciembreCompletaRequestDTO> filas) {
return service.grabar(filas);
}
@Operation(summary = "Eliminar un docente del año")
@DeleteMapping("/{dni}/{anio}")
public ResponseEntity<Void> eliminar(@PathVariable String dni, @PathVariable Integer anio) {
service.eliminar(dni, anio);
return ResponseEntity.noContent().build();
}
}
// Módulo desactivado temporalmente: no usar pl_docente_grati_diciembre_completa.
// package so.planilla_be.docentegratidiciembre.controller;
//
// import io.swagger.v3.oas.annotations.Operation;
// import io.swagger.v3.oas.annotations.tags.Tag;
// import jakarta.validation.Valid;
// import jakarta.validation.constraints.Min;
// import jakarta.validation.constraints.Max;
// import jakarta.validation.constraints.Size;
// import jakarta.validation.constraints.NotEmpty;
// import org.springframework.http.ResponseEntity;
// import org.springframework.validation.annotation.Validated;
// import org.springframework.web.bind.annotation.DeleteMapping;
// import org.springframework.web.bind.annotation.GetMapping;
// import org.springframework.web.bind.annotation.PathVariable;
// import org.springframework.web.bind.annotation.PostMapping;
// import org.springframework.web.bind.annotation.RequestBody;
// import org.springframework.web.bind.annotation.RequestMapping;
// import org.springframework.web.bind.annotation.RestController;
// import so.planilla_be.docentegratidiciembre.dto.DocenteGratiDiciembreCompletaDTO;
// import so.planilla_be.docentegratidiciembre.dto.DocenteGratiDiciembreCompletaRequestDTO;
// import so.planilla_be.docentegratidiciembre.service.DocenteGratiDiciembreCompletaService;
//
// import java.util.List;
//
// @Tag(name = "Docente gratificación diciembre completa",
// description = "Grabado de pl_docente_grati_diciembre_completa (DOC2 con diciembre a 30 días)")
// @Validated
// @RestController
// @RequestMapping("/api/v1/docente-grati-diciembre-completa")
// public class DocenteGratiDiciembreCompletaController {
//
// private final DocenteGratiDiciembreCompletaService service;
//
// public DocenteGratiDiciembreCompletaController(DocenteGratiDiciembreCompletaService service) {
// this.service = service;
// }
//
// @Operation(summary = "Docentes registrados en el año")
// @GetMapping("/{anio}")
// public List<DocenteGratiDiciembreCompletaDTO> listarPorAnio(@PathVariable @Min(2000) @Max(2100) Integer anio) {
// return service.listarPorAnio(anio);
// }
//
// @Operation(summary = "Grabar docentes (inserta o actualiza por documento + año + mes)")
// @PostMapping
// public List<DocenteGratiDiciembreCompletaDTO> grabar(
// @RequestBody @NotEmpty List<@Valid DocenteGratiDiciembreCompletaRequestDTO> filas) {
// return service.grabar(filas);
// }
//
// @Operation(summary = "Eliminar un registro por documento, año y mes")
// @DeleteMapping("/{numeroDocumento}/{anio}/{mes}")
// public ResponseEntity<Void> eliminar(
// @PathVariable @Size(min = 1, max = 15) String numeroDocumento,
// @PathVariable @Min(2000) @Max(2100) Integer anio,
// @PathVariable @Min(1) @Max(12) Integer mes) {
// service.eliminar(numeroDocumento, anio, mes);
// return ResponseEntity.noContent().build();
// }
// }
package so.planilla_be.docentegratidiciembre.dto;
public record DocenteGratiDiciembreCompletaDTO(
Integer anio,
String dni,
String apellidoPaterno,
String apellidoMaterno,
String nombres,
Integer seleccion
) {}
// Módulo desactivado temporalmente: no usar pl_docente_grati_diciembre_completa.
// package so.planilla_be.docentegratidiciembre.dto;
// public record DocenteGratiDiciembreCompletaDTO(String numeroDocumento, Integer anio, Integer mes, Integer estado) {}
package so.planilla_be.docentegratidiciembre.dto;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
/**
* Una fila de pl_docente_grati_diciembre_completa. La clave es dni + anio: si ya existe se
* actualizan apellidos, nombres y seleccion.
*/
public record DocenteGratiDiciembreCompletaRequestDTO(
@NotNull @Min(2000) @Max(2100) Integer anio,
@NotBlank @Size(max = 15) String dni,
@Size(max = 100) String apellidoPaterno,
@Size(max = 100) String apellidoMaterno,
@Size(max = 150) String nombres,
Integer seleccion
) {}
// Módulo desactivado temporalmente: no usar pl_docente_grati_diciembre_completa.
// package so.planilla_be.docentegratidiciembre.dto;
// import jakarta.validation.constraints.*;
// /** Registro por documento, año y mes; estado 0 inactivo, 1 activo. */
// public record DocenteGratiDiciembreCompletaRequestDTO(
// @NotBlank @Size(max = 15) String numeroDocumento,
// @NotNull @Min(2000) @Max(2100) Integer anio,
// @NotNull @Min(1) @Max(12) Integer mes,
// @NotNull @Min(0) @Max(1) Integer estado
// ) {}
package so.planilla_be.docentegratidiciembre.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;
/**
* Docente DOC2 cuya gratificación de diciembre del año va con diciembre completo (30 días) y no a
* 16 días (planilla.pl_docente_grati_diciembre_completa, una fila por DNI + año). La crea
* docs/create-docente-grati-diciembre-completa.sql; se graba desde el mantenimiento de este módulo
* y la renta de quinta solo la lee.
*/
@Getter
@Setter
@Entity
@Table(name = "pl_docente_grati_diciembre_completa")
@IdClass(DocenteGratiDiciembreCompletaId.class)
public class DocenteGratiDiciembreCompleta {
@Id
@Column(name = "dni")
private String dni;
@Id
@Column(name = "anio")
private Integer anio;
@Column(name = "apellido_paterno")
private String apellidoPaterno;
@Column(name = "apellido_materno")
private String apellidoMaterno;
@Column(name = "nombres")
private String nombres;
/** Campo libre del mantenimiento; el cálculo solo mira si existe la fila DNI + año. */
@Column(name = "seleccion")
private Integer seleccion;
}
// Módulo desactivado temporalmente: no usar pl_docente_grati_diciembre_completa.
// package so.planilla_be.docentegratidiciembre.entity;
// import jakarta.persistence.*;
// import lombok.Getter;
// import lombok.Setter;
// /** Registro mensual del mantenimiento. */
// @Getter
// @Setter
// @Entity
// @Table(name = "pl_docente_grati_diciembre_completa", schema = "planilla")
// @IdClass(DocenteGratiDiciembreCompletaId.class)
// public class DocenteGratiDiciembreCompleta {
// @Id @Column(name = "numero_documento", length = 15, nullable = false)
// private String numeroDocumento;
// @Id @Column(name = "anio", nullable = false)
// private Integer anio;
// @Id @Column(name = "mes", nullable = false)
// private Integer mes;
// @Column(name = "estado", nullable = false)
// private Integer estado;
// }
package so.planilla_be.docentegratidiciembre.entity;
import java.io.Serializable;
public record DocenteGratiDiciembreCompletaId(String dni, Integer anio) implements Serializable {}
// Módulo desactivado temporalmente: no usar pl_docente_grati_diciembre_completa.
// package so.planilla_be.docentegratidiciembre.entity;
// import java.io.Serializable;
// public record DocenteGratiDiciembreCompletaId(String numeroDocumento, Integer anio, Integer mes) implements Serializable {}
package so.planilla_be.docentegratidiciembre.exception;
public class DocenteGratiDiciembreCompletaNoEncontradaException extends RuntimeException {
public DocenteGratiDiciembreCompletaNoEncontradaException(String message) {
super(message);
}
}
// Módulo desactivado temporalmente: no usar pl_docente_grati_diciembre_completa.
// package so.planilla_be.docentegratidiciembre.exception;
//
// public class DocenteGratiDiciembreCompletaNoEncontradaException extends RuntimeException {
//
// public DocenteGratiDiciembreCompletaNoEncontradaException(String message) {
// super(message);
// }
// }
package so.planilla_be.docentegratidiciembre.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import so.planilla_be.docentegratidiciembre.entity.DocenteGratiDiciembreCompleta;
import so.planilla_be.docentegratidiciembre.entity.DocenteGratiDiciembreCompletaId;
import java.util.List;
public interface DocenteGratiDiciembreCompletaRepository
extends JpaRepository<DocenteGratiDiciembreCompleta, DocenteGratiDiciembreCompletaId> {
/** Docentes registrados en el año, por apellidos y nombres. */
List<DocenteGratiDiciembreCompleta> findByAnioOrderByApellidoPaternoAscApellidoMaternoAscNombresAsc(Integer anio);
/** Renta de quinta DOC2: true si el DNI tiene diciembre completo (30 días) en el año. */
boolean existsByDniAndAnio(String dni, Integer anio);
}
// Módulo desactivado temporalmente: no usar pl_docente_grati_diciembre_completa.
// package so.planilla_be.docentegratidiciembre.repository;
//
// import org.springframework.data.jpa.repository.JpaRepository;
// import so.planilla_be.docentegratidiciembre.entity.DocenteGratiDiciembreCompleta;
// import so.planilla_be.docentegratidiciembre.entity.DocenteGratiDiciembreCompletaId;
//
// import java.util.List;
//
// public interface DocenteGratiDiciembreCompletaRepository
// extends JpaRepository<DocenteGratiDiciembreCompleta, DocenteGratiDiciembreCompletaId> {
//
// /** Registros del año ordenados por documento y mes. */
// List<DocenteGratiDiciembreCompleta> findByAnioOrderByNumeroDocumentoAscMesAsc(Integer anio);
//
//
// }
package so.planilla_be.docentegratidiciembre.service;
import so.planilla_be.docentegratidiciembre.dto.DocenteGratiDiciembreCompletaDTO;
import so.planilla_be.docentegratidiciembre.dto.DocenteGratiDiciembreCompletaRequestDTO;
import java.util.List;
public interface DocenteGratiDiciembreCompletaService {
/** Docentes registrados en el año. Sin filas: lista vacía. */
List<DocenteGratiDiciembreCompletaDTO> listarPorAnio(Integer anio);
/**
* Graba las filas recibidas. La clave es dni + anio: si ya existe se actualizan los datos, si
* no se inserta. Todo o nada.
*/
List<DocenteGratiDiciembreCompletaDTO> grabar(List<DocenteGratiDiciembreCompletaRequestDTO> filas);
/** Elimina la fila dni + anio. Lanza DocenteGratiDiciembreCompletaNoEncontradaException si no existe. */
void eliminar(String dni, Integer anio);
}
// Módulo desactivado temporalmente: no usar pl_docente_grati_diciembre_completa.
// package so.planilla_be.docentegratidiciembre.service;
//
// import so.planilla_be.docentegratidiciembre.dto.DocenteGratiDiciembreCompletaDTO;
// import so.planilla_be.docentegratidiciembre.dto.DocenteGratiDiciembreCompletaRequestDTO;
//
// import java.util.List;
//
// public interface DocenteGratiDiciembreCompletaService {
//
// /** Docentes registrados en el año. Sin filas: lista vacía. */
// List<DocenteGratiDiciembreCompletaDTO> listarPorAnio(Integer anio);
//
// /**
// * Graba las filas recibidas. La clave es numeroDocumento + anio + mes: si ya existe se actualiza el estado, si
// * no se inserta. Todo o nada.
// */
// List<DocenteGratiDiciembreCompletaDTO> grabar(List<DocenteGratiDiciembreCompletaRequestDTO> filas);
//
// /** Elimina la fila numeroDocumento + anio + mes. Lanza DocenteGratiDiciembreCompletaNoEncontradaException si no existe. */
// void eliminar(String numeroDocumento, Integer anio, Integer mes);
// }
package so.planilla_be.docentegratidiciembre.service.impl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import so.planilla_be.docentegratidiciembre.dto.DocenteGratiDiciembreCompletaDTO;
import so.planilla_be.docentegratidiciembre.dto.DocenteGratiDiciembreCompletaRequestDTO;
import so.planilla_be.docentegratidiciembre.entity.DocenteGratiDiciembreCompleta;
import so.planilla_be.docentegratidiciembre.entity.DocenteGratiDiciembreCompletaId;
import so.planilla_be.docentegratidiciembre.exception.DocenteGratiDiciembreCompletaNoEncontradaException;
import so.planilla_be.docentegratidiciembre.repository.DocenteGratiDiciembreCompletaRepository;
import so.planilla_be.docentegratidiciembre.service.DocenteGratiDiciembreCompletaService;
import java.util.List;
@Service
public class DocenteGratiDiciembreCompletaServiceImpl implements DocenteGratiDiciembreCompletaService {
private final DocenteGratiDiciembreCompletaRepository repository;
public DocenteGratiDiciembreCompletaServiceImpl(DocenteGratiDiciembreCompletaRepository repository) {
this.repository = repository;
}
@Override
@Transactional(readOnly = true)
public List<DocenteGratiDiciembreCompletaDTO> listarPorAnio(Integer anio) {
return repository.findByAnioOrderByApellidoPaternoAscApellidoMaternoAscNombresAsc(anio).stream()
.map(this::toDto)
.toList();
}
@Override
@Transactional
public List<DocenteGratiDiciembreCompletaDTO> grabar(List<DocenteGratiDiciembreCompletaRequestDTO> filas) {
return filas.stream()
.map(this::grabarFila)
.toList();
}
@Override
@Transactional
public void eliminar(String dni, Integer anio) {
DocenteGratiDiciembreCompleta entidad = repository
.findById(new DocenteGratiDiciembreCompletaId(dni.trim(), anio))
.orElseThrow(() -> new DocenteGratiDiciembreCompletaNoEncontradaException(
"No existe docente con gratificación de diciembre completa para DNI %s, año %d"
.formatted(dni.trim(), anio)));
repository.delete(entidad);
}
private DocenteGratiDiciembreCompletaDTO grabarFila(DocenteGratiDiciembreCompletaRequestDTO fila) {
String dni = fila.dni().trim();
DocenteGratiDiciembreCompleta entidad = repository
.findById(new DocenteGratiDiciembreCompletaId(dni, fila.anio()))
.orElseGet(DocenteGratiDiciembreCompleta::new);
entidad.setDni(dni);
entidad.setAnio(fila.anio());
entidad.setApellidoPaterno(texto(fila.apellidoPaterno()));
entidad.setApellidoMaterno(texto(fila.apellidoMaterno()));
entidad.setNombres(texto(fila.nombres()));
entidad.setSeleccion(fila.seleccion());
return toDto(repository.save(entidad));
}
/** Apellidos y nombres en mayúsculas y sin espacios sobrantes; vacío se graba como null. */
private String texto(String valor) {
if (valor == null || valor.isBlank()) {
return null;
}
return valor.trim().toUpperCase();
}
private DocenteGratiDiciembreCompletaDTO toDto(DocenteGratiDiciembreCompleta e) {
return new DocenteGratiDiciembreCompletaDTO(
e.getAnio(),
e.getDni(),
e.getApellidoPaterno(),
e.getApellidoMaterno(),
e.getNombres(),
e.getSeleccion());
}
}
// Módulo desactivado temporalmente: no usar pl_docente_grati_diciembre_completa.
// package so.planilla_be.docentegratidiciembre.service.impl;
//
// import org.springframework.stereotype.Service;
// import org.springframework.transaction.annotation.Transactional;
// import so.planilla_be.docentegratidiciembre.dto.DocenteGratiDiciembreCompletaDTO;
// import so.planilla_be.docentegratidiciembre.dto.DocenteGratiDiciembreCompletaRequestDTO;
// import so.planilla_be.docentegratidiciembre.entity.DocenteGratiDiciembreCompleta;
// import so.planilla_be.docentegratidiciembre.entity.DocenteGratiDiciembreCompletaId;
// import so.planilla_be.docentegratidiciembre.exception.DocenteGratiDiciembreCompletaNoEncontradaException;
// import so.planilla_be.docentegratidiciembre.repository.DocenteGratiDiciembreCompletaRepository;
// import so.planilla_be.docentegratidiciembre.service.DocenteGratiDiciembreCompletaService;
//
// import java.util.List;
//
// @Service
// public class DocenteGratiDiciembreCompletaServiceImpl implements DocenteGratiDiciembreCompletaService {
//
// private final DocenteGratiDiciembreCompletaRepository repository;
//
// public DocenteGratiDiciembreCompletaServiceImpl(DocenteGratiDiciembreCompletaRepository repository) {
// this.repository = repository;
// }
//
// @Override
// @Transactional(readOnly = true)
// public List<DocenteGratiDiciembreCompletaDTO> listarPorAnio(Integer anio) {
// return repository.findByAnioOrderByNumeroDocumentoAscMesAsc(anio).stream()
// .map(this::toDto)
// .toList();
// }
//
// @Override
// @Transactional
// public List<DocenteGratiDiciembreCompletaDTO> grabar(List<DocenteGratiDiciembreCompletaRequestDTO> filas) {
// return filas.stream()
// .map(this::grabarFila)
// .toList();
// }
//
// @Override
// @Transactional
// public void eliminar(String numeroDocumento, Integer anio, Integer mes) {
// DocenteGratiDiciembreCompleta entidad = repository
// .findById(new DocenteGratiDiciembreCompletaId(numeroDocumento.trim(), anio, mes))
// .orElseThrow(() -> new DocenteGratiDiciembreCompletaNoEncontradaException(
// "No existe docente con gratificación de diciembre completa para documento %s, año %d, mes %d"
// .formatted(numeroDocumento.trim(), anio, mes)));
// repository.delete(entidad);
// }
//
// private DocenteGratiDiciembreCompletaDTO grabarFila(DocenteGratiDiciembreCompletaRequestDTO fila) {
// String numeroDocumento = fila.numeroDocumento().trim();
// DocenteGratiDiciembreCompleta entidad = repository
// .findById(new DocenteGratiDiciembreCompletaId(numeroDocumento, fila.anio(), fila.mes()))
// .orElseGet(DocenteGratiDiciembreCompleta::new);
// entidad.setNumeroDocumento(numeroDocumento);
// entidad.setAnio(fila.anio());
// entidad.setMes(fila.mes());
// entidad.setEstado(fila.estado());
// return toDto(repository.save(entidad));
// }
//
// private DocenteGratiDiciembreCompletaDTO toDto(DocenteGratiDiciembreCompleta e) {
// return new DocenteGratiDiciembreCompletaDTO(
// e.getNumeroDocumento(), e.getAnio(), e.getMes(), e.getEstado());
// }
// }
......@@ -198,7 +198,7 @@ public class RentaQuintaCalculadora {
* (semestre completo). Igual en ADM y DOC2.</li>
* <li>DOC2, diciembre: diciembre no es mes completo sino 16 días. Semestre completo =
* 5 meses (julio-noviembre) + 16 días; ingreso en diciembre → 16 − día + 1 días.
* Excepción: si el DNI está en planilla.pl_docente_grati_diciembre_completa, diciembre
* Excepción: si el documento tiene un registro de diciembre del año en planilla.pl_gratificacion, diciembre
* va completo (30 días) y se calcula igual que ADM.</li>
* <li>Ingreso en junio después del día 1 (ADM y DOC2): no hay gratificación de julio; los
* días de junio (días del mes − día + 1) se suman a la de diciembre, que además toma su
......@@ -383,7 +383,7 @@ public class RentaQuintaCalculadora {
* {@code gratificacionPorContrato} es true en ADM y DOC2: la gratificación se prorratea por
* {@code fechaInicioContrato} (sin fecha = 0); {@code diasMesInicioContrato} son los días de
* ese mes según planilla.pl_calendario. {@code gratificacionDiciembreCompleta} es true en DOC2
* cuando el DNI está en planilla.pl_docente_grati_diciembre_completa. La
* cuando el documento tiene un registro de diciembre del año en planilla.pl_gratificacion. La
* UIT, las 7 UIT, los tramos y el divisor son de ley e iguales para todas las planillas.
*/
public record DatosCalculo(
......
......@@ -8,7 +8,7 @@ import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import so.planilla_be.docentegratidiciembre.exception.DocenteGratiDiciembreCompletaNoEncontradaException;
// import so.planilla_be.docentegratidiciembre.exception.DocenteGratiDiciembreCompletaNoEncontradaException;
import so.planilla_be.otrosempleadores.exception.RetencionOtrosEmpleadoresNoEncontradaException;
import so.planilla_be.otrosempleadores.exception.RetencionOtrosEmpleadoresYaExisteException;
import so.planilla_be.proyecciondocentehora.exception.ProyeccionDocenteHoraNoEncontradaException;
......@@ -35,10 +35,10 @@ public class GlobalExceptionHandler {
return build(HttpStatus.NOT_FOUND, ex.getMessage());
}
@ExceptionHandler(DocenteGratiDiciembreCompletaNoEncontradaException.class)
public ResponseEntity<Map<String, Object>> handleNotFound(DocenteGratiDiciembreCompletaNoEncontradaException ex) {
return build(HttpStatus.NOT_FOUND, ex.getMessage());
}
// @ExceptionHandler(DocenteGratiDiciembreCompletaNoEncontradaException.class)
// public ResponseEntity<Map<String, Object>> handleNotFound(DocenteGratiDiciembreCompletaNoEncontradaException ex) {
// return build(HttpStatus.NOT_FOUND, ex.getMessage());
// }
@ExceptionHandler(RetencionOtrosEmpleadoresYaExisteException.class)
public ResponseEntity<Map<String, Object>> handleConflict(RetencionOtrosEmpleadoresYaExisteException ex) {
......
package so.planilla_be.rentaquinta.repository;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;
import java.util.Map;
/** Consulta de registros anuales que habilitan la gratificación DOC2 de diciembre completo. */
@Repository
public class GratificacionRepository {
private final NamedParameterJdbcTemplate jdbc;
public GratificacionRepository(NamedParameterJdbcTemplate jdbc) {
this.jdbc = jdbc;
}
/** Solo diciembre del año consultado habilita la excepción; monto_basico no interviene. */
public boolean existeEnDiciembre(String numeroDocumento, Integer anio) {
return Boolean.TRUE.equals(jdbc.queryForObject("""
select exists (
select 1 from planilla.pl_gratificacion
where trim(numero_documento) = :numeroDocumento and anio = :anio and mes = 12
)
""", Map.of("numeroDocumento", numeroDocumento.trim(), "anio", anio), Boolean.class));
}
}
......@@ -7,7 +7,7 @@ import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import so.planilla_be.docentegratidiciembre.repository.DocenteGratiDiciembreCompletaRepository;
import so.planilla_be.rentaquinta.repository.GratificacionRepository;
import so.planilla_be.proyecciondocentehora.entity.ProyeccionDocenteHora;
import so.planilla_be.proyecciondocentehora.repository.ProyeccionDocenteHoraRepository;
import so.planilla_be.rentaquinta.calculo.CalculoRentaQuinta;
......@@ -79,7 +79,7 @@ public class RentaQuintaServiceImpl implements RentaQuintaService {
private final RentaQuintaGrabadaRepository rentaQuintaGrabadaRepository;
private final ProyeccionDocenteHoraRepository proyeccionDocenteHoraRepository;
private final CalendarioRepository calendarioRepository;
private final DocenteGratiDiciembreCompletaRepository docenteGratiDiciembreCompletaRepository;
private final GratificacionRepository gratificacionRepository;
private final EntityManager entityManager;
private final OrganizacionPersonalRepository organizacionRepository;
......@@ -91,7 +91,7 @@ public class RentaQuintaServiceImpl implements RentaQuintaService {
RentaQuintaGrabadaRepository rentaQuintaGrabadaRepository,
ProyeccionDocenteHoraRepository proyeccionDocenteHoraRepository,
CalendarioRepository calendarioRepository,
DocenteGratiDiciembreCompletaRepository docenteGratiDiciembreCompletaRepository,
GratificacionRepository gratificacionRepository,
EntityManager entityManager,
OrganizacionPersonalRepository organizacionRepository) {
this.repository = repository;
......@@ -103,7 +103,7 @@ public class RentaQuintaServiceImpl implements RentaQuintaService {
this.rentaQuintaGrabadaRepository = rentaQuintaGrabadaRepository;
this.proyeccionDocenteHoraRepository = proyeccionDocenteHoraRepository;
this.calendarioRepository = calendarioRepository;
this.docenteGratiDiciembreCompletaRepository = docenteGratiDiciembreCompletaRepository;
this.gratificacionRepository = gratificacionRepository;
this.entityManager = entityManager;
this.organizacionRepository = organizacionRepository;
}
......@@ -582,10 +582,10 @@ public class RentaQuintaServiceImpl implements RentaQuintaService {
.findFirst()
.orElse(null)
: null;
// DOC2: si el DNI está en pl_docente_grati_diciembre_completa para el año, la gratificación
// de diciembre va con diciembre completo (30 días) en vez de 16.
// DOC2: un registro del documento en pl_gratificacion para diciembre del año habilita
// diciembre completo (30 días). monto_basico no modifica esta regla.
boolean gratificacionDiciembreCompleta = modalidad == ModalidadCalculo.MONTO_FIJO_DICIEMBRE_PROPORCIONAL
&& docenteGratiDiciembreCompletaRepository.existsByDniAndAnio(numeroDocumento.trim(), periodo);
&& gratificacionRepository.existeEnDiciembre(numeroDocumento, periodo);
return new DatosCalculo(
periodo, modalidad, proyeccionDocenteHora, sueldoBrutoMesCorteDocenteHora, sueldoBrutoRealMesCorte,
......
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