modificado iva dependiendo de si es reducido o no y del lugar de la entrega

This commit is contained in:
2025-10-14 12:30:23 +02:00
parent d99ef65268
commit 543ff9a079
9 changed files with 195 additions and 62 deletions

View File

@ -35,7 +35,9 @@ public class HomeController {
"presupuesto.plantilla-cubierta",
"presupuesto.plantilla-cubierta-text",
"presupuesto.impresion-cubierta",
"presupuesto.impresion-cubierta-help");
"presupuesto.impresion-cubierta-help",
"presupuesto.iva-reducido",
"presupuesto.iva-reducido-descripcion");
Map<String, String> translations = translationService.getTranslations(locale, keys);
model.addAttribute("languageBundle", translations);

View File

@ -533,7 +533,9 @@ public class PresupuestoController {
"presupuesto.impresion-cubierta",
"presupuesto.impresion-cubierta-help",
"presupuesto.exito.guardado",
"presupuesto.add.error.save.title");
"presupuesto.add.error.save.title",
"presupuesto.iva-reducido",
"presupuesto.iva-reducido-descripcion");
Map<String, String> translations = translationService.getTranslations(locale, keys);
model.addAttribute("languageBundle", translations);
@ -734,13 +736,13 @@ public class PresupuestoController {
Map<String, Object> saveResult = presupuestoService.guardarPresupuesto(
presupuesto,
serviciosList,
datosMaquetacion,
serviciosList,
datosMaquetacion,
datosMarcapaginas,
mode,
cliente_id,
id,
request,
mode,
cliente_id,
id,
request,
locale);
return ResponseEntity.ok(Map.of("id", saveResult.get("presupuesto_id"),

View File

@ -105,6 +105,20 @@ public class Presupuesto extends AbstractAuditedEntity implements Cloneable {
}
}
public enum Entrega{
peninsula("presupuesto.entrega.peninsula"),
canarias("presupuesto.entrega.canarias"),
paises_ue("presupuesto.entrega.paises-ue");
private final String messageKey;
Entrega(String messageKey) {
this.messageKey = messageKey;
}
public String getMessageKey() {
return messageKey;
}
}
@Override
public Presupuesto clone() {
try {
@ -165,11 +179,18 @@ public class Presupuesto extends AbstractAuditedEntity implements Cloneable {
@Column(name = "base_imponible", precision = 12, scale = 2)
private BigDecimal baseImponible;
@Column(name = "iva_tipo", precision = 5, scale = 2)
private BigDecimal ivaTipo;
@Column(name = "iva_reducido")
private Boolean ivaReducido;
@Column(name = "iva_importe", precision = 12, scale = 2)
private BigDecimal ivaImporte;
@Column(name = "entrega_tipo")
@Enumerated(EnumType.STRING)
private Entrega entregaTipo;
@Column(name = "iva_importe_4", precision = 12, scale = 2)
private BigDecimal ivaImporte4;
@Column(name = "iva_importe_21", precision = 12, scale = 2)
private BigDecimal ivaImporte21;
@Column(name = "total_con_iva", precision = 12, scale = 2)
private BigDecimal totalConIva;
@ -481,20 +502,36 @@ public class Presupuesto extends AbstractAuditedEntity implements Cloneable {
this.baseImponible = baseImponible;
}
public BigDecimal getIvaTipo() {
return ivaTipo;
public Boolean getIvaReducido() {
return ivaReducido;
}
public void setIvaTipo(BigDecimal ivaTipo) {
this.ivaTipo = ivaTipo;
public void setIvaReducido(Boolean ivaReducido) {
this.ivaReducido = ivaReducido;
}
public BigDecimal getIvaImporte() {
return ivaImporte;
public Entrega getEntregaTipo() {
return entregaTipo;
}
public void setIvaImporte(BigDecimal ivaImporte) {
this.ivaImporte = ivaImporte;
public void setEntregaTipo(Entrega entregaTipo) {
this.entregaTipo = entregaTipo;
}
public BigDecimal getIvaImporte4() {
return ivaImporte4;
}
public void setIvaImporte4(BigDecimal ivaImporte4) {
this.ivaImporte4 = ivaImporte4;
}
public BigDecimal getIvaImporte21() {
return ivaImporte21;
}
public void setIvaImporte21(BigDecimal ivaImporte21) {
this.ivaImporte21 = ivaImporte21;
}
public BigDecimal getTotalConIva() {
@ -881,7 +918,4 @@ public class Presupuesto extends AbstractAuditedEntity implements Cloneable {
public void setId(Long id){
this.id = id;
}
}

View File

@ -4,13 +4,11 @@ import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.imprimelibros.erp.presupuesto.dto.Presupuesto;
import jakarta.persistence.criteria.CriteriaBuilder.In;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Service
public class PresupuestoFormDataMapper {
@ -39,6 +37,8 @@ public class PresupuestoFormDataMapper {
public String paginasColor = "";
public String posicionPaginasColor = "";
public String tipoEncuadernacion = "fresado"; // enum name
public String entregaTipo = "peninsula"; // enum name
public boolean ivaReducido = true;
}
// ===== Interior =====
@ -157,6 +157,9 @@ public class PresupuestoFormDataMapper {
vm.datosGenerales.tipoEncuadernacion = enumName(p.getTipoEncuadernacion(), "fresado");
vm.datosGenerales.entregaTipo = enumName(p.getEntregaTipo(), "peninsula");
vm.datosGenerales.ivaReducido = Boolean.TRUE.equals(p.getIvaReducido());
// ===== Interior
vm.interior.tipoImpresion = enumName(p.getTipoImpresion(), "negro");
vm.interior.papelInteriorId = nz(p.getPapelInteriorId(), 3);

View File

@ -453,9 +453,9 @@ public class PresupuestoService {
}
private String obtenerPrecioRetractilado(Integer tirada) {
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("tirada",tirada != null ? tirada : 0);
requestBody.put("tirada", tirada != null ? tirada : 0);
Double precio_retractilado = apiClient.getRetractilado(requestBody);
return precio_retractilado != null
? String.valueOf(Math.round(precio_retractilado * 100.0) / 100.0)
@ -878,8 +878,8 @@ public class PresupuestoService {
resumen.put("precio_total_tirada", presupuesto.getPrecioTotalTirada());
resumen.put("servicios_total", presupuesto.getServiciosTotal());
resumen.put("base_imponible", presupuesto.getBaseImponible());
resumen.put("iva_tipo", presupuesto.getIvaTipo());
resumen.put("iva_importe", presupuesto.getIvaImporte());
resumen.put("iva_importe_4", presupuesto.getIvaImporte4());
resumen.put("iva_importe_21", presupuesto.getIvaImporte21());
resumen.put("total_con_iva", presupuesto.getTotalConIva());
return resumen;
@ -925,6 +925,12 @@ public class PresupuestoService {
List<Double> precios = (List<Double>) data.getOrDefault("precios", List.of());
@SuppressWarnings("unchecked")
List<Double> pesos = (List<Double>) data.getOrDefault("peso", List.of());
boolean hayDepositoLegal = servicios != null && servicios.stream()
.map(m -> java.util.Objects.toString(m.get("id"), ""))
.map(String::trim)
.anyMatch("deposito-legal"::equals);
if (precios.isEmpty()) {
var preciosCalc = this.calcularPresupuesto(presupuesto, locale);
precios = (List<Double>) ((Map<String, Object>) preciosCalc.get("data")).getOrDefault("precios", List.of());
@ -956,19 +962,42 @@ public class PresupuestoService {
BigDecimal precioTotalTirada = BigDecimal.valueOf(precioUnit)
.multiply(BigDecimal.valueOf(cantidad))
.setScale(2, RoundingMode.HALF_UP);
if( hayDepositoLegal ){
precioTotalTirada = precioTotalTirada.add(BigDecimal.valueOf(precioUnit).multiply(BigDecimal.valueOf(4)));
}
// servicios_total
BigDecimal serviciosIva4 = BigDecimal.ZERO;
BigDecimal serviciosTotal = BigDecimal.ZERO;
if (servicios != null) {
for (Map<String, Object> s : servicios) {
try {
// retractilado o ejemplar-prueba: recalcular precio
// retractilado: recalcular precio
if (s.get("id").equals("retractilado")) {
double precio_retractilado = obtenerPrecioRetractilado(cantidad) != null
? Double.parseDouble(obtenerPrecioRetractilado(cantidad))
: 0.0;
s.put("price", precio_retractilado);
}
}
// si tiene protitipo, guardamos el valor para el IVA al 4%
else if (s.get("id").equals("ejemplar-prueba")) {
serviciosIva4 = BigDecimal.valueOf(
s.get("price") != null ? Double.parseDouble(String.valueOf(s.get("price"))) : 0.0);
} else if (s.get("id").equals("marcapaginas")) {
PresupuestoMarcapaginas pm = presupuesto.getDatosMarcapaginasJson() != null
? new ObjectMapper().readValue(presupuesto.getDatosMarcapaginasJson(),
PresupuestoMarcapaginas.class)
: null;
Map<String, Object> precio_marcapaginas = this.getPrecioMarcapaginas(pm, locale);
s.put("price", precio_marcapaginas.getOrDefault("precio_total", 0.0));
} else if (s.get("id").equals("maquetacion")) {
PresupuestoMaquetacion pm = presupuesto.getDatosMaquetacionJson() != null
? new ObjectMapper().readValue(presupuesto.getDatosMaquetacionJson(),
PresupuestoMaquetacion.class)
: null;
Map<String, Object> precio_maquetacion = this.getPrecioMaquetacion(pm, locale);
s.put("price", precio_maquetacion.getOrDefault("precio", 0.0));
}
double unidades = Double.parseDouble(String.valueOf(s.getOrDefault("units", 0)));
double precio = Double.parseDouble(String.valueOf(
s.get("id").equals("marcapaginas")
@ -985,17 +1014,30 @@ public class PresupuestoService {
}
}
// base imponible, IVA y total (si tienes IVA configurable, úsalo; si no, 0)
BigDecimal baseImponible = precioTotalTirada.add(serviciosTotal);
BigDecimal ivaTipo = BigDecimal.ZERO;
try {
double iva = 4.0; // 0..100
ivaTipo = BigDecimal.valueOf(iva);
} catch (Exception ignore) {
}
BigDecimal ivaImporte = baseImponible.multiply(ivaTipo).divide(BigDecimal.valueOf(100), 2,
RoundingMode.HALF_UP);
BigDecimal totalConIva = baseImponible.add(ivaImporte);
BigDecimal baseImponible = precioTotalTirada;
BigDecimal ivaImporte4 = BigDecimal.ZERO;
BigDecimal ivaImporte21 = BigDecimal.ZERO;
// Si la entrega es en peninsula, se mira el valor del iva
// Canarias y paises UE no llevan IVA
if (presupuesto.getEntregaTipo() == Presupuesto.Entrega.peninsula){
// Si el iva es reducido, el precio de la tirada y el del prototipo llevan IVA
// 4%
if (presupuesto.getIvaReducido()) {
ivaImporte4 = baseImponible.add(serviciosIva4).multiply(BigDecimal.valueOf(4)).divide(
BigDecimal.valueOf(100), 2,
RoundingMode.HALF_UP);
ivaImporte21 = serviciosTotal.subtract(serviciosIva4).multiply(BigDecimal.valueOf(21)).divide(
BigDecimal.valueOf(100), 2,
RoundingMode.HALF_UP);
} else {
ivaImporte21 = baseImponible.add(serviciosTotal).multiply(BigDecimal.valueOf(21)).divide(
BigDecimal.valueOf(100), 2,
RoundingMode.HALF_UP);
}
}
baseImponible = baseImponible.add(serviciosTotal);
BigDecimal totalConIva = baseImponible.add(ivaImporte21).add(ivaImporte4);
// precios y totales
if (tirada == (presupuesto.getSelectedTirada() != null ? presupuesto.getSelectedTirada() : 0)) {
@ -1003,8 +1045,8 @@ public class PresupuestoService {
presupuesto.setPrecioTotalTirada(precioTotalTirada);
presupuesto.setServiciosTotal(serviciosTotal);
presupuesto.setBaseImponible(baseImponible);
presupuesto.setIvaTipo(ivaTipo);
presupuesto.setIvaImporte(ivaImporte);
presupuesto.setIvaImporte4(ivaImporte4);
presupuesto.setIvaImporte21(ivaImporte21);
presupuesto.setTotalConIva(totalConIva);
}
Map<String, Object> snap = new HashMap<>();
@ -1012,8 +1054,8 @@ public class PresupuestoService {
snap.put("precio_total_tirada", precioTotalTirada);
snap.put("servicios_total", serviciosTotal);
snap.put("base_imponible", baseImponible);
snap.put("iva_tipo", ivaTipo);
snap.put("iva_importe", ivaImporte);
snap.put("iva_importe_4", ivaImporte4);
snap.put("iva_importe_21", ivaImporte21);
snap.put("total_con_iva", totalConIva);
snap.put("peso", (index >= 0 && index < pesos.size()) ? pesos.get(index) : 0.0);
@ -1189,8 +1231,10 @@ public class PresupuestoService {
target.setPrecioTotalTirada(src.getPrecioTotalTirada());
target.setServiciosTotal(src.getServiciosTotal());
target.setBaseImponible(src.getBaseImponible());
target.setIvaTipo(src.getIvaTipo());
target.setIvaImporte(src.getIvaImporte());
target.setIvaReducido(src.getIvaReducido());
target.setEntregaTipo(src.getEntregaTipo());
target.setIvaImporte4(src.getIvaImporte4());
target.setIvaImporte21(src.getIvaImporte21());
target.setTotalConIva(src.getTotalConIva());
target.setCreatedBy(target.getCreatedBy() == null ? src.getCreatedBy() : target.getCreatedBy()); // no pisar si
// ya existe