diff --git a/src/main/java/com/imprimelibros/erp/home/HomeController.java b/src/main/java/com/imprimelibros/erp/home/HomeController.java index 1538327..4499dfb 100644 --- a/src/main/java/com/imprimelibros/erp/home/HomeController.java +++ b/src/main/java/com/imprimelibros/erp/home/HomeController.java @@ -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 translations = translationService.getTranslations(locale, keys); model.addAttribute("languageBundle", translations); diff --git a/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoController.java b/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoController.java index 36e65fa..a2e7cbe 100644 --- a/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoController.java +++ b/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoController.java @@ -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 translations = translationService.getTranslations(locale, keys); model.addAttribute("languageBundle", translations); @@ -734,13 +736,13 @@ public class PresupuestoController { Map 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"), diff --git a/src/main/java/com/imprimelibros/erp/presupuesto/dto/Presupuesto.java b/src/main/java/com/imprimelibros/erp/presupuesto/dto/Presupuesto.java index 1f1088f..da150ba 100644 --- a/src/main/java/com/imprimelibros/erp/presupuesto/dto/Presupuesto.java +++ b/src/main/java/com/imprimelibros/erp/presupuesto/dto/Presupuesto.java @@ -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; } - - - } diff --git a/src/main/java/com/imprimelibros/erp/presupuesto/service/PresupuestoFormDataMapper.java b/src/main/java/com/imprimelibros/erp/presupuesto/service/PresupuestoFormDataMapper.java index 7b37bd8..2e27e95 100644 --- a/src/main/java/com/imprimelibros/erp/presupuesto/service/PresupuestoFormDataMapper.java +++ b/src/main/java/com/imprimelibros/erp/presupuesto/service/PresupuestoFormDataMapper.java @@ -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); diff --git a/src/main/java/com/imprimelibros/erp/presupuesto/service/PresupuestoService.java b/src/main/java/com/imprimelibros/erp/presupuesto/service/PresupuestoService.java index be70a78..9cf63d2 100644 --- a/src/main/java/com/imprimelibros/erp/presupuesto/service/PresupuestoService.java +++ b/src/main/java/com/imprimelibros/erp/presupuesto/service/PresupuestoService.java @@ -453,9 +453,9 @@ public class PresupuestoService { } private String obtenerPrecioRetractilado(Integer tirada) { - + Map 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 precios = (List) data.getOrDefault("precios", List.of()); @SuppressWarnings("unchecked") List pesos = (List) 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) ((Map) 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 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 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 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 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 diff --git a/src/main/resources/i18n/presupuesto_es.properties b/src/main/resources/i18n/presupuesto_es.properties index f05835b..bf7f2c8 100644 --- a/src/main/resources/i18n/presupuesto_es.properties +++ b/src/main/resources/i18n/presupuesto_es.properties @@ -206,7 +206,8 @@ presupuesto.resumen.tabla.cantidad=Cantidad presupuesto.resumen.tabla.precio-unidad=Precio/unidad presupuesto.resumen.tabla.precio-total=Precio total presupuesto.resumen.tabla.base=Base -presupuesto.resumen.tabla.iva=I.V.A. (4%) +presupuesto.resumen.tabla.iva4=I.V.A. (4%) +presupuesto.resumen.tabla.iva21=I.V.A. (21%) presupuesto.resumen.tabla.total=Total presupuesto presupuesto.resumen-texto=Impresion de {0} unidades encuadernadas en {1} en {2} con {3} páginas en formato {4} x {5} mm. \
    \ diff --git a/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/wizard.js b/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/wizard.js index 19968d6..1234501 100644 --- a/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/wizard.js +++ b/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/wizard.js @@ -33,6 +33,8 @@ export default class PresupuestoWizard { paginasColor: 0, posicionPaginasColor: '', tipoEncuadernacion: 'fresado', + entregaTipo: 'peninsula', + ivaReducido: true, }, interior: { tipoImpresion: 'negro', @@ -122,6 +124,9 @@ export default class PresupuestoWizard { this.divPosicionPaginasColor = $('#div-posicion-paginas-color'); this.posicionPaginasColor = $('#posicionPaginasColor'); this.paginas = $('#paginas'); + this.entregaTipo = $('#entregaTipo'); + this.ivaReducido = $('#iva-reducido'); + this.btnIvaReducidoDetail = $('#btn-iva-reducido-detail'); this.btn_next_datos_generales = $('#next-datos-generales'); this.datos_generales_alert = $('#datos-generales-alert'); @@ -393,6 +398,27 @@ export default class PresupuestoWizard { ******************************/ #initDatosGenerales() { + this.btnIvaReducidoDetail.on('click', () => { + Swal.fire({ + position: 'top-end', + icon: 'info', + title: window.languageBundle.get('presupuesto.iva-reducido'), + html: ` +
    + ${window.languageBundle.get('presupuesto.iva-reducido-descripcion')} +
    + `, + confirmButtonClass: 'btn btn-primary w-xs mt-2', + showConfirmButton: false, + showCloseButton: true, + buttonsStyling: false, + customClass: { + confirmButton: 'btn btn-secondary me-2', // clases para el botón confirmar + cancelButton: 'btn btn-light' // clases para cancelar + }, + }); + }); + $('.datos-generales-data').on('change', () => { const dataToStore = this.#getDatosGeneralesData(); this.#updateDatosGeneralesData(dataToStore); @@ -537,6 +563,8 @@ export default class PresupuestoWizard { paginasColor: this.paginasColor.val(), posicionPaginasColor: this.posicionPaginasColor.val(), tipoEncuadernacion: $('.tipo-libro input:checked').val() || 'fresado', + entregaTipo: this.entregaTipo.val(), + ivaReducido: this.ivaReducido.is(':checked'), }; } @@ -557,6 +585,8 @@ export default class PresupuestoWizard { paginasColor: data.paginasColor, posicionPaginasColor: data.posicionPaginasColor, tipoEncuadernacion: data.tipoEncuadernacion, + entregaTipo: data.entregaTipo, + ivaReducido: data.ivaReducido, }; } @@ -602,6 +632,9 @@ export default class PresupuestoWizard { this.formato.val(option.val()).trigger('change'); } } + + this.entregaTipo.val(this.formData.datosGenerales.entregaTipo); + this.ivaReducido.prop('checked', this.formData.datosGenerales.ivaReducido); } #getTamanio() { @@ -1752,8 +1785,6 @@ export default class PresupuestoWizard { }); const servicios = data.servicios || []; - let total = 0; - const locale = document.documentElement.lang || 'es-ES'; for (const l of lineas) { @@ -1766,7 +1797,6 @@ export default class PresupuestoWizard { ${formateaMoneda(data[l].precio_total, 2, locale)} `; - total += data[l].precio_total; this.tablaResumen.find('tbody').append(row); } for (const s of servicios) { @@ -1779,13 +1809,26 @@ export default class PresupuestoWizard { ${s.id === "marcapaginas" ? formateaMoneda(s.precio * s.unidades, 2, locale) : formateaMoneda(s.precio, 2, locale)} `; - total += s.precio; this.tablaResumen.find('tbody').append(row); } - $('#resumen-base').text(formateaMoneda(total, 2, locale)); - $('#resumen-iva').text(formateaMoneda(total * 0.04, 2, locale)); - $('#resumen-total').text(formateaMoneda(total * 1.04, 2, locale)); + $('#resumen-base').text(formateaMoneda(data.base_imponible, 2, locale)); + if(data.iva_importe_4 > 0) { + $('#tr-resumen-iva4').removeClass('d-none'); + $('#resumen-iva4').text(formateaMoneda(data.iva_importe_4, 2, locale)); + } + else{ + $('#tr-resumen-iva4').addClass('d-none'); + $('#resumen-iva4').text(formateaMoneda(0, 2, locale)); + } + if(data.iva_importe_21 > 0) { + $('#tr-resumen-iva21').removeClass('d-none'); + $('#resumen-iva21').text(formateaMoneda(data.iva_importe_21, 2, locale)); + } else { + $('#tr-resumen-iva21').addClass('d-none'); + $('#resumen-iva21').text(formateaMoneda(0, 2, locale)); + } + $('#resumen-total').text(formateaMoneda(data.total_con_iva, 2, locale)); } /****************************** diff --git a/src/main/resources/templates/imprimelibros/presupuestos/presupuestador-items/_datos-generales.html b/src/main/resources/templates/imprimelibros/presupuestos/presupuestador-items/_datos-generales.html index 2d3416e..5dd5076 100644 --- a/src/main/resources/templates/imprimelibros/presupuestos/presupuestador-items/_datos-generales.html +++ b/src/main/resources/templates/imprimelibros/presupuestos/presupuestador-items/_datos-generales.html @@ -270,7 +270,7 @@
    + name="iva-reducido" checked/>