Usar pl_gratificacion para diciembre completo DOC2 y desactivar mantenimiento anterior

parent b7ffff5a
...@@ -153,12 +153,12 @@ que la duplicaba en la grilla. ...@@ -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, - **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 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. `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 Excepción: si el documento tiene un registro de diciembre del año en `planilla.pl_gratificacion`
(PK `dni + anio`), su gratificación de diciembre DOC2 va con diciembre completo (30 días, (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. La crea igual que ADM). Solo la gratificación: el sueldo bruto de diciembre sigue ÷ 30 × 16. El mantenimiento
`docs/create-docente-grati-diciembre-completa.sql` (correr antes de desplegar). Mantenimiento
en `/api/v1/docente-grati-diciembre-completa` (paquete `docentegratidiciembre`, ver 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 2. Renta bruta anual = historial + planilla cargada + proyección
3. Renta neta = bruta − **7 UIT** 3. Renta neta = bruta − **7 UIT**
4. Impuesto anual = tramos progresivos 8% / 14% / 17% / 20% / 30% (5, 20, 35 y 45 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. ...@@ -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, - **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 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. `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 Excepción: si el documento tiene un registro de diciembre del año en `planilla.pl_gratificacion`
(PK `dni + anio`), su gratificación de diciembre DOC2 va con diciembre completo (30 días, (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. La crea igual que ADM). Solo la gratificación: el sueldo bruto de diciembre sigue ÷ 30 × 16. El mantenimiento
`docs/create-docente-grati-diciembre-completa.sql` (correr antes de desplegar). Mantenimiento
en `/api/v1/docente-grati-diciembre-completa` (paquete `docentegratidiciembre`, ver 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 2. Renta bruta anual = historial + planilla cargada + proyección
3. Renta neta = bruta − **7 UIT** 3. Renta neta = bruta − **7 UIT**
4. Impuesto anual = tramos progresivos 8% / 14% / 17% / 20% / 30% (5, 20, 35 y 45 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 # API de mantenimiento de docentes
escribe en esa tabla. La renta de quinta la lee para decidir cómo calcula la gratificación de
diciembre en la planilla **DOC2**.
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 El cálculo DOC2 continúa consultando registros de diciembre en `planilla.pl_gratificacion`.
Este mantenimiento no determina la excepción del cálculo.
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**.
## Endpoints ## Endpoints
| Método | Ruta | Descripción | - `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.
| GET | `/api/v1/docente-grati-diciembre-completa/{anio}` | Docentes registrados en el año | - `DELETE /api/v1/docente-grati-diciembre-completa/{numeroDocumento}/{anio}/{mes}`: elimina por clave; 204 o 404 si no existe.
| 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**.
## Estructura de la tabla ## Cuerpo del POST y respuesta
| 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).
```json ```json
[ [
{ {
"numeroDocumento": "73180595",
"anio": 2026, "anio": 2026,
"dni": "73180595", "mes": 12,
"apellidoPaterno": "ACEVEDO", "estado": 1
"apellidoMaterno": "CUITO",
"nombres": "MAURICIO JUNIO",
"seleccion": 1
} }
] ]
``` ```
## POST Se recortan espacios del documento. Los campos anteriores `dni`, apellidos, nombres y `seleccion`
se sustituyen por el contrato anterior. El DELETE ahora requiere mes.
Body: arreglo JSON, no vacío.
| Campo | Tipo | Obligatorio | Regla | ## DDL
|---|---|---|---|
| `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/
```
En renta de quinta: `RentaQuintaServiceImpl#calcular` arma `gratificacionDiciembreCompleta` (DOC2 + Para una instalación nueva ejecutar `docs/create-docente-grati-diciembre-completa.sql` antes de
fila DNI + año) y `RentaQuintaCalculadora#gratificacionProporcional` deja de aplicar los 16 días. 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 -- Mantenimiento mensual. El cálculo DOC2 consulta pl_gratificacion.
-- 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.
CREATE TABLE planilla.pl_docente_grati_diciembre_completa ( CREATE TABLE planilla.pl_docente_grati_diciembre_completa (
dni varchar(15) NOT NULL, numero_documento varchar(15) NOT NULL,
anio integer NOT NULL, anio integer NOT NULL,
apellido_paterno varchar(100), mes integer NOT NULL,
apellido_materno varchar(100), estado integer NOT NULL,
nombres varchar(150), PRIMARY KEY (numero_documento, anio, mes),
seleccion integer, CONSTRAINT ck_docente_grati_mes CHECK (mes BETWEEN 1 AND 12),
PRIMARY KEY (dni, anio) 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 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.'; 'Mantenimiento mensual de docentes: estado 0 inactivo, 1 activo.';
-- 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);
package so.planilla_be.docentegratidiciembre.controller; // 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 io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.Valid; // import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotEmpty; // import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity; // import jakarta.validation.constraints.Min;
import org.springframework.validation.annotation.Validated; // import jakarta.validation.constraints.Max;
import org.springframework.web.bind.annotation.DeleteMapping; // import jakarta.validation.constraints.Size;
import org.springframework.web.bind.annotation.GetMapping; // import jakarta.validation.constraints.NotEmpty;
import org.springframework.web.bind.annotation.PathVariable; // import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping; // import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody; // import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.RequestMapping; // import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController; // import org.springframework.web.bind.annotation.PathVariable;
import so.planilla_be.docentegratidiciembre.dto.DocenteGratiDiciembreCompletaDTO; // import org.springframework.web.bind.annotation.PostMapping;
import so.planilla_be.docentegratidiciembre.dto.DocenteGratiDiciembreCompletaRequestDTO; // import org.springframework.web.bind.annotation.RequestBody;
import so.planilla_be.docentegratidiciembre.service.DocenteGratiDiciembreCompletaService; // import org.springframework.web.bind.annotation.RequestMapping;
// import org.springframework.web.bind.annotation.RestController;
import java.util.List; // import so.planilla_be.docentegratidiciembre.dto.DocenteGratiDiciembreCompletaDTO;
// import so.planilla_be.docentegratidiciembre.dto.DocenteGratiDiciembreCompletaRequestDTO;
@Tag(name = "Docente gratificación diciembre completa", // import so.planilla_be.docentegratidiciembre.service.DocenteGratiDiciembreCompletaService;
description = "Grabado de pl_docente_grati_diciembre_completa (DOC2 con diciembre a 30 días)") //
@Validated // import java.util.List;
@RestController //
@RequestMapping("/api/v1/docente-grati-diciembre-completa") // @Tag(name = "Docente gratificación diciembre completa",
public class DocenteGratiDiciembreCompletaController { // description = "Grabado de pl_docente_grati_diciembre_completa (DOC2 con diciembre a 30 días)")
// @Validated
private final DocenteGratiDiciembreCompletaService service; // @RestController
// @RequestMapping("/api/v1/docente-grati-diciembre-completa")
public DocenteGratiDiciembreCompletaController(DocenteGratiDiciembreCompletaService service) { // public class DocenteGratiDiciembreCompletaController {
this.service = service; //
} // private final DocenteGratiDiciembreCompletaService service;
//
@Operation(summary = "Docentes registrados en el año") // public DocenteGratiDiciembreCompletaController(DocenteGratiDiciembreCompletaService service) {
@GetMapping("/{anio}") // this.service = service;
public List<DocenteGratiDiciembreCompletaDTO> listarPorAnio(@PathVariable Integer anio) { // }
return service.listarPorAnio(anio); //
} // @Operation(summary = "Docentes registrados en el año")
// @GetMapping("/{anio}")
@Operation(summary = "Grabar docentes (inserta o actualiza por DNI + año)") // public List<DocenteGratiDiciembreCompletaDTO> listarPorAnio(@PathVariable @Min(2000) @Max(2100) Integer anio) {
@PostMapping // return service.listarPorAnio(anio);
public List<DocenteGratiDiciembreCompletaDTO> grabar( // }
@RequestBody @NotEmpty List<@Valid DocenteGratiDiciembreCompletaRequestDTO> filas) { //
return service.grabar(filas); // @Operation(summary = "Grabar docentes (inserta o actualiza por documento + año + mes)")
} // @PostMapping
// public List<DocenteGratiDiciembreCompletaDTO> grabar(
@Operation(summary = "Eliminar un docente del año") // @RequestBody @NotEmpty List<@Valid DocenteGratiDiciembreCompletaRequestDTO> filas) {
@DeleteMapping("/{dni}/{anio}") // return service.grabar(filas);
public ResponseEntity<Void> eliminar(@PathVariable String dni, @PathVariable Integer anio) { // }
service.eliminar(dni, anio); //
return ResponseEntity.noContent().build(); // @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; // Módulo desactivado temporalmente: no usar pl_docente_grati_diciembre_completa.
// package so.planilla_be.docentegratidiciembre.dto;
public record DocenteGratiDiciembreCompletaDTO( // public record DocenteGratiDiciembreCompletaDTO(String numeroDocumento, Integer anio, Integer mes, Integer estado) {}
Integer anio,
String dni,
String apellidoPaterno,
String apellidoMaterno,
String nombres,
Integer seleccion
) {}
package so.planilla_be.docentegratidiciembre.dto; // Módulo desactivado temporalmente: no usar pl_docente_grati_diciembre_completa.
// package so.planilla_be.docentegratidiciembre.dto;
import jakarta.validation.constraints.Max; // import jakarta.validation.constraints.*;
import jakarta.validation.constraints.Min; // /** Registro por documento, año y mes; estado 0 inactivo, 1 activo. */
import jakarta.validation.constraints.NotBlank; // public record DocenteGratiDiciembreCompletaRequestDTO(
import jakarta.validation.constraints.NotNull; // @NotBlank @Size(max = 15) String numeroDocumento,
import jakarta.validation.constraints.Size; // @NotNull @Min(2000) @Max(2100) Integer anio,
// @NotNull @Min(1) @Max(12) Integer mes,
/** // @NotNull @Min(0) @Max(1) Integer estado
* 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
) {}
package so.planilla_be.docentegratidiciembre.entity; // Módulo desactivado temporalmente: no usar pl_docente_grati_diciembre_completa.
// package so.planilla_be.docentegratidiciembre.entity;
import jakarta.persistence.Column; // import jakarta.persistence.*;
import jakarta.persistence.Entity; // import lombok.Getter;
import jakarta.persistence.Id; // import lombok.Setter;
import jakarta.persistence.IdClass; // /** Registro mensual del mantenimiento. */
import jakarta.persistence.Table; // @Getter
import lombok.Getter; // @Setter
import lombok.Setter; // @Entity
// @Table(name = "pl_docente_grati_diciembre_completa", schema = "planilla")
/** // @IdClass(DocenteGratiDiciembreCompletaId.class)
* Docente DOC2 cuya gratificación de diciembre del año va con diciembre completo (30 días) y no a // public class DocenteGratiDiciembreCompleta {
* 16 días (planilla.pl_docente_grati_diciembre_completa, una fila por DNI + año). La crea // @Id @Column(name = "numero_documento", length = 15, nullable = false)
* docs/create-docente-grati-diciembre-completa.sql; se graba desde el mantenimiento de este módulo // private String numeroDocumento;
* y la renta de quinta solo la lee. // @Id @Column(name = "anio", nullable = false)
*/ // private Integer anio;
@Getter // @Id @Column(name = "mes", nullable = false)
@Setter // private Integer mes;
@Entity // @Column(name = "estado", nullable = false)
@Table(name = "pl_docente_grati_diciembre_completa") // private Integer estado;
@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;
}
package so.planilla_be.docentegratidiciembre.entity; // Módulo desactivado temporalmente: no usar pl_docente_grati_diciembre_completa.
// package so.planilla_be.docentegratidiciembre.entity;
import java.io.Serializable; // import java.io.Serializable;
// public record DocenteGratiDiciembreCompletaId(String numeroDocumento, Integer anio, Integer mes) implements Serializable {}
public record DocenteGratiDiciembreCompletaId(String dni, Integer anio) implements Serializable {}
package so.planilla_be.docentegratidiciembre.exception; // Módulo desactivado temporalmente: no usar pl_docente_grati_diciembre_completa.
// package so.planilla_be.docentegratidiciembre.exception;
public class DocenteGratiDiciembreCompletaNoEncontradaException extends RuntimeException { //
// public class DocenteGratiDiciembreCompletaNoEncontradaException extends RuntimeException {
public DocenteGratiDiciembreCompletaNoEncontradaException(String message) { //
super(message); // public DocenteGratiDiciembreCompletaNoEncontradaException(String message) {
} // super(message);
} // }
// }
package so.planilla_be.docentegratidiciembre.repository; // 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 org.springframework.data.jpa.repository.JpaRepository;
import so.planilla_be.docentegratidiciembre.entity.DocenteGratiDiciembreCompletaId; // import so.planilla_be.docentegratidiciembre.entity.DocenteGratiDiciembreCompleta;
// import so.planilla_be.docentegratidiciembre.entity.DocenteGratiDiciembreCompletaId;
import java.util.List; //
// import java.util.List;
public interface DocenteGratiDiciembreCompletaRepository //
extends JpaRepository<DocenteGratiDiciembreCompleta, DocenteGratiDiciembreCompletaId> { // public interface DocenteGratiDiciembreCompletaRepository
// extends JpaRepository<DocenteGratiDiciembreCompleta, DocenteGratiDiciembreCompletaId> {
/** Docentes registrados en el año, por apellidos y nombres. */ //
List<DocenteGratiDiciembreCompleta> findByAnioOrderByApellidoPaternoAscApellidoMaternoAscNombresAsc(Integer anio); // /** Registros del año ordenados por documento y mes. */
// List<DocenteGratiDiciembreCompleta> findByAnioOrderByNumeroDocumentoAscMesAsc(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); //
} // }
package so.planilla_be.docentegratidiciembre.service; // 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 so.planilla_be.docentegratidiciembre.dto.DocenteGratiDiciembreCompletaDTO;
// import so.planilla_be.docentegratidiciembre.dto.DocenteGratiDiciembreCompletaRequestDTO;
import java.util.List; //
// import java.util.List;
public interface DocenteGratiDiciembreCompletaService { //
// public interface DocenteGratiDiciembreCompletaService {
/** Docentes registrados en el año. Sin filas: lista vacía. */ //
List<DocenteGratiDiciembreCompletaDTO> listarPorAnio(Integer anio); // /** 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. // * 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); // */
// List<DocenteGratiDiciembreCompletaDTO> grabar(List<DocenteGratiDiciembreCompletaRequestDTO> filas);
/** Elimina la fila dni + anio. Lanza DocenteGratiDiciembreCompletaNoEncontradaException si no existe. */ //
void eliminar(String dni, Integer anio); // /** 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; // 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 org.springframework.stereotype.Service;
import so.planilla_be.docentegratidiciembre.dto.DocenteGratiDiciembreCompletaDTO; // import org.springframework.transaction.annotation.Transactional;
import so.planilla_be.docentegratidiciembre.dto.DocenteGratiDiciembreCompletaRequestDTO; // import so.planilla_be.docentegratidiciembre.dto.DocenteGratiDiciembreCompletaDTO;
import so.planilla_be.docentegratidiciembre.entity.DocenteGratiDiciembreCompleta; // import so.planilla_be.docentegratidiciembre.dto.DocenteGratiDiciembreCompletaRequestDTO;
import so.planilla_be.docentegratidiciembre.entity.DocenteGratiDiciembreCompletaId; // import so.planilla_be.docentegratidiciembre.entity.DocenteGratiDiciembreCompleta;
import so.planilla_be.docentegratidiciembre.exception.DocenteGratiDiciembreCompletaNoEncontradaException; // import so.planilla_be.docentegratidiciembre.entity.DocenteGratiDiciembreCompletaId;
import so.planilla_be.docentegratidiciembre.repository.DocenteGratiDiciembreCompletaRepository; // import so.planilla_be.docentegratidiciembre.exception.DocenteGratiDiciembreCompletaNoEncontradaException;
import so.planilla_be.docentegratidiciembre.service.DocenteGratiDiciembreCompletaService; // import so.planilla_be.docentegratidiciembre.repository.DocenteGratiDiciembreCompletaRepository;
// import so.planilla_be.docentegratidiciembre.service.DocenteGratiDiciembreCompletaService;
import java.util.List; //
// import java.util.List;
@Service //
public class DocenteGratiDiciembreCompletaServiceImpl implements DocenteGratiDiciembreCompletaService { // @Service
// public class DocenteGratiDiciembreCompletaServiceImpl implements DocenteGratiDiciembreCompletaService {
private final DocenteGratiDiciembreCompletaRepository repository; //
// private final DocenteGratiDiciembreCompletaRepository repository;
public DocenteGratiDiciembreCompletaServiceImpl(DocenteGratiDiciembreCompletaRepository repository) { //
this.repository = repository; // public DocenteGratiDiciembreCompletaServiceImpl(DocenteGratiDiciembreCompletaRepository repository) {
} // this.repository = repository;
// }
@Override //
@Transactional(readOnly = true) // @Override
public List<DocenteGratiDiciembreCompletaDTO> listarPorAnio(Integer anio) { // @Transactional(readOnly = true)
return repository.findByAnioOrderByApellidoPaternoAscApellidoMaternoAscNombresAsc(anio).stream() // public List<DocenteGratiDiciembreCompletaDTO> listarPorAnio(Integer anio) {
.map(this::toDto) // return repository.findByAnioOrderByNumeroDocumentoAscMesAsc(anio).stream()
.toList(); // .map(this::toDto)
} // .toList();
// }
@Override //
@Transactional // @Override
public List<DocenteGratiDiciembreCompletaDTO> grabar(List<DocenteGratiDiciembreCompletaRequestDTO> filas) { // @Transactional
return filas.stream() // public List<DocenteGratiDiciembreCompletaDTO> grabar(List<DocenteGratiDiciembreCompletaRequestDTO> filas) {
.map(this::grabarFila) // return filas.stream()
.toList(); // .map(this::grabarFila)
} // .toList();
// }
@Override //
@Transactional // @Override
public void eliminar(String dni, Integer anio) { // @Transactional
DocenteGratiDiciembreCompleta entidad = repository // public void eliminar(String numeroDocumento, Integer anio, Integer mes) {
.findById(new DocenteGratiDiciembreCompletaId(dni.trim(), anio)) // DocenteGratiDiciembreCompleta entidad = repository
.orElseThrow(() -> new DocenteGratiDiciembreCompletaNoEncontradaException( // .findById(new DocenteGratiDiciembreCompletaId(numeroDocumento.trim(), anio, mes))
"No existe docente con gratificación de diciembre completa para DNI %s, año %d" // .orElseThrow(() -> new DocenteGratiDiciembreCompletaNoEncontradaException(
.formatted(dni.trim(), anio))); // "No existe docente con gratificación de diciembre completa para documento %s, año %d, mes %d"
repository.delete(entidad); // .formatted(numeroDocumento.trim(), anio, mes)));
} // repository.delete(entidad);
// }
private DocenteGratiDiciembreCompletaDTO grabarFila(DocenteGratiDiciembreCompletaRequestDTO fila) { //
String dni = fila.dni().trim(); // private DocenteGratiDiciembreCompletaDTO grabarFila(DocenteGratiDiciembreCompletaRequestDTO fila) {
DocenteGratiDiciembreCompleta entidad = repository // String numeroDocumento = fila.numeroDocumento().trim();
.findById(new DocenteGratiDiciembreCompletaId(dni, fila.anio())) // DocenteGratiDiciembreCompleta entidad = repository
.orElseGet(DocenteGratiDiciembreCompleta::new); // .findById(new DocenteGratiDiciembreCompletaId(numeroDocumento, fila.anio(), fila.mes()))
entidad.setDni(dni); // .orElseGet(DocenteGratiDiciembreCompleta::new);
entidad.setAnio(fila.anio()); // entidad.setNumeroDocumento(numeroDocumento);
entidad.setApellidoPaterno(texto(fila.apellidoPaterno())); // entidad.setAnio(fila.anio());
entidad.setApellidoMaterno(texto(fila.apellidoMaterno())); // entidad.setMes(fila.mes());
entidad.setNombres(texto(fila.nombres())); // entidad.setEstado(fila.estado());
entidad.setSeleccion(fila.seleccion()); // return toDto(repository.save(entidad));
return toDto(repository.save(entidad)); // }
} //
// private DocenteGratiDiciembreCompletaDTO toDto(DocenteGratiDiciembreCompleta e) {
/** Apellidos y nombres en mayúsculas y sin espacios sobrantes; vacío se graba como null. */ // return new DocenteGratiDiciembreCompletaDTO(
private String texto(String valor) { // e.getNumeroDocumento(), e.getAnio(), e.getMes(), e.getEstado());
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());
}
}
...@@ -198,7 +198,7 @@ public class RentaQuintaCalculadora { ...@@ -198,7 +198,7 @@ public class RentaQuintaCalculadora {
* (semestre completo). Igual en ADM y DOC2.</li> * (semestre completo). Igual en ADM y DOC2.</li>
* <li>DOC2, diciembre: diciembre no es mes completo sino 16 días. Semestre completo = * <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. * 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> * 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 * <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 * 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 { ...@@ -383,7 +383,7 @@ public class RentaQuintaCalculadora {
* {@code gratificacionPorContrato} es true en ADM y DOC2: la gratificación se prorratea por * {@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 * {@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 * 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. * UIT, las 7 UIT, los tramos y el divisor son de ley e iguales para todas las planillas.
*/ */
public record DatosCalculo( public record DatosCalculo(
......
...@@ -8,7 +8,7 @@ import org.springframework.web.bind.MissingServletRequestParameterException; ...@@ -8,7 +8,7 @@ import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; 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.RetencionOtrosEmpleadoresNoEncontradaException;
import so.planilla_be.otrosempleadores.exception.RetencionOtrosEmpleadoresYaExisteException; import so.planilla_be.otrosempleadores.exception.RetencionOtrosEmpleadoresYaExisteException;
import so.planilla_be.proyecciondocentehora.exception.ProyeccionDocenteHoraNoEncontradaException; import so.planilla_be.proyecciondocentehora.exception.ProyeccionDocenteHoraNoEncontradaException;
...@@ -35,10 +35,10 @@ public class GlobalExceptionHandler { ...@@ -35,10 +35,10 @@ public class GlobalExceptionHandler {
return build(HttpStatus.NOT_FOUND, ex.getMessage()); return build(HttpStatus.NOT_FOUND, ex.getMessage());
} }
@ExceptionHandler(DocenteGratiDiciembreCompletaNoEncontradaException.class) // @ExceptionHandler(DocenteGratiDiciembreCompletaNoEncontradaException.class)
public ResponseEntity<Map<String, Object>> handleNotFound(DocenteGratiDiciembreCompletaNoEncontradaException ex) { // public ResponseEntity<Map<String, Object>> handleNotFound(DocenteGratiDiciembreCompletaNoEncontradaException ex) {
return build(HttpStatus.NOT_FOUND, ex.getMessage()); // return build(HttpStatus.NOT_FOUND, ex.getMessage());
} // }
@ExceptionHandler(RetencionOtrosEmpleadoresYaExisteException.class) @ExceptionHandler(RetencionOtrosEmpleadoresYaExisteException.class)
public ResponseEntity<Map<String, Object>> handleConflict(RetencionOtrosEmpleadoresYaExisteException ex) { 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; ...@@ -7,7 +7,7 @@ import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; 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.entity.ProyeccionDocenteHora;
import so.planilla_be.proyecciondocentehora.repository.ProyeccionDocenteHoraRepository; import so.planilla_be.proyecciondocentehora.repository.ProyeccionDocenteHoraRepository;
import so.planilla_be.rentaquinta.calculo.CalculoRentaQuinta; import so.planilla_be.rentaquinta.calculo.CalculoRentaQuinta;
...@@ -79,7 +79,7 @@ public class RentaQuintaServiceImpl implements RentaQuintaService { ...@@ -79,7 +79,7 @@ public class RentaQuintaServiceImpl implements RentaQuintaService {
private final RentaQuintaGrabadaRepository rentaQuintaGrabadaRepository; private final RentaQuintaGrabadaRepository rentaQuintaGrabadaRepository;
private final ProyeccionDocenteHoraRepository proyeccionDocenteHoraRepository; private final ProyeccionDocenteHoraRepository proyeccionDocenteHoraRepository;
private final CalendarioRepository calendarioRepository; private final CalendarioRepository calendarioRepository;
private final DocenteGratiDiciembreCompletaRepository docenteGratiDiciembreCompletaRepository; private final GratificacionRepository gratificacionRepository;
private final EntityManager entityManager; private final EntityManager entityManager;
private final OrganizacionPersonalRepository organizacionRepository; private final OrganizacionPersonalRepository organizacionRepository;
...@@ -91,7 +91,7 @@ public class RentaQuintaServiceImpl implements RentaQuintaService { ...@@ -91,7 +91,7 @@ public class RentaQuintaServiceImpl implements RentaQuintaService {
RentaQuintaGrabadaRepository rentaQuintaGrabadaRepository, RentaQuintaGrabadaRepository rentaQuintaGrabadaRepository,
ProyeccionDocenteHoraRepository proyeccionDocenteHoraRepository, ProyeccionDocenteHoraRepository proyeccionDocenteHoraRepository,
CalendarioRepository calendarioRepository, CalendarioRepository calendarioRepository,
DocenteGratiDiciembreCompletaRepository docenteGratiDiciembreCompletaRepository, GratificacionRepository gratificacionRepository,
EntityManager entityManager, EntityManager entityManager,
OrganizacionPersonalRepository organizacionRepository) { OrganizacionPersonalRepository organizacionRepository) {
this.repository = repository; this.repository = repository;
...@@ -103,7 +103,7 @@ public class RentaQuintaServiceImpl implements RentaQuintaService { ...@@ -103,7 +103,7 @@ public class RentaQuintaServiceImpl implements RentaQuintaService {
this.rentaQuintaGrabadaRepository = rentaQuintaGrabadaRepository; this.rentaQuintaGrabadaRepository = rentaQuintaGrabadaRepository;
this.proyeccionDocenteHoraRepository = proyeccionDocenteHoraRepository; this.proyeccionDocenteHoraRepository = proyeccionDocenteHoraRepository;
this.calendarioRepository = calendarioRepository; this.calendarioRepository = calendarioRepository;
this.docenteGratiDiciembreCompletaRepository = docenteGratiDiciembreCompletaRepository; this.gratificacionRepository = gratificacionRepository;
this.entityManager = entityManager; this.entityManager = entityManager;
this.organizacionRepository = organizacionRepository; this.organizacionRepository = organizacionRepository;
} }
...@@ -582,10 +582,10 @@ public class RentaQuintaServiceImpl implements RentaQuintaService { ...@@ -582,10 +582,10 @@ public class RentaQuintaServiceImpl implements RentaQuintaService {
.findFirst() .findFirst()
.orElse(null) .orElse(null)
: null; : null;
// DOC2: si el DNI está en pl_docente_grati_diciembre_completa para el año, la gratificación // DOC2: un registro del documento en pl_gratificacion para diciembre del año habilita
// de diciembre va con diciembre completo (30 días) en vez de 16. // diciembre completo (30 días). monto_basico no modifica esta regla.
boolean gratificacionDiciembreCompleta = modalidad == ModalidadCalculo.MONTO_FIJO_DICIEMBRE_PROPORCIONAL boolean gratificacionDiciembreCompleta = modalidad == ModalidadCalculo.MONTO_FIJO_DICIEMBRE_PROPORCIONAL
&& docenteGratiDiciembreCompletaRepository.existsByDniAndAnio(numeroDocumento.trim(), periodo); && gratificacionRepository.existeEnDiciembre(numeroDocumento, periodo);
return new DatosCalculo( return new DatosCalculo(
periodo, modalidad, proyeccionDocenteHora, sueldoBrutoMesCorteDocenteHora, sueldoBrutoRealMesCorte, 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