Commit 22d44d40 by Vanesa Puma Yamo

feat: incluir retenciones de otros empleadores en el calculo de renta de quinta

Suma los importes y retenciones declarados por otros empleadores (anteriores,
mes de corte y proyeccion) a la renta bruta e impuesto anual, y descuenta esas
retenciones del saldo a prorratear (art. 40 y 53 LIR; art. 40 del Reglamento).
Expone rentaBrutaAnualSinOtrosEmpleadores para mostrar los ingresos propios por
separado en la grilla y la boleta.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
parent d2e3731b
# Otros empleadores en el punto 5 — Control de retenciones efectuadas y cálculo de cuota
Este documento explica cómo se obtiene, para un trabajador con registros en
`retencion_otros_empleadores`, la fila de **total ya retenido por otros empleadores** dentro del
punto 5 de `RentaQuintaDetalleCompletoDTO` (`controlRetenciones`), y cómo esa fila participa en el
cálculo de la retención del mes de corte.
## 1. Origen del dato
Tabla `planilla.retencion_otros_empleadores`, un registro por trabajador/año/mes (CRUD del
módulo `otrosempleadores`):
| Columna | Significado |
|---|---|
| `documento_trabajador` | Documento del trabajador (mismo que `numero_documento` de `renta_quinta`) |
| `anio`, `mes` | Periodo declarado |
| `importe` | Ingreso bruto que ese mes le pagó el otro empleador |
| `retencion` | Retención de quinta que ese otro empleador ya le practicó ese mes |
`RentaQuintaServiceImpl` la trae con
`RetencionOtrosEmpleadoresRepository.findByDocumentoTrabajadorAndAnioOrderByMes(documento, anio)`.
## 2. Separación anterior / mes de corte (misma lógica que la propia planilla)
Igual que `historialReal` (meses < mes de corte) y `planillaCargada` (mes de corte) para la
planilla propia, los registros de otros empleadores se recorren y se clasifican por `mes` contra
el `mesCorte` consultado:
```
para cada registro r en retencion_otros_empleadores del (documento, anio):
si r.mes > mesCorte: se ignora (mes futuro, aún no declarado)
si r.mes < mesCorte: importeAnterior += r.importe
retencionAnterior += r.retencion
si r.mes == mesCorte: importeMesActual += r.importe
retencionMesActual += r.retencion
```
Esto vive en `RentaQuintaServiceImpl.otrosEmpleadores(...)`, que devuelve el record interno
`OtrosEmpleadoresAgregado(importeAnterior, importeMesActual, retencionAnterior,
retencionMesActual)` y se pasa a `RentaQuintaCalculadora.DatosCalculo`.
## 3. Fila "total ya retenido por otros empleadores"
El punto 5 (`ControlRetencionesDTO`) ya expone las dos partes por separado:
```java
public record ControlRetencionesDTO(
BigDecimal impuestoAnualReferencia,
BigDecimal retencionesAcumuladasAnteriores, // propia planilla, meses < corte
BigDecimal retencionOtrosEmpleadoresAnterior, // otros empleadores, meses < corte
BigDecimal retencionOtrosEmpleadoresMesActual, // otros empleadores, mes de corte
BigDecimal saldoPendienteRetencion,
BigDecimal factorDivisorMes,
BigDecimal retencionMesActual
) {}
```
La fila de **total ya retenido por otros empleadores** (lo que pide agregar) es simplemente la
suma de esas dos columnas, para los meses que el trabajador ya tiene ingresados:
```
totalRetenidoOtrosEmpleadores = retencionOtrosEmpleadoresAnterior + retencionOtrosEmpleadoresMesActual
= Σ retencion de todos los registros con mes <= mesCorte
```
Si el trabajador **no tiene registros** en `retencion_otros_empleadores` para ese año, la consulta
del paso 1 devuelve lista vacía y las cuatro variables (`importeAnterior`, `importeMesActual`,
`retencionAnterior`, `retencionMesActual`) quedan en `BigDecimal.ZERO` — la fila sale en 0, no
rompe el cálculo ni requiere un `if` especial en el mapper (mismo criterio que ya usa
`orZero(...)` en el resto del código).
## 4. Cómo entra en el cálculo del saldo pendiente
El saldo pendiente (base de la retención del mes) ya descuenta ambas partes, junto con lo
retenido antes por la propia planilla:
```
saldoPendienteRetencion = MAX(0,
impuestoAnualReferencia
− retencionesAcumuladasAnteriores (propia planilla, meses < corte)
− retencionOtrosEmpleadoresAnterior (otros empleadores, meses < corte)
− retencionOtrosEmpleadoresMesActual) (otros empleadores, mes de corte)
retencionMesActual = saldoPendienteRetencion ÷ factorDivisorMes
```
Esto es exactamente lo que hace `RentaQuintaCalculadora.calcular(...)` (variable `saldo`) y
`RentaQuintaMapper.toDetalleCompleto(...)` (variable `saldoPendiente`) — ambos restan las cuatro
fuentes de retención antes de prorratear.
## 5. Fila "total" ya implementada
`ControlRetencionesDTO` expone el campo `retencionOtrosEmpleadoresTotal`, calculado en
`RentaQuintaMapper.toDetalleCompleto(...)` como
`calculo.otrosEmpleadoresRetencionAnterior().add(calculo.otrosEmpleadoresRetencionMesActual())` —
la suma de todo lo ya retenido por otros empleadores hasta el mes de corte (0 si el trabajador no
tiene registros en `retencion_otros_empleadores` para ese año). No participa dos veces en el
cálculo: el saldo pendiente sigue restando `retencionOtrosEmpleadoresAnterior` y
`retencionOtrosEmpleadoresMesActual` por separado (paso 4); esta fila es solo la vista consolidada
de esas dos.
Orden final de `ControlRetencionesDTO`:
```java
public record ControlRetencionesDTO(
BigDecimal impuestoAnualReferencia,
BigDecimal retencionesAcumuladasAnteriores,
BigDecimal retencionOtrosEmpleadoresAnterior,
BigDecimal retencionOtrosEmpleadoresMesActual,
BigDecimal retencionOtrosEmpleadoresTotal,
BigDecimal saldoPendienteRetencion,
BigDecimal factorDivisorMes,
BigDecimal retencionMesActual
) {}
```
# Renta bruta anual "sin otros empleadores" — por qué existen dos números y cómo se calculan
Este documento explica, en detalle, un cambio hecho sobre `RentaQuintaCalculadora` y
`RentaQuintaMapper`: por qué la grilla (`/listado`) y la liquidación de impuesto anual (punto 3
del detalle de boleta) mostraban montos distintos para lo que parecía ser el mismo concepto
("renta bruta anual" / "ingresos anuales totales"), y cómo se unificaron sin tocar el cálculo del
impuesto.
## 1. El síntoma
Antes de este cambio, para un mismo trabajador en un mismo mes de corte, dos partes de la
respuesta del API mostraban números distintos:
| Endpoint / campo | Valor mostrado |
|---|---|
| `GET /api/v1/renta-quinta/listado` → `rentaBrutaAnual` | **Incluía** el importe de otros empleadores |
| `GET /api/v1/renta-quinta/detallado` → `liquidacionImpuestoAnual.rentaBrutaAnual` (fila "ingresos anuales totales") | **No incluía** el importe de otros empleadores (aparece aparte, en su propia fila `otrosEmpleadores`) |
Esto es confuso para quien mira ambos endpoints: parecen medir lo mismo pero no coinciden.
## 2. Por qué el motor de cálculo SÍ necesita sumar otros empleadores
`RentaQuintaCalculadora.calcular(...)` arma el impuesto anual siguiendo el art. 40 y 53 de la Ley
del Impuesto a la Renta. La renta neta imponible se calcula sobre **todos** los ingresos del
trabajador en el año, sin importar qué empleador los pagó — si el trabajador tuvo un segundo
empleador ese año, lo que ese otro empleador le pagó (`importe`, tabla
`retencion_otros_empleadores`) también tributa y debe sumarse a la base.
Por eso, dentro de la calculadora, existe (y debe seguir existiendo) un número que **sí** incluye
otros empleadores: es la base real sobre la que se calcula cuánto impuesto le corresponde pagar al
trabajador. Ese número no se tocó.
## 3. Por qué mostrar ese mismo número en la grilla generaba confusión
La grilla (`/listado`) es una vista rápida por trabajador, pensada para mostrar "lo que ganó en su
propio trabajo" — sueldo, gratificaciones, horas extra, utilidades — mes a mes. Si el campo
`rentaBrutaAnual` de la grilla incluye por dentro un monto de otro empleador (que no viene de
ninguna boleta de `planilla.renta_quinta`, sino de una tabla completamente distinta,
`retencion_otros_empleadores`), el número deja de representar "lo que pagó esta planilla" y nadie
que mire solo la grilla puede saber cuánto de ese total es de otro empleador.
La liquidación de impuesto anual (punto 3 del detalle), en cambio, **ya** separaba el concepto:
tenía una fila para "ingresos anuales totales" (sin otros empleadores) y otra fila aparte para
"otros empleadores". El pedido fue: que la grilla use el mismo criterio que esa fila, no el número
mezclado.
## 4. La solución: dos números, uno para calcular y otro para mostrar
Se agregó un segundo resultado en `CalculoRentaQuinta`, sin cambiar el cálculo del impuesto:
```java
public record CalculoRentaQuinta(
...
BigDecimal rentaBrutaAnual, // CON otros empleadores — para el impuesto
BigDecimal rentaBrutaAnualSinOtrosEmpleadores, // SIN otros empleadores — solo para mostrar
BigDecimal rentaNeta,
...
) {}
```
Dentro de `RentaQuintaCalculadora.calcular(...)`:
```java
// Base remunerativa propia (sin otros empleadores)
BigDecimal rentaBrutaAnualSinOtrosEmpleadores = historialReal
.add(planillaCargada)
.add(estimadoMesCorte)
.add(proyeccionFutura);
// La base real para el impuesto SÍ suma otros empleadores
BigDecimal rentaBrutaAnual = rentaBrutaAnualSinOtrosEmpleadores
.add(otrosEmpleadoresImporteAnterior)
.add(otrosEmpleadoresImporteMesActual);
BigDecimal rentaNeta = rentaBrutaAnual.subtract(DEDUCCION_UIT.multiply(valorUit));
// ... el impuesto anual y la retención del mes siguen calculándose sobre rentaNeta,
// es decir, sobre el total CON otros empleadores. Eso no cambió.
```
`rentaBrutaAnualSinOtrosEmpleadores` es puramente informativo: no participa en ninguna resta,
tramo ni división de las que arman el impuesto. Es un "subtotal" que ya estaba implícito en la
cuenta (`rentaBrutaAnual` menos otros empleadores), solo que ahora se calcula una vez y se expone,
en vez de que cada consumidor (grilla, liquidación) lo recalculara por su cuenta cada uno a su
manera.
## 5. Dónde se usa cada número ahora
| Lugar | Campo | Fuente | ¿Incluye otros empleadores? |
|---|---|---|---|
| Cálculo del impuesto anual y la retención del mes | `rentaNeta`, `impuestoAnual`, `retencionDelMes` | `calculo.rentaBrutaAnual()` | **Sí** (correcto, así lo exige la ley) |
| Grilla `/listado` → `rentaBrutaAnual` | `RentaQuintaMapper.toResumen(...)` | `calculo.rentaBrutaAnualSinOtrosEmpleadores()` | **No** |
| Detalle → `liquidacionImpuestoAnual.rentaBrutaAnual` ("ingresos anuales totales") | `RentaQuintaMapper.toDetalleCompleto(...)` | `calculo.rentaBrutaAnualSinOtrosEmpleadores()` | **No** |
| Detalle → `liquidacionImpuestoAnual.otrosEmpleadores` (fila aparte) | mismo método | `calculo.otrosEmpleadoresImporteAnterior() + calculo.otrosEmpleadoresImporteMesActual()` | (es exactamente el monto de otros empleadores, mostrado solo) |
Antes de este cambio, la fila "ingresos anuales totales" de la liquidación se recalculaba en el
mapper sumando manualmente los 4 conceptos (sueldo bruto, horas extra, gratificaciones,
utilidades) de los 3 bloques del historial (pasado, actual, proyección). Ese cálculo daba el mismo
número que `rentaBrutaAnualSinOtrosEmpleadores`, pero estaba duplicado en dos sitios distintos del
código (la calculadora, por un lado, y el mapper, reconstruyéndolo aparte). Se eliminó esa
duplicación: ahora todos los consumidores leen el mismo campo ya calculado una sola vez en
`RentaQuintaCalculadora`.
## 6. Ejemplo numérico
Trabajador con, hasta el mes de corte:
- Sueldo/gratificaciones propios ya percibidos + proyección restante del año: **S/ 45,000**
- Otro empleador le pagó en el año (declarado en `retencion_otros_empleadores`): **S/ 5,000**
Antes:
| Campo | Valor |
|---|---|
| Grilla → `rentaBrutaAnual` | S/ 50,000 (45,000 + 5,000 mezclados) |
| Detalle → `liquidacionImpuestoAnual.rentaBrutaAnual` | S/ 45,000 |
| Detalle → `liquidacionImpuestoAnual.otrosEmpleadores` | S/ 5,000 |
→ La grilla y el detalle no coincidían, y encima el detalle ya mostraba S/ 5,000 aparte, con lo
que sumar la grilla + la fila de otros empleadores daba S/ 55,000, un número que no existe en
ningún lado (doble conteo).
Ahora:
| Campo | Valor |
|---|---|
| Grilla → `rentaBrutaAnual` | S/ 45,000 |
| Detalle → `liquidacionImpuestoAnual.rentaBrutaAnual` | S/ 45,000 |
| Detalle → `liquidacionImpuestoAnual.otrosEmpleadores` | S/ 5,000 |
Los dos "ingresos anuales totales" (grilla y detalle) coinciden, y el impuesto anual — que no
aparece en esta tabla porque es otro campo — se sigue calculando puertas adentro sobre S/ 50,000
(45,000 + 5,000), como corresponde legalmente.
## 7. Archivos tocados
- `RentaQuintaCalculadora.java` — agrega el cálculo de `rentaBrutaAnualSinOtrosEmpleadores` y lo
devuelve como campo nuevo del resultado, sin tocar la fórmula del impuesto.
- `CalculoRentaQuinta.java` — agrega el campo al record de resultado.
- `RentaQuintaMapper.java` — `toResumen(...)` y `toDetalleCompleto(...)` ahora leen
`calculo.rentaBrutaAnualSinOtrosEmpleadores()` en vez de `calculo.rentaBrutaAnual()` (grilla) o
de recalcularlo sumando bloques a mano (liquidación); se eliminó el helper
`remunerativoSinOtrosEmpleadores(...)` que quedó sin uso.
......@@ -31,6 +31,8 @@
<java.version>25</java.version>
<springdoc.version>3.1.0</springdoc.version>
<testcontainers.version>1.21.3</testcontainers.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencyManagement>
<dependencies>
......
......@@ -11,11 +11,19 @@ public record CalculoRentaQuinta(
BigDecimal estimadoMesCorteSueldoBruto,
BigDecimal estimadoMesCorteGratificacion,
BigDecimal rentaBrutaAnual,
BigDecimal rentaBrutaAnualSinOtrosEmpleadores,
BigDecimal rentaNeta,
BigDecimal impuestoAnual,
BigDecimal retencionDelMes,
BigDecimal valorUit,
BigDecimal divisorMes,
BigDecimal historialReal,
BigDecimal otrosEmpleadoresImporteAnterior,
BigDecimal otrosEmpleadoresImporteMesActual,
BigDecimal otrosEmpleadoresImporteProyeccion,
BigDecimal otrosEmpleadoresRetencionAnterior,
BigDecimal otrosEmpleadoresRetencionMesActual,
BigDecimal otrosEmpleadoresRetencionProyeccion,
List<TramoCalculado> tramos
) {
......
......@@ -70,16 +70,40 @@ public class RentaQuintaCalculadora {
BigDecimal proyeccionGratificaciones = gratificacionesPendientes(datos, julioCubierto, diciembreCubierto);
BigDecimal proyeccionFutura = proyeccionSueldoBruto.add(proyeccionGratificaciones);
BigDecimal rentaBrutaAnual = orZero(datos.historialReal())
// Otros empleadores: igual que la propia planilla, se distingue lo declarado en meses
// anteriores al de corte de lo declarado en el propio mes de corte, para que cada monto
// se muestre en la columna del historial que le corresponde.
BigDecimal otrosEmpleadoresImporteAnterior = orZero(datos.otrosEmpleadoresImporteAnterior());
BigDecimal otrosEmpleadoresImporteMesActual = orZero(datos.otrosEmpleadoresImporteMesActual());
BigDecimal otrosEmpleadoresImporteProyeccion = orZero(datos.otrosEmpleadoresImporteProyeccion());
BigDecimal otrosEmpleadoresRetencionAnterior = orZero(datos.otrosEmpleadoresRetencionAnterior());
BigDecimal otrosEmpleadoresRetencionMesActual = orZero(datos.otrosEmpleadoresRetencionMesActual());
BigDecimal otrosEmpleadoresRetencionProyeccion = orZero(datos.otrosEmpleadoresRetencionProyeccion());
// Base remunerativa propia (sin otros empleadores), usada tal cual para mostrar "ingresos
// anuales totales" en la grilla y en la liquidación anual; el impuesto sí se calcula sobre
// el total con otros empleadores incluidos (art. 40 y 53 LIR).
BigDecimal rentaBrutaAnualSinOtrosEmpleadores = orZero(datos.historialReal())
.add(orZero(datos.planillaCargada()))
.add(estimadoMesCorte)
.add(proyeccionFutura);
BigDecimal rentaBrutaAnual = rentaBrutaAnualSinOtrosEmpleadores
.add(otrosEmpleadoresImporteAnterior)
.add(otrosEmpleadoresImporteMesActual)
.add(otrosEmpleadoresImporteProyeccion);
BigDecimal rentaNeta = rentaBrutaAnual.subtract(DEDUCCION_UIT.multiply(valorUit));
List<TramoCalculado> tramos = new ArrayList<>();
BigDecimal impuestoAnual = impuestoSobre(rentaNeta.max(BigDecimal.ZERO), valorUit, tramos);
BigDecimal saldo = impuestoAnual.subtract(orZero(datos.retenidoAnterior()));
// El impuesto anual se cubre primero con lo ya retenido por la propia planilla y por
// otros empleadores, en el mes de corte y en los anteriores (art. 40 del Reglamento);
// solo el saldo se prorratea en lo que queda del año.
BigDecimal saldo = impuestoAnual
.subtract(orZero(datos.retenidoAnterior()))
.subtract(otrosEmpleadoresRetencionAnterior)
.subtract(otrosEmpleadoresRetencionMesActual)
.subtract(otrosEmpleadoresRetencionProyeccion);
BigDecimal divisorMes = divisorDelMes(datos.mesCorte());
BigDecimal retencionDelMes = saldo.max(BigDecimal.ZERO)
.divide(divisorMes, ESCALA, RoundingMode.HALF_UP);
......@@ -91,11 +115,19 @@ public class RentaQuintaCalculadora {
escala(estimadoSueldoBruto),
escala(estimadoGratificacion),
escala(rentaBrutaAnual),
escala(rentaBrutaAnualSinOtrosEmpleadores),
escala(rentaNeta),
escala(impuestoAnual),
retencionDelMes,
valorUit,
divisorMes,
escala(orZero(datos.historialReal())),
escala(otrosEmpleadoresImporteAnterior),
escala(otrosEmpleadoresImporteMesActual),
escala(otrosEmpleadoresImporteProyeccion),
escala(otrosEmpleadoresRetencionAnterior),
escala(otrosEmpleadoresRetencionMesActual),
escala(otrosEmpleadoresRetencionProyeccion),
tramos);
}
......@@ -104,7 +136,7 @@ public class RentaQuintaCalculadora {
* boleta real ni el estimado del propio mes de corte cuando este es julio/diciembre).
*/
private BigDecimal gratificacionesPendientes(DatosCalculo datos, boolean julioCubierto, boolean diciembreCubierto) {
BigDecimal gratificacion = orZero(datos.remuneracionReferencia())
BigDecimal gratificacion = orZero(datos.basicoReferencia().add(datos.asignacionFamiliarReferencia()))
.multiply(FACTOR_GRATIFICACION);
BigDecimal total = BigDecimal.ZERO;
if (!julioCubierto) {
......@@ -197,6 +229,12 @@ public class RentaQuintaCalculadora {
BigDecimal asignacionFamiliarReferencia,
BigDecimal retenidoAnterior,
boolean julioPercibida,
boolean diciembrePercibida
boolean diciembrePercibida,
BigDecimal otrosEmpleadoresImporteAnterior,
BigDecimal otrosEmpleadoresImporteMesActual,
BigDecimal otrosEmpleadoresImporteProyeccion,
BigDecimal otrosEmpleadoresRetencionAnterior,
BigDecimal otrosEmpleadoresRetencionMesActual,
BigDecimal otrosEmpleadoresRetencionProyeccion
) {}
}
......@@ -69,6 +69,10 @@ public record RentaQuintaDetalleCompletoDTO(
public record ControlRetencionesDTO(
BigDecimal impuestoAnualReferencia,
BigDecimal retencionesAcumuladasAnteriores,
BigDecimal retencionOtrosEmpleadoresAnterior,
BigDecimal retencionOtrosEmpleadoresMesActual,
BigDecimal retencionOtrosEmpleadoresProyeccion,
BigDecimal retencionOtrosEmpleadoresTotal,
BigDecimal saldoPendienteRetencion,
BigDecimal factorDivisorMes,
BigDecimal retencionMesActual
......
......@@ -34,7 +34,7 @@ public class RentaQuintaMapper {
orZero(historialReal),
orZero(planillaCargada),
calculo.proyeccionFutura(),
calculo.rentaBrutaAnual(),
calculo.rentaBrutaAnualSinOtrosEmpleadores(),
calculo.impuestoAnual(),
orZero(retenidoAnterior),
calculo.retencionDelMes());
......@@ -78,27 +78,36 @@ public class RentaQuintaMapper {
* y control de retenciones.
*/
public RentaQuintaDetalleCompletoDTO toDetalleCompleto(RentaQuinta boleta, List<RentaQuinta> historialAnio, int mesCorte, CalculoRentaQuinta calculo) {
BloqueIngresosDTO pasado = bloque(historialAnio, r -> r.getBoletaMes() < mesCorte);
// El total del bloque "pasado" usa el mismo historialReal que ya entra a la base
// imponible en RentaQuintaCalculadora, en vez de recalcularlo aparte: así el punto 1
// siempre queda consistente con lo que efectivamente se usó para el impuesto anual.
BloqueIngresosDTO pasado = conHistorialRealYOtrosEmpleadores(
bloque(historialAnio, r -> r.getBoletaMes() < mesCorte),
calculo.historialReal(), calculo.otrosEmpleadoresImporteAnterior());
boolean mesCorteReal = historialAnio.stream().anyMatch(r -> r.getBoletaMes().equals(mesCorte));
// Sin boleta real del mes de corte, se estima con sueldo bruto del mes de referencia
// (+ gratificación 109% si el mes de corte es julio o diciembre).
BloqueIngresosDTO actual = mesCorteReal
? bloque(historialAnio, r -> r.getBoletaMes().equals(mesCorte))
? conOtrosEmpleadores(bloque(historialAnio, r -> r.getBoletaMes().equals(mesCorte)), calculo.otrosEmpleadoresImporteMesActual())
: new BloqueIngresosDTO(
calculo.estimadoMesCorteSueldoBruto(),
BigDecimal.ZERO,
calculo.estimadoMesCorteGratificacion(),
BigDecimal.ZERO,
BigDecimal.ZERO,
calculo.estimadoMesCorteSueldoBruto().add(calculo.estimadoMesCorteGratificacion()));
calculo.otrosEmpleadoresImporteMesActual(),
calculo.estimadoMesCorteSueldoBruto()
.add(calculo.estimadoMesCorteGratificacion())
.add(calculo.otrosEmpleadoresImporteMesActual()));
BloqueIngresosDTO proyeccion = new BloqueIngresosDTO(
BloqueIngresosDTO proyeccion = conOtrosEmpleadores(
new BloqueIngresosDTO(
calculo.proyeccionSueldoBruto(),
BigDecimal.ZERO,
calculo.proyeccionGratificaciones(),
BigDecimal.ZERO,
BigDecimal.ZERO,
calculo.proyeccionFutura());
calculo.proyeccionFutura()),
calculo.otrosEmpleadoresImporteProyeccion());
BigDecimal ingresosAnuales = pasado.totalBrutoRemunerativo()
.add(actual.totalBrutoRemunerativo())
......@@ -113,9 +122,14 @@ public class RentaQuintaMapper {
calculo.valorUit().multiply(BigDecimal.valueOf(5)),
calculo.divisorMes());
// La liquidación anual arma "renta bruta anual" (ingresos anuales totales) solo con lo
// remunerativo propio, igual que la grilla; otros empleadores va aparte, en su propia
// fila, para no sumarlo dos veces.
LiquidacionImpuestoAnualDTO liquidacion = new LiquidacionImpuestoAnualDTO(
ingresosAnuales,
BigDecimal.ZERO,
calculo.rentaBrutaAnualSinOtrosEmpleadores(),
calculo.otrosEmpleadoresImporteAnterior()
.add(calculo.otrosEmpleadoresImporteMesActual())
.add(calculo.otrosEmpleadoresImporteProyeccion()),
deduccionLey,
calculo.rentaNeta(),
calculo.impuestoAnual());
......@@ -129,10 +143,21 @@ public class RentaQuintaMapper {
.filter(r -> r.getBoletaMes() < mesCorte)
.map(r -> orZero(r.getRenta5taCategoria()))
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal saldoPendiente = calculo.impuestoAnual().subtract(retenidoAnterior).max(BigDecimal.ZERO);
BigDecimal saldoPendiente = calculo.impuestoAnual()
.subtract(retenidoAnterior)
.subtract(calculo.otrosEmpleadoresRetencionAnterior())
.subtract(calculo.otrosEmpleadoresRetencionMesActual())
.subtract(calculo.otrosEmpleadoresRetencionProyeccion())
.max(BigDecimal.ZERO);
BigDecimal retencionOtrosEmpleadoresTotal = calculo.otrosEmpleadoresRetencionAnterior()
.add(calculo.otrosEmpleadoresRetencionMesActual())
.add(calculo.otrosEmpleadoresRetencionProyeccion());
ControlRetencionesDTO control = new ControlRetencionesDTO(
calculo.impuestoAnual(), retenidoAnterior, saldoPendiente,
calculo.divisorMes(), calculo.retencionDelMes());
calculo.impuestoAnual(), retenidoAnterior,
calculo.otrosEmpleadoresRetencionAnterior(), calculo.otrosEmpleadoresRetencionMesActual(),
calculo.otrosEmpleadoresRetencionProyeccion(),
retencionOtrosEmpleadoresTotal,
saldoPendiente, calculo.divisorMes(), calculo.retencionDelMes());
return new RentaQuintaDetalleCompletoDTO(toDetalle(boleta), registro, parametros, liquidacion, escala, control);
}
......@@ -194,6 +219,27 @@ public class RentaQuintaMapper {
return new BloqueIngresosDTO(sueldoBruto, horasExtras, gratificaciones, utilidades, BigDecimal.ZERO, total);
}
private BloqueIngresosDTO conHistorialRealYOtrosEmpleadores(BloqueIngresosDTO bloque, BigDecimal historialReal,
BigDecimal otrosEmpleadores) {
return new BloqueIngresosDTO(
bloque.sueldoBruto(),
bloque.horasExtras(),
bloque.gratificacionesOrdinarias(),
bloque.utilidades(),
otrosEmpleadores,
historialReal.add(otrosEmpleadores));
}
private BloqueIngresosDTO conOtrosEmpleadores(BloqueIngresosDTO bloque, BigDecimal otrosEmpleadores) {
return new BloqueIngresosDTO(
bloque.sueldoBruto(),
bloque.horasExtras(),
bloque.gratificacionesOrdinarias(),
bloque.utilidades(),
otrosEmpleadores,
bloque.totalBrutoRemunerativo().add(otrosEmpleadores));
}
private BigDecimal orZero(BigDecimal v) {
return v == null ? BigDecimal.ZERO : v;
}
......
......@@ -19,6 +19,8 @@ import so.planilla_be.rentaquinta.mapper.RentaQuintaMapper;
import so.planilla_be.rentaquinta.repository.DocumentoResumenProjection;
import so.planilla_be.rentaquinta.repository.RentaQuintaRepository;
import so.planilla_be.rentaquinta.service.RentaQuintaService;
import so.planilla_be.otrosempleadores.entity.RetencionOtrosEmpleadores;
import so.planilla_be.otrosempleadores.repository.RetencionOtrosEmpleadoresRepository;
import java.math.BigDecimal;
import java.util.Comparator;
......@@ -39,12 +41,15 @@ public class RentaQuintaServiceImpl implements RentaQuintaService {
private final RentaQuintaRepository repository;
private final RentaQuintaMapper mapper;
private final RentaQuintaCalculadora calculadora;
private final RetencionOtrosEmpleadoresRepository otrosEmpleadoresRepository;
public RentaQuintaServiceImpl(RentaQuintaRepository repository, RentaQuintaMapper mapper,
RentaQuintaCalculadora calculadora) {
RentaQuintaCalculadora calculadora,
RetencionOtrosEmpleadoresRepository otrosEmpleadoresRepository) {
this.repository = repository;
this.mapper = mapper;
this.calculadora = calculadora;
this.otrosEmpleadoresRepository = otrosEmpleadoresRepository;
}
@Override
......@@ -82,8 +87,11 @@ public class RentaQuintaServiceImpl implements RentaQuintaService {
.filter(Objects::nonNull)
.max(Integer::compareTo)
.orElse(null);
// Mismo total que el bloque "pasado real realizado" del detalle completo: planilla
// propia + lo declarado por otros empleadores en meses anteriores al de corte.
BigDecimal historialReal = a.historialReal().add(calculo.otrosEmpleadoresImporteAnterior());
return mapper.toResumen(a.referencia(), codper, filtro.periodo(), filtro.mesCorte(),
a.historialReal(), planillaCargada, a.retenidoAnterior(), calculo);
historialReal, planillaCargada, a.retenidoAnterior(), calculo);
});
}
......@@ -175,10 +183,50 @@ public class RentaQuintaServiceImpl implements RentaQuintaService {
BigDecimal asignacionFamiliarReferencia =
sumaMesCorte(historial, esMesCorte(mesReferencia), RentaQuinta::getAsignacionFamiliar);
OtrosEmpleadoresAgregado otrosEmpleadores = otrosEmpleadores(a.referencia().getNumeroDocumento(), periodo, mesCorte);
return calculadora.calcular(new DatosCalculo(
periodo, mesCorte, mesReferencia, a.historialReal(), a.planillaCargadaReal(),
sueldoBrutoReferencia, basicoReferencia, asignacionFamiliarReferencia, a.retenidoAnterior(),
a.julioPercibida(), a.diciembrePercibida()));
a.julioPercibida(), a.diciembrePercibida(),
otrosEmpleadores.importeAnterior(), otrosEmpleadores.importeMesActual(),
otrosEmpleadores.importeProyeccion(),
otrosEmpleadores.retencionAnterior(), otrosEmpleadores.retencionMesActual(),
otrosEmpleadores.retencionProyeccion()));
}
/**
* Importe y retención declarados por otros empleadores, separados en tres columnas: meses
* anteriores al de corte, el propio mes de corte, y meses posteriores (proyección) — estos
* últimos ya pueden estar cargados de antemano por el trabajador/empleador y afectan tanto la
* renta bruta anual como la retención del mes de corte, igual que los otros dos bloques.
*/
private OtrosEmpleadoresAgregado otrosEmpleadores(String numeroDocumento, Integer periodo, int mesCorte) {
List<RetencionOtrosEmpleadores> declaraciones =
otrosEmpleadoresRepository.findByDocumentoTrabajadorAndAnioOrderByMes(numeroDocumento, periodo);
BigDecimal importeAnterior = BigDecimal.ZERO;
BigDecimal importeMesActual = BigDecimal.ZERO;
BigDecimal importeProyeccion = BigDecimal.ZERO;
BigDecimal retencionAnterior = BigDecimal.ZERO;
BigDecimal retencionMesActual = BigDecimal.ZERO;
BigDecimal retencionProyeccion = BigDecimal.ZERO;
for (RetencionOtrosEmpleadores r : declaraciones) {
if (r.getMes() == null) {
continue;
}
if (r.getMes() < mesCorte) {
importeAnterior = importeAnterior.add(orZero(r.getImporte()));
retencionAnterior = retencionAnterior.add(orZero(r.getRetencion()));
} else if (r.getMes() == mesCorte) {
importeMesActual = importeMesActual.add(orZero(r.getImporte()));
retencionMesActual = retencionMesActual.add(orZero(r.getRetencion()));
} else {
importeProyeccion = importeProyeccion.add(orZero(r.getImporte()));
retencionProyeccion = retencionProyeccion.add(orZero(r.getRetencion()));
}
}
return new OtrosEmpleadoresAgregado(importeAnterior, importeMesActual, importeProyeccion,
retencionAnterior, retencionMesActual, retencionProyeccion);
}
private Predicate<RentaQuinta> esMesCorte(int mesCorte) {
......@@ -218,4 +266,13 @@ public class RentaQuintaServiceImpl implements RentaQuintaService {
boolean julioPercibida,
boolean diciembrePercibida
) {}
private record OtrosEmpleadoresAgregado(
BigDecimal importeAnterior,
BigDecimal importeMesActual,
BigDecimal importeProyeccion,
BigDecimal retencionAnterior,
BigDecimal retencionMesActual,
BigDecimal retencionProyeccion
) {}
}
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