mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-23 09:10:22 +00:00
Compare commits
16 Commits
bb6baf96ce
...
096e5387c4
| Author | SHA1 | Date | |
|---|---|---|---|
| 096e5387c4 | |||
| bdfafea458 | |||
| 0d205f9488 | |||
| c1df92b840 | |||
| 85681b4d6e | |||
| 479cecf52b | |||
| 62d67012be | |||
| 9ae6edb727 | |||
| 42fa347829 | |||
| b5275f89f7 | |||
| 6d88608612 | |||
| 796a0ccb6e | |||
| 2c8d6bc1b7 | |||
| bff560f159 | |||
| 2798316745 | |||
| cc8b30add6 |
8
pom.xml
8
pom.xml
@ -28,7 +28,7 @@
|
||||
<url />
|
||||
</scm>
|
||||
<properties>
|
||||
<java.version>24</java.version>
|
||||
<java.version>21</java.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
@ -92,6 +92,12 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<version>1.17.2</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
package com.imprimelibros.erp.common;
|
||||
|
||||
import jakarta.persistence.AttributeConverter;
|
||||
import jakarta.persistence.Converter;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.safety.Safelist;
|
||||
|
||||
@Converter(autoApply = false)
|
||||
public class HtmlStripConverter implements AttributeConverter<String, String> {
|
||||
|
||||
@Override
|
||||
public String convertToDatabaseColumn(String attribute) {
|
||||
return attribute == null ? null : Jsoup.clean(attribute, Safelist.none());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertToEntityAttribute(String dbData) {
|
||||
return dbData; // tal cual
|
||||
}
|
||||
}
|
||||
13
src/main/java/com/imprimelibros/erp/config/WebConfig.java
Normal file
13
src/main/java/com/imprimelibros/erp/config/WebConfig.java
Normal file
@ -0,0 +1,13 @@
|
||||
package com.imprimelibros.erp.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.web.servlet.resource.ResourceUrlEncodingFilter;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class WebConfig {
|
||||
@Bean
|
||||
public ResourceUrlEncodingFilter resourceUrlEncodingFilter() {
|
||||
return new ResourceUrlEncodingFilter();
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,10 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.imprimelibros.erp.presupuesto.Presupuesto.TipoCubierta;
|
||||
import com.imprimelibros.erp.presupuesto.Presupuesto.TipoEncuadernacion;
|
||||
import com.imprimelibros.erp.presupuesto.Presupuesto.TipoImpresion;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@Component
|
||||
@ -19,4 +23,16 @@ public class TranslationService {
|
||||
}
|
||||
return translations;
|
||||
}
|
||||
|
||||
public String label(TipoEncuadernacion t, Locale locale) {
|
||||
return messageSource.getMessage(t.getMessageKey(), null, locale);
|
||||
}
|
||||
public String label(TipoImpresion t, Locale locale) {
|
||||
return messageSource.getMessage(t.getMessageKey(), null, locale);
|
||||
}
|
||||
public String label(TipoCubierta t, Locale locale) {
|
||||
return messageSource.getMessage(t.getMessageKey(), null, locale);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -8,6 +8,8 @@ import com.imprimelibros.erp.presupuesto.validation.Par;
|
||||
import com.imprimelibros.erp.presupuesto.validation.PresupuestoValidationGroups;
|
||||
import com.imprimelibros.erp.presupuesto.validation.Tamanio;
|
||||
|
||||
import com.imprimelibros.erp.common.HtmlStripConverter;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
@ConsistentTiradas(groups = PresupuestoValidationGroups.DatosGenerales.class)
|
||||
@ -17,15 +19,54 @@ import jakarta.persistence.*;
|
||||
public class Presupuesto implements Cloneable{
|
||||
|
||||
public enum TipoEncuadernacion {
|
||||
fresado, cosido, grapado, espiral, wireo
|
||||
fresado("presupuesto.fresado"),
|
||||
cosido("presupuesto.cosido"),
|
||||
grapado("presupuesto.grapado"),
|
||||
espiral("presupuesto.espiral"),
|
||||
wireo("presupuesto.wireo");
|
||||
|
||||
private final String messageKey;
|
||||
|
||||
TipoEncuadernacion(String messageKey) {
|
||||
this.messageKey = messageKey;
|
||||
}
|
||||
|
||||
public String getMessageKey() {
|
||||
return messageKey;
|
||||
}
|
||||
}
|
||||
|
||||
public enum TipoImpresion {
|
||||
negro, negrohq, color, colorhq
|
||||
negro("presupuesto.blanco-negro"),
|
||||
negrohq("presupuesto.blanco-negro-premium"),
|
||||
color("presupuesto.color"),
|
||||
colorhq("presupuesto.color-premium");
|
||||
|
||||
private final String messageKey;
|
||||
|
||||
TipoImpresion(String messageKey) {
|
||||
this.messageKey = messageKey;
|
||||
}
|
||||
|
||||
public String getMessageKey() {
|
||||
return messageKey;
|
||||
}
|
||||
}
|
||||
|
||||
public enum TipoCubierta {
|
||||
tapaBlanda, tapaDura, tapaDuraLomoRedondo
|
||||
tapaBlanda("presupuesto.tapa-blanda"),
|
||||
tapaDura("presupuesto.tapa-dura"),
|
||||
tapaDuraLomoRedondo("presupuesto.tapa-dura-lomo-redondo");
|
||||
|
||||
private final String messageKey;
|
||||
|
||||
TipoCubierta(String messageKey) {
|
||||
this.messageKey = messageKey;
|
||||
}
|
||||
|
||||
public String getMessageKey() {
|
||||
return messageKey;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -42,16 +83,20 @@ public class Presupuesto implements Cloneable{
|
||||
private Long id;
|
||||
|
||||
@NotNull(message = "{presupuesto.errores.tipo-encuadernacion}", groups = PresupuestoValidationGroups.DatosGenerales.class)
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "tipo_encuadernacion")
|
||||
private TipoEncuadernacion tipoEncuadernacion = TipoEncuadernacion.fresado;
|
||||
|
||||
@Convert(converter = HtmlStripConverter.class)
|
||||
@NotBlank(message = "{presupuesto.errores.titulo}", groups = PresupuestoValidationGroups.DatosGenerales.class)
|
||||
@Column(name = "titulo")
|
||||
private String titulo;
|
||||
|
||||
@Convert(converter = HtmlStripConverter.class)
|
||||
@Column(name = "autor")
|
||||
private String autor;
|
||||
|
||||
@Convert(converter = HtmlStripConverter.class)
|
||||
@Column(name = "isbn")
|
||||
private String isbn;
|
||||
|
||||
@ -99,6 +144,7 @@ public class Presupuesto implements Cloneable{
|
||||
private Integer paginasColorTotal;
|
||||
|
||||
@NotNull(message = "{presupuesto.errores.tipo-impresion}", groups = PresupuestoValidationGroups.Interior.class)
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "tipo_impresion")
|
||||
private TipoImpresion tipoImpresion = TipoImpresion.negro;
|
||||
|
||||
@ -111,6 +157,7 @@ public class Presupuesto implements Cloneable{
|
||||
private Integer gramajeInterior;
|
||||
|
||||
@NotNull(message = "{presupuesto.errores.tipo-cubierta}", groups = PresupuestoValidationGroups.Cubierta.class)
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "tipo_cubierta")
|
||||
private TipoCubierta tipoCubierta = TipoCubierta.tapaBlanda;
|
||||
|
||||
@ -530,4 +577,16 @@ public class Presupuesto implements Cloneable{
|
||||
public void setPresupuestoMaquetacionData(String presupuestoMaquetacionData) {
|
||||
this.presupuestoMaquetacionData = presupuestoMaquetacionData;
|
||||
}
|
||||
|
||||
public String resumenPresupuesto() {
|
||||
return String.format("%s - %s - %dx%d mm - %d Páginas (N:%d C:%d) - Tira:%d",
|
||||
this.titulo,
|
||||
this.tipoEncuadernacion,
|
||||
this.ancho,
|
||||
this.alto,
|
||||
this.paginasNegro + this.paginasColorTotal,
|
||||
this.paginasNegro,
|
||||
this.paginasColorTotal,
|
||||
this.selectedTirada != null ? this.selectedTirada : 0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ package com.imprimelibros.erp.presupuesto;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
@ -18,14 +19,14 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.imprimelibros.erp.externalApi.skApiClient;
|
||||
import com.imprimelibros.erp.presupuesto.classes.ImagenPresupuesto;
|
||||
import com.imprimelibros.erp.presupuesto.classes.PresupuestoMaquetacion;
|
||||
import com.imprimelibros.erp.presupuesto.classes.PresupuestoMarcapaginas;
|
||||
import com.imprimelibros.erp.presupuesto.validation.PresupuestoValidationGroups;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
@ -43,6 +44,12 @@ public class PresupuestoController {
|
||||
@Autowired
|
||||
protected MessageSource messageSource;
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public PresupuestoController(ObjectMapper objectMapper){
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@PostMapping("/public/validar/datos-generales")
|
||||
public ResponseEntity<?> validarDatosGenerales(
|
||||
@Validated(PresupuestoValidationGroups.DatosGenerales.class) Presupuesto presupuesto,
|
||||
@ -114,21 +121,14 @@ public class PresupuestoController {
|
||||
|
||||
if (calcular) {
|
||||
|
||||
HashMap<String, Object> price = new HashMap<>();
|
||||
String priceStr = apiClient.getPrice(presupuestoService.toSkApiRequest(presupuesto));
|
||||
HashMap<String, Object> price = presupuestoService.calcularPresupuesto(presupuesto, locale);
|
||||
|
||||
try {
|
||||
price = new ObjectMapper().readValue(priceStr, new TypeReference<>() {
|
||||
});
|
||||
} catch (JsonProcessingException e) {
|
||||
price = new HashMap<>();
|
||||
price.put("error", messageSource.getMessage("presupuesto.error-obtener-precio", null, locale));
|
||||
}
|
||||
if (!price.containsKey("data")) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(messageSource.getMessage("presupuesto.error-obtener-precio", null, locale));
|
||||
}
|
||||
return ResponseEntity.ok(price.get("data"));
|
||||
|
||||
}
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
@ -152,7 +152,7 @@ public class PresupuestoController {
|
||||
|
||||
Map<String, Object> resultado = new HashMap<>();
|
||||
// servicios extra
|
||||
resultado.putAll(presupuestoService.obtenerServiciosExtras(presupuesto, locale, apiClient));
|
||||
resultado.putAll(presupuestoService.obtenerServiciosExtras(presupuesto, locale));
|
||||
Map<String, String> language = new HashMap<>();
|
||||
language.put("calcular", messageSource.getMessage("presupuesto.calcular", null, locale));
|
||||
resultado.put("language", language);
|
||||
@ -344,4 +344,45 @@ public class PresupuestoController {
|
||||
return ResponseEntity.ok(resultado);
|
||||
}
|
||||
|
||||
@GetMapping(value = "/public/marcapaginas/form", produces = MediaType.TEXT_HTML_VALUE)
|
||||
public String getMarcapaginasForm(Model model) {
|
||||
model.addAttribute("presupuestoMarcapaginas", new PresupuestoMarcapaginas());
|
||||
return "imprimelibros/presupuestos/presupuesto-marcapaginas-form :: marcapaginasForm";
|
||||
}
|
||||
|
||||
@GetMapping("/public/marcapaginas")
|
||||
public ResponseEntity<?> getPresupuestoMarcapaginas(
|
||||
@Valid @ModelAttribute PresupuestoMarcapaginas presupuestoMarcapaginas,
|
||||
BindingResult result,
|
||||
Locale locale) {
|
||||
|
||||
if (result.hasErrors()) {
|
||||
// Construimos un mapa field -> mensaje para tu AJAX
|
||||
Map<String, String> errores = result.getFieldErrors().stream()
|
||||
.collect(java.util.stream.Collectors.toMap(
|
||||
fe -> fe.getField(),
|
||||
fe -> fe.getDefaultMessage(),
|
||||
(a, b) -> a));
|
||||
return ResponseEntity.badRequest().body(errores);
|
||||
}
|
||||
|
||||
Map<String, Object> resultado = presupuestoService.getPrecioMarcapaginas(presupuestoMarcapaginas, locale);
|
||||
if ((Double) resultado.get("precio_total") == 0.0 && (Double) resultado.get("precio_unitario") == 0.0) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(messageSource.getMessage("presupuesto.errores.presupuesto-marcapaginas", null, locale));
|
||||
}
|
||||
return ResponseEntity.ok(resultado);
|
||||
}
|
||||
|
||||
// Se hace un post para no tener problemas con la longitud de la URL
|
||||
@PostMapping("/public/resumen")
|
||||
public ResponseEntity<?> getResumen(@RequestBody Map<String, Object> body, Locale locale) {
|
||||
Presupuesto p = objectMapper.convertValue(body.get("presupuesto"), Presupuesto.class);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> serviciosList = (List<Map<String, Object>>) body.getOrDefault("servicios", List.of());
|
||||
|
||||
return ResponseEntity.ok(presupuestoService.getResumen(p, serviciosList, locale));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -6,12 +6,14 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.Locale;
|
||||
import java.text.NumberFormat;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
@ -25,11 +27,14 @@ import com.imprimelibros.erp.configurationERP.VariableService;
|
||||
import com.imprimelibros.erp.presupuesto.Presupuesto.TipoCubierta;
|
||||
import com.imprimelibros.erp.presupuesto.classes.ImagenPresupuesto;
|
||||
import com.imprimelibros.erp.presupuesto.classes.PresupuestadorItems;
|
||||
import com.imprimelibros.erp.presupuesto.classes.PresupuestoFormatter;
|
||||
import com.imprimelibros.erp.presupuesto.maquetacion.MaquetacionPrecios;
|
||||
import com.imprimelibros.erp.presupuesto.maquetacion.MaquetacionPreciosRepository;
|
||||
import com.imprimelibros.erp.presupuesto.marcapaginas.Marcapaginas;
|
||||
import com.imprimelibros.erp.presupuesto.classes.PresupuestoMaquetacion;
|
||||
import com.imprimelibros.erp.presupuesto.maquetacion.MaquetacionMatrices;
|
||||
import com.imprimelibros.erp.presupuesto.classes.PresupuestoMarcapaginas;
|
||||
import com.imprimelibros.erp.presupuesto.maquetacion.MaquetacionMatricesRepository;
|
||||
import com.imprimelibros.erp.presupuesto.marcapaginas.MarcapaginasRepository;
|
||||
import com.imprimelibros.erp.externalApi.skApiClient;
|
||||
|
||||
@Service
|
||||
@ -41,19 +46,23 @@ public class PresupuestoService {
|
||||
@Autowired
|
||||
protected MessageSource messageSource;
|
||||
|
||||
@Autowired
|
||||
protected skApiClient skApiClient;
|
||||
|
||||
@Autowired
|
||||
protected MaquetacionPreciosRepository maquetacionPreciosRepository;
|
||||
|
||||
@Autowired
|
||||
protected MaquetacionMatricesRepository maquetacionMatricesRepository;
|
||||
|
||||
private final PresupuestadorItems presupuestadorItems;
|
||||
@Autowired
|
||||
protected MarcapaginasRepository marcapaginasRepository;
|
||||
|
||||
public PresupuestoService(PresupuestadorItems presupuestadorItems) {
|
||||
private final PresupuestadorItems presupuestadorItems;
|
||||
private final PresupuestoFormatter presupuestoFormatter;
|
||||
private final skApiClient apiClient;
|
||||
|
||||
public PresupuestoService(PresupuestadorItems presupuestadorItems, PresupuestoFormatter presupuestoFormatter, skApiClient apiClient) {
|
||||
this.presupuestadorItems = presupuestadorItems;
|
||||
this.presupuestoFormatter = presupuestoFormatter;
|
||||
this.apiClient = apiClient;
|
||||
}
|
||||
|
||||
public boolean validateDatosGenerales(int[] tiradas) {
|
||||
@ -296,15 +305,6 @@ public class PresupuestoService {
|
||||
"cabezada", presupuesto.getCabezada(),
|
||||
"lomoRedondo", presupuesto.getTipoCubierta() == TipoCubierta.tapaDuraLomoRedondo ? 1 : 0);
|
||||
|
||||
/*
|
||||
* Map<String, Object> servicios = Map.of(
|
||||
* "retractilado", 0,
|
||||
* "retractilado5", 0,
|
||||
* "ferro", 0,
|
||||
* "ferroDigital", 0,
|
||||
* "marcapaginas", 0,
|
||||
* "prototipo", 0);
|
||||
*/
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("tipo_impresion_id", this.getTipoImpresionId(presupuesto));
|
||||
body.put("tirada", Arrays.stream(presupuesto.getTiradas())
|
||||
@ -454,18 +454,18 @@ public class PresupuestoService {
|
||||
Map<String, Object> requestBody = new HashMap<>();
|
||||
requestBody.put("tirada",
|
||||
presupuesto.getSelectedTirada() != null ? presupuesto.getSelectedTirada() : tirada_min);
|
||||
Double precio_retractilado = skApiClient.getRetractilado(requestBody);
|
||||
Double precio_retractilado = apiClient.getRetractilado(requestBody);
|
||||
return precio_retractilado != null
|
||||
? NumberFormat.getNumberInstance(locale)
|
||||
.format(Math.round(precio_retractilado * 100.0) / 100.0)
|
||||
: "0,00";
|
||||
}
|
||||
|
||||
public Map<String, Object> obtenerServiciosExtras(Presupuesto presupuesto, Locale locale, skApiClient apiClient) {
|
||||
public Map<String, Object> obtenerServiciosExtras(Presupuesto presupuesto, Locale locale) {
|
||||
|
||||
List<Object> opciones = new ArrayList<>();
|
||||
|
||||
Double price_prototipo = this.obtenerPrototipo(presupuesto, apiClient);
|
||||
Double price_prototipo = this.obtenerPrototipo(presupuesto);
|
||||
|
||||
opciones.add(new HashMap<String, String>() {
|
||||
{
|
||||
@ -562,7 +562,7 @@ public class PresupuestoService {
|
||||
return response;
|
||||
}
|
||||
|
||||
private Double obtenerPrototipo(Presupuesto presupuesto, skApiClient apiClient) {
|
||||
private Double obtenerPrototipo(Presupuesto presupuesto) {
|
||||
|
||||
// Obtenemos el precio de 1 unidad para el ejemplar de prueba
|
||||
HashMap<String, Object> price = new HashMap<>();
|
||||
@ -607,7 +607,8 @@ public class PresupuestoService {
|
||||
BigDecimal precio = BigDecimal.ZERO;
|
||||
|
||||
// millar_maquetacion * (numCaracteres / 1000.0)
|
||||
BigDecimal millares = BigDecimal.valueOf(presupuestoMaquetacion.getNumCaracteres()).divide(BigDecimal.valueOf(1000), 6,
|
||||
BigDecimal millares = BigDecimal.valueOf(presupuestoMaquetacion.getNumCaracteres()).divide(
|
||||
BigDecimal.valueOf(1000), 6,
|
||||
RoundingMode.HALF_UP);
|
||||
precio = precio.add(millares.multiply(BigDecimal.valueOf(price.apply("millar_maquetacion"))));
|
||||
|
||||
@ -630,11 +631,14 @@ public class PresupuestoService {
|
||||
|
||||
// tabla, columna, foto
|
||||
precio = precio
|
||||
.add(BigDecimal.valueOf(presupuestoMaquetacion.getNumTablas()).multiply(BigDecimal.valueOf(price.apply("tabla"))));
|
||||
.add(BigDecimal.valueOf(presupuestoMaquetacion.getNumTablas())
|
||||
.multiply(BigDecimal.valueOf(price.apply("tabla"))));
|
||||
precio = precio.add(
|
||||
BigDecimal.valueOf(presupuestoMaquetacion.getNumColumnas()).multiply(BigDecimal.valueOf(price.apply("columnas"))));
|
||||
BigDecimal.valueOf(presupuestoMaquetacion.getNumColumnas())
|
||||
.multiply(BigDecimal.valueOf(price.apply("columnas"))));
|
||||
precio = precio
|
||||
.add(BigDecimal.valueOf(presupuestoMaquetacion.getNumFotos()).multiply(BigDecimal.valueOf(price.apply("foto"))));
|
||||
.add(BigDecimal.valueOf(presupuestoMaquetacion.getNumFotos())
|
||||
.multiply(BigDecimal.valueOf(price.apply("foto"))));
|
||||
|
||||
if (presupuestoMaquetacion.isCorreccionOrtotipografica()) {
|
||||
precio = precio
|
||||
@ -658,7 +662,12 @@ public class PresupuestoService {
|
||||
out.put("numPaginasEstimadas", numPaginas);
|
||||
out.put("precioPaginaEstimado", precioPaginaEstimado);
|
||||
HashMap<String, String> language = new HashMap<>();
|
||||
language.put("add_to_presupuesto", messageSource.getMessage("presupuesto.add-to-presupuesto", null, locale));
|
||||
language.put("num_paginas_estimadas",
|
||||
messageSource.getMessage("presupuesto.maquetacion.num-paginas-estimadas", null, locale));
|
||||
language.put("precio_por_pagina_estimado",
|
||||
messageSource.getMessage("presupuesto.maquetacion.precio-por-pagina-estimado", null, locale));
|
||||
language.put("add_to_presupuesto",
|
||||
messageSource.getMessage("presupuesto.add-to-presupuesto", null, locale));
|
||||
language.put("cancel", messageSource.getMessage("app.cancelar", null, locale));
|
||||
language.put("presupuesto_maquetacion", messageSource.getMessage("presupuesto.maquetacion", null, locale));
|
||||
out.put("language", language);
|
||||
@ -675,4 +684,176 @@ public class PresupuestoService {
|
||||
return out;
|
||||
}
|
||||
|
||||
public HashMap<String, Object> getPrecioMarcapaginas(PresupuestoMarcapaginas presupuestoMarcapaginas,
|
||||
Locale locale) {
|
||||
|
||||
try {
|
||||
|
||||
List<Marcapaginas> m = marcapaginasRepository.findPrecios(presupuestoMarcapaginas);
|
||||
if (m.isEmpty() || m.get(0) == null) {
|
||||
HashMap<String, Object> out = new HashMap<>();
|
||||
out.put("precio_unidad", 0.0);
|
||||
out.put("precio_total", 0.0);
|
||||
return out;
|
||||
}
|
||||
|
||||
Marcapaginas marcapaginas = m.get(0);
|
||||
|
||||
Double precio = 0.0;
|
||||
Double margen = 0.0;
|
||||
Double pvp = 0.0;
|
||||
BigDecimal data = BigDecimal.ZERO;
|
||||
|
||||
if (marcapaginas.getUnidades_max() >= presupuestoMarcapaginas.getUnidades()) {
|
||||
|
||||
precio = marcapaginas.getPrecio_unidades_min() +
|
||||
(presupuestoMarcapaginas.getUnidades() - marcapaginas.getUnidades_min())
|
||||
* (marcapaginas.getPrecio_unidades_max() - marcapaginas.getPrecio_unidades_min())
|
||||
/ (marcapaginas.getUnidades_max() - marcapaginas.getUnidades_min());
|
||||
|
||||
data = new BigDecimal(precio);
|
||||
precio = data.setScale(2, RoundingMode.HALF_UP).doubleValue();
|
||||
|
||||
margen = 1.0 * marcapaginas.getMargen_unidades_min() +
|
||||
(1.0 * presupuestoMarcapaginas.getUnidades() - 1.0 * marcapaginas.getUnidades_min())
|
||||
* (1.0 * marcapaginas.getMargen_unidades_max()
|
||||
- 1.0 * marcapaginas.getMargen_unidades_min())
|
||||
/ (1.0 * marcapaginas.getUnidades_max() - 1.0 * marcapaginas.getUnidades_min());
|
||||
|
||||
data = new BigDecimal(margen);
|
||||
margen = data.setScale(2, RoundingMode.HALF_UP).doubleValue();
|
||||
|
||||
pvp = precio + (precio * margen / 100);
|
||||
|
||||
data = new BigDecimal(pvp);
|
||||
pvp = data.setScale(2, RoundingMode.HALF_UP).doubleValue();
|
||||
|
||||
} else {
|
||||
|
||||
// precio unidad para el máximo de unidades
|
||||
precio = marcapaginas.getPrecio_unidades_max() / marcapaginas.getUnidades_max();
|
||||
precio = precio * presupuestoMarcapaginas.getUnidades();
|
||||
data = new BigDecimal(precio);
|
||||
precio = data.setScale(2, RoundingMode.HALF_UP).doubleValue();
|
||||
|
||||
margen = 1.0 * marcapaginas.getMargen_unidades_max();
|
||||
data = new BigDecimal(margen);
|
||||
margen = data.setScale(2, RoundingMode.HALF_UP).doubleValue();
|
||||
|
||||
pvp = precio + (precio * margen / 100);
|
||||
data = new BigDecimal(pvp);
|
||||
pvp = data.setScale(2, RoundingMode.HALF_UP).doubleValue();
|
||||
|
||||
}
|
||||
|
||||
Double precio_unidad = pvp / presupuestoMarcapaginas.getUnidades();
|
||||
data = new BigDecimal(precio_unidad);
|
||||
precio_unidad = data.setScale(6, RoundingMode.HALF_UP).doubleValue();
|
||||
|
||||
HashMap<String, Object> resultado;
|
||||
resultado = new HashMap<>();
|
||||
resultado.put("precio_unitario", precio_unidad);
|
||||
resultado.put("precio_total", pvp);
|
||||
HashMap<String, String> language = new HashMap<>();
|
||||
language.put("precio_unidad",
|
||||
messageSource.getMessage("presupuesto.marcapaginas.precio-unidad", null, locale));
|
||||
language.put("precio_total",
|
||||
messageSource.getMessage("presupuesto.marcapaginas.precio-total", null, locale));
|
||||
language.put("add_to_presupuesto",
|
||||
messageSource.getMessage("presupuesto.add-to-presupuesto", null, locale));
|
||||
language.put("cancel", messageSource.getMessage("app.cancelar", null, locale));
|
||||
language.put("presupuesto_marcapaginas",
|
||||
messageSource.getMessage("presupuesto.marcapaginas", null, locale));
|
||||
resultado.put("language", language);
|
||||
return resultado;
|
||||
|
||||
} catch (Exception e) {
|
||||
System.out.println("Error procesando presupuesto marcapaginas: " + e.getMessage());
|
||||
}
|
||||
HashMap<String, Object> out = new HashMap<>();
|
||||
out.put("precio_unidad", 0.0);
|
||||
out.put("precio_total", 0.0);
|
||||
return out;
|
||||
}
|
||||
|
||||
public Map<String, Object> getResumen(Presupuesto presupuesto, List<Map<String, Object>> servicios, Locale locale) {
|
||||
|
||||
Map<String, Object> resumen = new HashMap<>();
|
||||
resumen.put("titulo", presupuesto.getTitulo());
|
||||
|
||||
Presupuesto pressupuestoTemp = presupuesto.clone();
|
||||
|
||||
resumen.put("imagen", "/assets/images/imprimelibros/presupuestador/" + presupuesto.getTipoEncuadernacion() + ".png");
|
||||
resumen.put("imagen_alt", messageSource.getMessage("presupuesto." + presupuesto.getTipoEncuadernacion(), null, locale));
|
||||
|
||||
boolean hayDepositoLegal = servicios != null && servicios.stream()
|
||||
.map(m -> java.util.Objects.toString(m.get("id"), "")) // null-safe -> String
|
||||
.map(String::trim)
|
||||
.anyMatch("deposito-legal"::equals);
|
||||
|
||||
if(hayDepositoLegal){
|
||||
pressupuestoTemp.setSelectedTirada(presupuesto.getSelectedTirada()+4);
|
||||
}
|
||||
|
||||
HashMap<String, Object> precios = this.calcularPresupuesto(pressupuestoTemp, locale);
|
||||
if(precios.containsKey("error")){
|
||||
resumen.put("error", precios.get("error"));
|
||||
return resumen;
|
||||
}
|
||||
|
||||
HashMap<String, Object> linea = new HashMap<>();
|
||||
Double precio_unitario = 0.0;
|
||||
Double precio_total = 0.0;
|
||||
Integer counter = 0;
|
||||
linea.put("descripcion", presupuestoFormatter.resumen(presupuesto, servicios, locale));
|
||||
linea.put("cantidad", presupuesto.getSelectedTirada() != null ? presupuesto.getSelectedTirada() : 0);
|
||||
precio_unitario = ((List<Double>) ((Map<String, Object>) precios.get("data")).get("precios"))
|
||||
.get(0);
|
||||
precio_total = precio_unitario * presupuesto.getSelectedTirada();
|
||||
linea.put("precio_unitario", precio_unitario);
|
||||
linea.put("precio_total", BigDecimal.valueOf(precio_total).setScale(2, RoundingMode.HALF_UP));
|
||||
resumen.put("linea" + counter, linea);
|
||||
counter++;
|
||||
|
||||
if(hayDepositoLegal) {
|
||||
|
||||
linea = new HashMap<>();
|
||||
linea.put("descripcion", messageSource.getMessage("presupuesto.resumen-deposito-legal", null, locale));
|
||||
linea.put("cantidad", 4);
|
||||
linea.put("precio_unitario", precio_unitario);
|
||||
linea.put("precio_total", BigDecimal.valueOf(precio_unitario * 4).setScale(2, RoundingMode.HALF_UP));
|
||||
resumen.put("linea" + counter, linea);
|
||||
counter++;
|
||||
}
|
||||
|
||||
List<Map<String, Object>> serviciosExtras = new ArrayList<>();
|
||||
|
||||
if(servicios != null){
|
||||
for (Map<String, Object> servicio : servicios) {
|
||||
HashMap<String, Object> servicioData = new HashMap<>();
|
||||
servicioData.put("descripcion", servicio.get("label"));
|
||||
servicioData.put("precio", servicio.get("price"));
|
||||
serviciosExtras.add(servicioData);
|
||||
}
|
||||
}
|
||||
resumen.put("servicios", serviciosExtras);
|
||||
|
||||
return resumen;
|
||||
}
|
||||
|
||||
public HashMap<String, Object> calcularPresupuesto(Presupuesto presupuesto, Locale locale) {
|
||||
|
||||
HashMap<String, Object> price = new HashMap<>();
|
||||
String priceStr = apiClient.getPrice(this.toSkApiRequest(presupuesto));
|
||||
|
||||
try {
|
||||
price = new ObjectMapper().readValue(priceStr, new TypeReference<>() {
|
||||
});
|
||||
} catch (JsonProcessingException e) {
|
||||
price = new HashMap<>();
|
||||
price.put("error", messageSource.getMessage("presupuesto.error-obtener-precio", null, locale));
|
||||
}
|
||||
|
||||
return price;
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,6 +10,8 @@ import java.util.Map;
|
||||
@Component
|
||||
public class PresupuestadorItems {
|
||||
|
||||
|
||||
|
||||
@Autowired
|
||||
private MessageSource messageSource;
|
||||
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
package com.imprimelibros.erp.presupuesto.classes;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class PresupuestoAcabados {
|
||||
|
||||
private final MessageSource messageSource;
|
||||
|
||||
public PresupuestoAcabados(MessageSource messageSource) {
|
||||
this.messageSource = messageSource;
|
||||
}
|
||||
|
||||
private static final Map<Integer, String> ACABADO_KEYS = Map.of(
|
||||
0, "presupuesto.acabado-ninguno",
|
||||
1, "presupuesto.acabado-plastificado-brillo-1c",
|
||||
5, "presupuesto.acabado-plastificado-mate-1c",
|
||||
8, "presupuesto.acabado-plastificado-mate-1c-antirrayado",
|
||||
2, "presupuesto.acabado-plastificado-mate-uvi",
|
||||
3, "presupuesto.acabado-plastificado-mate-uvi3d",
|
||||
4, "presupuesto.acabado-plastificado-mate-uvi-braile",
|
||||
9, "presupuesto.acabado-plastificado-sandy-1c"
|
||||
);
|
||||
|
||||
public String labelAcabado(int id, Locale locale) {
|
||||
String key = ACABADO_KEYS.get(id);
|
||||
return key != null ? messageSource.getMessage(key, null, locale) : String.valueOf(id);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,133 @@
|
||||
package com.imprimelibros.erp.presupuesto.classes;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.imprimelibros.erp.i18n.TranslationService;
|
||||
import com.imprimelibros.erp.presupuesto.Presupuesto;
|
||||
import org.springframework.context.MessageSource;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
public class PresupuestoFormatter {
|
||||
|
||||
private final TranslationService translationService;
|
||||
private final MessageSource ms;
|
||||
private final PresupuestoPapeles papeles;
|
||||
private final PresupuestoAcabados acabados;
|
||||
|
||||
public PresupuestoFormatter(
|
||||
TranslationService translationService,
|
||||
MessageSource ms,
|
||||
PresupuestoPapeles papeles,
|
||||
PresupuestoAcabados acabados) {
|
||||
this.translationService = translationService;
|
||||
this.ms = ms;
|
||||
this.papeles = papeles;
|
||||
this.acabados = acabados;
|
||||
}
|
||||
|
||||
public String resumen(Presupuesto p, List<Map<String, Object>> servicios, Locale locale) {
|
||||
String encuadernacion = translationService.label(p.getTipoEncuadernacion(), locale);
|
||||
String tipoImpresion = translationService.label(p.getTipoImpresion(), locale);
|
||||
String tapaCubierta = translationService.label(p.getTipoCubierta(), locale);
|
||||
|
||||
Object[] args = {
|
||||
|
||||
p.getSelectedTirada(),
|
||||
encuadernacion,
|
||||
tipoImpresion,
|
||||
(p.getPaginasColorTotal() != null ? p.getPaginasColorTotal() : p.getPaginasColor())
|
||||
+ p.getPaginasNegro(),
|
||||
p.getAncho(), p.getAlto(),
|
||||
papeles.labelPapel(p.getPapelInteriorId(), locale), p.getGramajeInterior(),
|
||||
tapaCubierta,
|
||||
papeles.labelPapel(p.getPapelCubiertaId(), locale), p.getGramajeCubierta(),
|
||||
};
|
||||
String textoResumen = ms.getMessage("presupuesto.resumen-texto", args, locale);
|
||||
textoResumen += "<ul>";
|
||||
|
||||
// tapa blanda
|
||||
if (p.getTipoCubierta() == Presupuesto.TipoCubierta.tapaBlanda) {
|
||||
String impresionCubierta = ms.getMessage(
|
||||
"presupuesto.resumen-texto-impresion-caras-cubierta",
|
||||
new Object[] {
|
||||
p.getCubiertaCaras() == 2
|
||||
? ms.getMessage("presupuesto.una-cara", null, locale)
|
||||
: ms.getMessage("presupuesto.dos-caras", null, locale)
|
||||
},
|
||||
locale);
|
||||
textoResumen += impresionCubierta;
|
||||
|
||||
if (p.getSolapasCubierta()) {
|
||||
textoResumen += ms.getMessage(
|
||||
"presupuesto.resumen-texto-solapas-cubierta",
|
||||
new Object[] { p.getTamanioSolapasCubierta() },
|
||||
locale);
|
||||
}
|
||||
}
|
||||
// tapa dura
|
||||
else if (p.getTipoCubierta() == Presupuesto.TipoCubierta.tapaDura
|
||||
|| p.getTipoCubierta() == Presupuesto.TipoCubierta.tapaDuraLomoRedondo) {
|
||||
|
||||
String textImpresionGuardas = "";
|
||||
if (p.getGuardasImpresas() == 0) {
|
||||
textImpresionGuardas = ms.getMessage("presupuesto.guardas-no-impresas", null, locale);
|
||||
} else if (p.getGuardasImpresas() == 4) {
|
||||
textImpresionGuardas = ms.getMessage("presupuesto.guardas-impresas-una-cara", null, locale);
|
||||
} else if (p.getGuardasImpresas() == 8) {
|
||||
textImpresionGuardas = ms.getMessage("presupuesto.guardas-impresas-dos-caras", null, locale);
|
||||
}
|
||||
|
||||
textoResumen += ms.getMessage(
|
||||
"presupuesto.resumen-texto-guardas-cabezada",
|
||||
new Object[] {
|
||||
textImpresionGuardas,
|
||||
papeles.labelPapel(p.getPapelGuardasId(), locale),
|
||||
p.getGramajeGuardas(),
|
||||
papeles.labelCabezada(p.getCabezada(), locale),
|
||||
},
|
||||
locale);
|
||||
}
|
||||
|
||||
if (p.getAcabado() != null) {
|
||||
textoResumen += ms.getMessage(
|
||||
"presupuesto.resumen-texto-acabado-cubierta",
|
||||
new Object[] { acabados.labelAcabado(p.getAcabado(), locale) },
|
||||
locale);
|
||||
}
|
||||
|
||||
textoResumen += ms.getMessage("presupuesto.resumen-texto-end", null, locale);
|
||||
|
||||
if (Boolean.TRUE.equals(p.getSobrecubierta())) {
|
||||
textoResumen += ms.getMessage(
|
||||
"presupuesto.resumen-texto-sobrecubierta",
|
||||
new Object[] {
|
||||
papeles.labelPapel(p.getPapelSobrecubiertaId(), locale),
|
||||
p.getGramajeSobrecubierta(),
|
||||
acabados.labelAcabado(p.getAcabadoSobrecubierta(), locale),
|
||||
p.getTamanioSolapasSobrecubierta()
|
||||
},
|
||||
locale);
|
||||
}
|
||||
|
||||
if (Boolean.TRUE.equals(p.getFaja())) {
|
||||
textoResumen += ms.getMessage(
|
||||
"presupuesto.resumen-texto-faja",
|
||||
new Object[] {
|
||||
papeles.labelPapel(p.getPapelFajaId(), locale),
|
||||
p.getGramajeFaja(),
|
||||
p.getAltoFaja(),
|
||||
acabados.labelAcabado(p.getAcabadoFaja(), locale),
|
||||
p.getTamanioSolapasFaja()
|
||||
},
|
||||
locale);
|
||||
}
|
||||
textoResumen += ms.getMessage("presupuesto.resumen-texto-end", null, locale);
|
||||
|
||||
return textoResumen;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
package com.imprimelibros.erp.presupuesto.classes;
|
||||
|
||||
import static com.imprimelibros.erp.presupuesto.marcapaginas.Marcapaginas.Papeles;
|
||||
import static com.imprimelibros.erp.presupuesto.marcapaginas.Marcapaginas.Acabado;
|
||||
import static com.imprimelibros.erp.presupuesto.marcapaginas.Marcapaginas.Tamanios;
|
||||
import static com.imprimelibros.erp.presupuesto.marcapaginas.Marcapaginas.Caras_Impresion;
|
||||
|
||||
import jakarta.validation.constraints.Min;
|
||||
|
||||
public class PresupuestoMarcapaginas {
|
||||
|
||||
@Min(value = 100, message = "{validation.min}")
|
||||
private Integer unidades = 100;
|
||||
private Tamanios tamanio = Tamanios._50x140_;
|
||||
private Caras_Impresion carasImpresion = Caras_Impresion.una_cara;
|
||||
private Papeles papel = Papeles.cartulina_grafica;
|
||||
private Integer gramaje = 300;
|
||||
private Acabado acabado = Acabado.ninguno;
|
||||
|
||||
|
||||
public Integer getUnidades() {
|
||||
return unidades;
|
||||
}
|
||||
public void setUnidades(Integer unidades) {
|
||||
this.unidades = unidades;
|
||||
}
|
||||
public Tamanios getTamanio() {
|
||||
return tamanio;
|
||||
}
|
||||
public void setTamanio(Tamanios tamanio) {
|
||||
this.tamanio = tamanio;
|
||||
}
|
||||
public Caras_Impresion getCarasImpresion() {
|
||||
return carasImpresion;
|
||||
}
|
||||
public void setCarasImpresion(Caras_Impresion carasImpresion) {
|
||||
this.carasImpresion = carasImpresion;
|
||||
}
|
||||
public Papeles getPapel() {
|
||||
return papel;
|
||||
}
|
||||
public void setPapel(Papeles papel) {
|
||||
this.papel = papel;
|
||||
}
|
||||
public Integer getGramaje() {
|
||||
return gramaje;
|
||||
}
|
||||
public void setGramaje(Integer gramaje) {
|
||||
this.gramaje = gramaje;
|
||||
}
|
||||
public Acabado getAcabado() {
|
||||
return acabado;
|
||||
}
|
||||
public void setAcabado(Acabado acabado) {
|
||||
this.acabado = acabado;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
package com.imprimelibros.erp.presupuesto.classes;
|
||||
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
public class PresupuestoPapeles {
|
||||
|
||||
private final MessageSource messageSource;
|
||||
|
||||
public PresupuestoPapeles(MessageSource messageSource) {
|
||||
this.messageSource = messageSource;
|
||||
}
|
||||
|
||||
private static final Map<Integer, String> PAPEL_KEYS = Map.of(
|
||||
2, "presupuesto.estucado-mate",
|
||||
3, "presupuesto.offset-blanco",
|
||||
4, "presupuesto.offset-ahuesado",
|
||||
5, "presupuesto.cartulina-grafica-cubierta",
|
||||
6, "presupuesto.offset-ahuesado-volumen",
|
||||
7, "presupuesto.offset-blanco-volumen"
|
||||
);
|
||||
|
||||
private static final Map<String, String> CABEZADA_COLOR_KEYS = Map.of(
|
||||
"WHI", "presupuesto.cabezada-blanca",
|
||||
"GRE", "presupuesto.cabezada-verde",
|
||||
"BLUE", "presupuesto.cabezada-azul",
|
||||
"REDYEL", "presupuesto.cabezada-roja-amarilla"
|
||||
);
|
||||
|
||||
public String labelPapel(int id, Locale locale) {
|
||||
String key = PAPEL_KEYS.get(id);
|
||||
return key != null ? messageSource.getMessage(key, null, locale) : String.valueOf(id);
|
||||
}
|
||||
|
||||
public String labelCabezada(String code, Locale locale) {
|
||||
String key = CABEZADA_COLOR_KEYS.get(code);
|
||||
return key != null ? messageSource.getMessage(key, null, locale) : code;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,154 @@
|
||||
package com.imprimelibros.erp.presupuesto.marcapaginas;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
|
||||
@Entity
|
||||
@Table(name = "marcapaginas")
|
||||
public class Marcapaginas {
|
||||
|
||||
public enum Acabado{
|
||||
ninguno,
|
||||
plastificado_brillo_1c,
|
||||
plastificado_brillo_2c,
|
||||
plastificado_mate_1c,
|
||||
plastificado_mate_2c
|
||||
};
|
||||
|
||||
public enum Tamanios{
|
||||
_50x140_, _50x170_, _50x210_
|
||||
};
|
||||
|
||||
public enum Papeles{
|
||||
cartulina_grafica, estucado_mate
|
||||
};
|
||||
|
||||
public enum Caras_Impresion{
|
||||
una_cara, dos_caras
|
||||
};
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@NotNull(message = "{validation.required}")
|
||||
@Enumerated(EnumType.STRING)
|
||||
private Caras_Impresion carasImpresion = Caras_Impresion.una_cara;
|
||||
|
||||
@NotNull(message = "{validation.required}")
|
||||
@Enumerated(EnumType.STRING)
|
||||
private Tamanios tamanio = Tamanios._50x140_;
|
||||
|
||||
@NotNull(message = "{validation.required}")
|
||||
@Enumerated(EnumType.STRING)
|
||||
private Papeles papel = Papeles.cartulina_grafica;
|
||||
|
||||
@NotNull(message = "{validation.required}")
|
||||
private Integer gramaje = 300;
|
||||
|
||||
@NotNull(message = "{validation.required}")
|
||||
@Enumerated(EnumType.STRING)
|
||||
private Acabado acabado = Acabado.ninguno;
|
||||
|
||||
@Min(value = 100, message = "{validation.min}")
|
||||
@NotNull(message = "{validation.required}")
|
||||
private Integer unidades_min = 100;
|
||||
|
||||
@Min(value = 101, message = "{validation.min}")
|
||||
@NotNull(message = "{validation.required}")
|
||||
private Integer unidades_max = 5000;
|
||||
|
||||
@NotNull(message = "{validation.required}")
|
||||
private Double precio_unidades_min;
|
||||
|
||||
@NotNull(message = "{validation.required}")
|
||||
private Double precio_unidades_max;
|
||||
|
||||
@NotNull(message = "{validation.required}")
|
||||
@Min(value = 0, message = "{validation.min}")
|
||||
@Max(value = 300, message = "{validation.max}")
|
||||
|
||||
private Integer margen_unidades_min = 0;
|
||||
|
||||
@NotNull(message = "{validation.required}")
|
||||
@Min(value = 0, message = "{validation.min}")
|
||||
@Max(value = 300, message = "{validation.max}")
|
||||
private Integer margen_unidades_max = 0;
|
||||
|
||||
/* Getters and Setters */
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
public Caras_Impresion getCarasImpresion() {
|
||||
return carasImpresion;
|
||||
}
|
||||
public void setCarasImpresion(Caras_Impresion carasImpresion) {
|
||||
this.carasImpresion = carasImpresion;
|
||||
}
|
||||
public Tamanios getTamanio() {
|
||||
return tamanio;
|
||||
}
|
||||
public void setTamanio(Tamanios tamanio) {
|
||||
this.tamanio = tamanio;
|
||||
}
|
||||
public Papeles getPapel() {
|
||||
return papel;
|
||||
}
|
||||
public void setPapel(Papeles papel) {
|
||||
this.papel = papel;
|
||||
}
|
||||
public Integer getGramaje() {
|
||||
return gramaje;
|
||||
}
|
||||
public void setGramaje(Integer gramaje) {
|
||||
this.gramaje = gramaje;
|
||||
}
|
||||
public Acabado getAcabado() {
|
||||
return acabado;
|
||||
}
|
||||
public void setAcabado(Acabado acabado) {
|
||||
this.acabado = acabado;
|
||||
}
|
||||
public Integer getUnidades_min() {
|
||||
return unidades_min;
|
||||
}
|
||||
public void setUnidades_min(Integer unidades_min) {
|
||||
this.unidades_min = unidades_min;
|
||||
}
|
||||
public Integer getUnidades_max() {
|
||||
return unidades_max;
|
||||
}
|
||||
public void setUnidades_max(Integer unidades_max) {
|
||||
this.unidades_max = unidades_max;
|
||||
}
|
||||
public Double getPrecio_unidades_min() {
|
||||
return precio_unidades_min;
|
||||
}
|
||||
public void setPrecio_unidades_min(Double precio_unidades_min) {
|
||||
this.precio_unidades_min = precio_unidades_min;
|
||||
}
|
||||
public Double getPrecio_unidades_max() {
|
||||
return precio_unidades_max;
|
||||
}
|
||||
public void setPrecio_unidades_max(Double precio_unidades_max) {
|
||||
this.precio_unidades_max = precio_unidades_max;
|
||||
}
|
||||
public Integer getMargen_unidades_min() {
|
||||
return margen_unidades_min;
|
||||
}
|
||||
public void setMargen_unidades_min(Integer margen_unidades_min) {
|
||||
this.margen_unidades_min = margen_unidades_min;
|
||||
}
|
||||
public Integer getMargen_unidades_max() {
|
||||
return margen_unidades_max;
|
||||
}
|
||||
public void setMargen_unidades_max(Integer margen_unidades_max) {
|
||||
this.margen_unidades_max = margen_unidades_max;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package com.imprimelibros.erp.presupuesto.marcapaginas;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import com.imprimelibros.erp.presupuesto.classes.PresupuestoMarcapaginas;
|
||||
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface MarcapaginasRepository extends JpaRepository<Marcapaginas, Long> {
|
||||
|
||||
@Query("""
|
||||
SELECT m
|
||||
FROM Marcapaginas m
|
||||
WHERE m.tamanio = :#{#p.tamanio}
|
||||
AND m.carasImpresion = :#{#p.carasImpresion}
|
||||
AND m.papel = :#{#p.papel}
|
||||
AND m.gramaje = :#{#p.gramaje}
|
||||
AND m.acabado = :#{#p.acabado}
|
||||
AND m.unidades_min <= :#{#p.unidades}
|
||||
ORDER BY
|
||||
CASE
|
||||
WHEN :#{#p.unidades} BETWEEN m.unidades_min AND m.unidades_max THEN 0
|
||||
ELSE 1
|
||||
END,
|
||||
m.unidades_max DESC
|
||||
""")
|
||||
List<Marcapaginas> findPrecios(@Param("p") PresupuestoMarcapaginas p);
|
||||
}
|
||||
@ -1,19 +1,49 @@
|
||||
spring.application.name=erp
|
||||
logging.level.org.springframework.security=DEBUG
|
||||
|
||||
#
|
||||
# Logging
|
||||
#
|
||||
logging.level.org.springframework.security=DEBUG
|
||||
logging.level.root=WARN
|
||||
logging.level.org.springframework=ERROR
|
||||
|
||||
|
||||
#
|
||||
# Database Configuration
|
||||
#
|
||||
spring.datasource.url=jdbc:mysql://localhost:3309/imprimelibros
|
||||
#spring.datasource.url=jdbc:mysql://127.0.0.1:3309/imprimelibros?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Europe/Madrid&characterEncoding=utf8
|
||||
spring.datasource.username=imprimelibros_user
|
||||
spring.datasource.password=om91irrDctd
|
||||
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
spring.jpa.show-sql=true
|
||||
|
||||
|
||||
#
|
||||
# Safekat API Configuration
|
||||
#
|
||||
safekat.api.url=http://localhost:8000/
|
||||
#safekat.api.url=https://erp-dev.safekat.es/
|
||||
safekat.api.email=imnavajas@coit.es
|
||||
safekat.api.password=Safekat2024
|
||||
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
spring.jpa.show-sql=true
|
||||
|
||||
#
|
||||
# Debug JPA / Hibernate
|
||||
#
|
||||
#spring.jpa.show-sql=true
|
||||
#logging.level.org.hibernate.SQL=DEBUG
|
||||
#logging.level.org.hibernate.orm.jdbc.bind=TRACE
|
||||
#spring.jpa.properties.hibernate.format_sql=true
|
||||
|
||||
|
||||
#
|
||||
# Resource chain
|
||||
# Activa el resource chain y versionado por contenido
|
||||
#
|
||||
spring.web.resources.chain.enabled=true
|
||||
spring.web.resources.chain.strategy.content.enabled=true
|
||||
spring.web.resources.chain.strategy.content.paths=/assets/**
|
||||
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
presupuesto.datos-generales=Datos Generales
|
||||
presupuesto.interior=Interior
|
||||
presupuesto.cubierta=Cubierta
|
||||
presupuesto.seleccion-tirada=Seleccion de tirada
|
||||
presupuesto.seleccion-tirada=Selección tirada
|
||||
presupuesto.extras=Extras
|
||||
presupuesto.resumen=Resumen
|
||||
presupuesto.add-to-presupuesto=Añadir al presupuesto
|
||||
presupuesto.calcular=Calcular
|
||||
|
||||
# Pestaña datos generales de presupuesto
|
||||
presupuesto.informacion-libro=Información del libro
|
||||
@ -11,6 +13,7 @@ presupuesto.datos-generales-descripcion=Datos generales del presupuesto
|
||||
presupuesto.titulo=Título*
|
||||
presupuesto.autor=Autor
|
||||
presupuesto.isbn=ISBN
|
||||
presupuesto.tirada=Tirada
|
||||
presupuesto.tirada1=Tirada 1*
|
||||
presupuesto.tirada2=Tirada 2
|
||||
presupuesto.tirada3=Tirada 3
|
||||
@ -76,6 +79,9 @@ presupuesto.impresion-cubierta=Impresión de cubierta
|
||||
presupuesto.impresion-cubierta-help=La cubierta se puede imprimir por anverso y reverso, como en el caso de las revistas, pero para un libro normal con portada y contraportada, la impresión de cubierta es a una cara.
|
||||
presupuesto.una-cara=Una cara
|
||||
presupuesto.dos-caras=Dos caras
|
||||
presupuesto.guardas-impresas-una-cara=impresas a una cara
|
||||
presupuesto.guardas-impresas-dos-caras=impresas a dos caras
|
||||
presupuesto.guardas-no-impresas=no impresas
|
||||
presupuesto.tamanio-solapa=Tamaño solapas
|
||||
presupuesto.papel-guardas=Papel de guardas
|
||||
presupuesto.guardas-impresas=Guardas impresas
|
||||
@ -88,6 +94,7 @@ presupuesto.cabezada-roja-amarilla=Roja-Amarilla
|
||||
presupuesto.papel-cubierta=Papel cubierta
|
||||
presupuesto.papel-cubierta-descripcion=Seleccione el papel para la cubierta
|
||||
presupuesto.cartulina-grafica-cubierta=Cartulina gráfica estucada a una cara
|
||||
presupuesto.cartulina-grafica=Cartulina gráfica
|
||||
presupuesto.estucado-mate-cubierta=Estucado mate
|
||||
presupuesto.gramaje-cubierta=Gramaje cubierta
|
||||
presupuesto.gramaje-cubierta-descripcion=Seleccione el gramaje para la cubierta
|
||||
@ -103,7 +110,9 @@ presupuesto.acabado-cubierta-descripcion=Seleccione el acabado para la cubierta
|
||||
presupuesto.acabado-cubierta-aviso=La falta de plastificado en la cubierta puede comprometer su calidad, ya que aumenta el riesgo de agrietamiento en los pliegues o hendidos, afectando su apariencia y resistencia.
|
||||
presupuesto.acabado-ninguno=Sin acabado
|
||||
presupuesto.acabado-plastificado-brillo-1c=Plastificado Brillo 1/C
|
||||
presupuesto.acabado-plastificado-brillo-2c=Plastificado Brillo 2/C
|
||||
presupuesto.acabado-plastificado-mate-1c=Plastificado Mate 1/C
|
||||
presupuesto.acabado-plastificado-mate-2c=Plastificado Mate 2/C
|
||||
presupuesto.acabado-plastificado-mate-1c-antirrayado=Plastificado Mate 1/C Antirrayado
|
||||
presupuesto.acabado-plastificado-mate-uvi=Plastificado Mate 1/C + Reserva UVI
|
||||
presupuesto.acabado-plastificado-mate-uvi3d=Plastificado Mate 1/C + Reserva UVI 3D
|
||||
@ -149,7 +158,30 @@ presupuesto.volver-cubierta=Volver a diseño cubierta
|
||||
presupuesto.finalizar=Finalizar presupuesto
|
||||
presupuesto.calcular-presupuesto=Calcular presupuesto
|
||||
presupuesto.consultar-soporte=Consultar con soporte
|
||||
presupuesto.calcular=Calcular
|
||||
|
||||
# Pestaña resumen del presupuesto
|
||||
presupuesto.resumen.tabla.descripcion=Descripción
|
||||
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.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. \
|
||||
<ul> \
|
||||
<li>Papel interior {6} {7} gr.</li> \
|
||||
<li>Cubierta {8} en {9} {10} gr.</li>
|
||||
presupuesto.resumen-texto-impresion-caras-cubierta=<li>Impresa a {0}.</li>
|
||||
presupuesto.resumen-texto-solapas-cubierta=<li>Solapas de {0} mm.</li>
|
||||
presupuesto.resumen-texto-guardas-cabezada= <li>Guardas {0} en {1} {2}. Color de la cabezada: {3}.</li>
|
||||
presupuesto.resumen-texto-acabado-cubierta= <li>Acabado {0}. </li>
|
||||
presupuesto.resumen-texto-end=</ul>
|
||||
presupuesto.resumen-texto-sobrecubierta=<li>Sobrecubierta impresa en {0} {1} gr. <ul><li>Acabado {2}</li><li>Solapas: {3} mm.</li></ul></li>
|
||||
presupuesto.resumen-texto-faja=<li>Faja impresa en {0} {1} gr. con un alto de {2} mm. <ul><li>Acabado {3}</li><li>Solapas: {4} mm.</li></ul></li>
|
||||
presupuesto.resumen-deposito-legal=Ejemplares para el Depósito Legal
|
||||
presupuesto.volver-extras=Volver a extras
|
||||
presupuesto.resumen.inicie-sesion=Inicie sesión para continuar
|
||||
presupuesto.resumen.agregar-cesta=Agregar a la cesta
|
||||
|
||||
# Resumen del presupuesto
|
||||
presupuesto.resumen-presupuesto=Resumen presupuesto
|
||||
@ -181,6 +213,27 @@ presupuesto.maquetacion.correccion-ortotipografica=Corrección ortotipográfica
|
||||
presupuesto.maquetacion.texto-mecanografiado=Texto mecanografiado
|
||||
presupuesto.maquetacion.diseno-portada=Diseño de portada
|
||||
presupuesto.maquetacion.epub=Generación Epub
|
||||
presupuesto.maquetacion.num-paginas-estimadas=Páginas estimadas
|
||||
presupuesto.maquetacion.precio-por-pagina-estimado=Precio por página estimado
|
||||
|
||||
# Presupuesto de marcapáginas
|
||||
presupuesto.marcapaginas=Presupuesto de marcapáginas
|
||||
presupuesto.marcapaginas.tamanio=Tamaño de los marcapáginas
|
||||
presupuesto.marcapaginas.caras-impresion=Caras impresas
|
||||
presupuesto.marcapaginas.caras-impresion-1=Una cara
|
||||
presupuesto.marcapaginas.caras-impresion-2=Dos caras
|
||||
presupuesto.marcapaginas.papel=Papel marcapáginas
|
||||
presupuesto.marcapaginas.papel.cartulina-grafica=Cartulina gráfica
|
||||
presupuesto.marcapaginas.papel.estucado-mate=Estucado mate
|
||||
presupuesto.marcapaginas.gramaje=Gramaje papel marcapáginas
|
||||
presupuesto.marcapaginas.acabado=Acabado marcapáginas
|
||||
presupuesto.marcapaginas.acabado.ninguno=Sin acabado
|
||||
presupuesto.marcapaginas.acabado.plastificado-brillo-1c=Plastificado brillo 1/C
|
||||
presupuesto.marcapaginas.acabado.plastificado-mate-1c=Plastificado mate 1/C
|
||||
presupuesto.marcapaginas.acabado.plastificado-brillo-2c=Plastificado brillo 2/C
|
||||
presupuesto.marcapaginas.acabado.plastificado-mate-2c=Plastificado mate 2/C
|
||||
presupuesto.marcapaginas.precio-unidad=Precio por unidad
|
||||
presupuesto.marcapaginas.precio-total=Precio total
|
||||
|
||||
# Errores
|
||||
presupuesto.errores-title=Corrija los siguientes errores:
|
||||
@ -205,4 +258,5 @@ presupuesto.errores.papel-cubierta=Seleccione el tipo de papel para la cubierta
|
||||
presupuesto.errores.gramaje-cubierta=Seleccione el gramaje del papel para la cubierta
|
||||
presupuesto.errores.acabado-cubierta=Seleccione el acabado de la cubierta
|
||||
|
||||
presupuesto.errores.presupuesto-maquetacion=No se pudo calcular el presupuesto de maquetación.
|
||||
presupuesto.errores.presupuesto-maquetacion=No se pudo calcular el presupuesto de maquetación.
|
||||
presupuesto.errores.presupuesto-marcapaginas=No se pudo calcular el presupuesto de marcapáginas.
|
||||
@ -213,7 +213,8 @@
|
||||
|
||||
/* ===== Tiradas (pricing cards) ===== */
|
||||
.tirada-card {
|
||||
--il-accent: #92b2a7; /* verde grisáceo */
|
||||
--il-accent: #92b2a7;
|
||||
/* verde grisáceo */
|
||||
--radius: 18px;
|
||||
--sel-scale-y: 1.12;
|
||||
/* cuánto más alta la seleccionada (crece arriba y abajo) */
|
||||
@ -352,19 +353,23 @@
|
||||
|
||||
|
||||
.nav-link.active .bg-soft-primary {
|
||||
background-color: #ffffff33 !important; /* #4c5c63 al 20% */
|
||||
background-color: #ffffff33 !important;
|
||||
/* #4c5c63 al 20% */
|
||||
}
|
||||
|
||||
.nav-link.active .text-primary {
|
||||
color: #ffffff !important; /* #4c5c63 al 20% */
|
||||
color: #ffffff !important;
|
||||
/* #4c5c63 al 20% */
|
||||
}
|
||||
|
||||
.nav-link:not(.active) .bg-soft-primary {
|
||||
background-color: #4c5c6366 !important; /* #4c5c63 al 20% */
|
||||
background-color: #4c5c6366 !important;
|
||||
/* #4c5c63 al 20% */
|
||||
}
|
||||
|
||||
.nav-link:not(.active) .text-primary {
|
||||
color: #000000 !important; /* #4c5c63 al 20% */
|
||||
color: #000000 !important;
|
||||
/* #4c5c63 al 20% */
|
||||
}
|
||||
|
||||
/* base */
|
||||
@ -376,20 +381,20 @@
|
||||
}
|
||||
|
||||
/* hover no seleccionado */
|
||||
.btn-check-service + .btn-service-option:hover {
|
||||
.btn-check-service+.btn-service-option:hover {
|
||||
background-color: rgba(146, 178, 167, 0.3);
|
||||
color: #92b2a7;
|
||||
}
|
||||
|
||||
/* seleccionado */
|
||||
.btn-check-service:checked + .btn-service-option {
|
||||
.btn-check-service:checked+.btn-service-option {
|
||||
background-color: #92b2a7;
|
||||
color: #fff;
|
||||
border-color: #92b2a7;
|
||||
}
|
||||
|
||||
/* hover estando seleccionado (que no se aclare) */
|
||||
.btn-check-service:checked + .btn-service-option:hover {
|
||||
.btn-check-service:checked+.btn-service-option:hover {
|
||||
background-color: #92b2a7;
|
||||
color: #fff;
|
||||
}
|
||||
@ -407,4 +412,70 @@
|
||||
|
||||
.form-switch-custom.form-switch-presupuesto .form-check-input:checked::before {
|
||||
color: #92b2a7;
|
||||
}
|
||||
|
||||
/* ==== Paso al resumen ==== */
|
||||
/* ---- Ajustes rápidos ---- */
|
||||
/* Valores por defecto (col-9 / col-3 ≈ 75% / 25%) */
|
||||
#presupuesto-row{
|
||||
--main-col: 75%;
|
||||
--summary-col: 25%;
|
||||
--il-dur-main: 1.4s;
|
||||
--il-dur-summary: .6s;
|
||||
--il-delay-main: .15s; /* empieza un poco después */
|
||||
--il-delay-summary: 0s;
|
||||
--il-ease-main: cubic-bezier(.2,.8,.2,1);
|
||||
--il-ease-summary: cubic-bezier(.4,0,.2,1);
|
||||
--il-shift: 32px;
|
||||
}
|
||||
|
||||
/* Forzamos que el ancho venga de las variables (y sea animable) */
|
||||
@media (prefers-reduced-motion: no-preference){
|
||||
#presupuesto-row .col-main{
|
||||
flex: 0 0 var(--main-col) !important;
|
||||
max-width: var(--main-col) !important;
|
||||
transition:
|
||||
flex-basis var(--il-dur-main) var(--il-ease-main) var(--il-delay-main),
|
||||
max-width var(--il-dur-main) var(--il-ease-main) var(--il-delay-main);
|
||||
}
|
||||
#presupuesto-row .summary-col{
|
||||
flex: 0 0 var(--summary-col) !important;
|
||||
max-width: var(--summary-col) !important;
|
||||
transition:
|
||||
flex-basis var(--il-dur-summary) var(--il-ease-summary) var(--il-delay-summary),
|
||||
max-width var(--il-dur-summary) var(--il-ease-summary) var(--il-delay-summary),
|
||||
opacity var(--il-dur-summary) var(--il-ease-summary) var(--il-delay-summary),
|
||||
transform var(--il-dur-summary) var(--il-ease-summary) var(--il-delay-summary);
|
||||
}
|
||||
}
|
||||
|
||||
/* Estado expandido: cambiamos SOLO las variables (esto sí se anima) */
|
||||
#presupuesto-row.expanded{
|
||||
--main-col: 100%;
|
||||
--summary-col: 0%;
|
||||
}
|
||||
|
||||
#presupuesto-row.expanded .summary-col{
|
||||
opacity: 0;
|
||||
transform: translateX(var(--il-shift));
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (min-width: 1200px){
|
||||
/* Evita que las columnas se vayan a la siguiente línea durante la animación */
|
||||
#presupuesto-row{
|
||||
display: flex; /* por si acaso algún wrapper cambia el display */
|
||||
flex-wrap: nowrap; /* <-- clave */
|
||||
align-items: stretch;
|
||||
}
|
||||
/* Permite que las columnas puedan encoger sin forzar salto de línea */
|
||||
#presupuesto-row .col-main,
|
||||
#presupuesto-row .summary-col{
|
||||
min-width: 0; /* <-- clave para que el contenido no fuerce ancho */
|
||||
}
|
||||
/* Opcional: evita “asomar” algo durante el slide */
|
||||
#presupuesto-row .summary-col{
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,7 @@ import imagen_presupuesto from "./imagen-presupuesto.js";
|
||||
import ServiceOptionCard from "./service-option-card.js";
|
||||
import TiradaCard from "./tirada-price-card.js";
|
||||
import * as Summary from "./summary.js";
|
||||
import { formateaMoneda } from "../utils.js";
|
||||
|
||||
class PresupuestoCliente {
|
||||
|
||||
@ -36,8 +37,8 @@ class PresupuestoCliente {
|
||||
solapasCubierta: 0,
|
||||
tamanioSolapasCubierta: '80',
|
||||
cubiertaCaras: 2,
|
||||
guardasPapelId: 3,
|
||||
guardasGramaje: 170,
|
||||
papelGuardasId: 3,
|
||||
gramajeGuardas: 170,
|
||||
guardasImpresas: 0,
|
||||
cabezada: 'WHI',
|
||||
papelCubiertaId: 3,
|
||||
@ -59,6 +60,38 @@ class PresupuestoCliente {
|
||||
acabado: 0
|
||||
}
|
||||
},
|
||||
servicios: {
|
||||
servicios: [],
|
||||
datosMarcapaginas: {
|
||||
marcapaginas_tirada: 100,
|
||||
tamanio_marcapaginas: '_50x140_',
|
||||
caras_impresion: 'una_cara',
|
||||
papel_marcapaginas: 'cartulina_grafica',
|
||||
gramaje_marcapaginas: 300,
|
||||
acabado_marcapaginas: 'ninguno',
|
||||
resultado: {
|
||||
precio_unitario: 0,
|
||||
precio: 0
|
||||
}
|
||||
},
|
||||
datosMaquetacion: {
|
||||
num_caracteres: 200000,
|
||||
formato_maquetacion: 'A5',
|
||||
cuerpo_texto: 'medium',
|
||||
num_columnas: 1,
|
||||
num_tablas: 0,
|
||||
num_fotos: 0,
|
||||
correccion_ortotipografica: false,
|
||||
texto_mecanografiado: false,
|
||||
disenio_portada: false,
|
||||
epub: false,
|
||||
resultado: {
|
||||
num_paginas_estimadas: 0,
|
||||
precio_pagina_estimado: 0,
|
||||
precio: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
selectedTirada: null,
|
||||
}
|
||||
|
||||
@ -124,6 +157,9 @@ class PresupuestoCliente {
|
||||
// pestaña extras
|
||||
this.divExtras = $('#div-extras');
|
||||
|
||||
// pestaña resumen
|
||||
this.tablaResumen = $('#resumen-tabla-final');
|
||||
|
||||
// resumen
|
||||
this.summaryTableInterior = $('#summary-interior');
|
||||
this.summaryTableCubierta = $('#summary-cubierta');
|
||||
@ -152,6 +188,7 @@ class PresupuestoCliente {
|
||||
this.#initInterior();
|
||||
this.#initSeleccionTirada();
|
||||
this.#initExtras();
|
||||
this.#initResumen();
|
||||
|
||||
$(document).on('change', 'input[min][max]', function () {
|
||||
const $input = $(this);
|
||||
@ -236,8 +273,9 @@ class PresupuestoCliente {
|
||||
contenedor.append(label);
|
||||
}
|
||||
|
||||
|
||||
/******************************
|
||||
* END OF DATOS GENERALES
|
||||
* DATOS GENERALES
|
||||
******************************/
|
||||
#initDatosGenerales() {
|
||||
|
||||
@ -1087,10 +1125,10 @@ class PresupuestoCliente {
|
||||
|
||||
const tipoCubierta = $('.tapa-cubierta.selected').attr('id') || 'tapaBlanda';
|
||||
const solapas = $('.solapas-cubierta.selected').id == 'sin-solapas' ? 0 : 1 || 0;
|
||||
const tamanioSolapas = $('#tamanio-solapas-cubierta').val() || '80';
|
||||
const tamanioSolapasCubierta = $('#tamanio-solapas-cubierta').val() || '80';
|
||||
const cubiertaCaras = parseInt(this.carasImpresionCubierta.val()) || 2;
|
||||
const guardasPapelId = parseInt($('#papel-guardas option:selected').data('papel-id')) || 3;
|
||||
const guardasGramaje = parseInt($('#papel-guardas option:selected').data('gramaje')) || 170;
|
||||
const papelGuardasId = parseInt($('#papel-guardas option:selected').data('papel-id')) || 3;
|
||||
const gramajeGuardas = parseInt($('#papel-guardas option:selected').data('gramaje')) || 170;
|
||||
const guardasImpresas = parseInt(this.guardasImpresas) || 0;
|
||||
const cabezada = this.cabezada.val() || 'WHI';
|
||||
const papelCubiertaId = $('#div-papel-cubierta .image-container.selected').data('sk-id') || this.formData.cubierta.papelCubiertaId || 3;
|
||||
@ -1110,11 +1148,11 @@ class PresupuestoCliente {
|
||||
|
||||
return {
|
||||
tipoCubierta: tipoCubierta,
|
||||
solapas: solapas,
|
||||
tamanioSolapas: tamanioSolapas,
|
||||
solapasCubierta: solapas,
|
||||
tamanioSolapasCubierta: tamanioSolapasCubierta,
|
||||
cubiertaCaras: cubiertaCaras,
|
||||
guardasPapelId: guardasPapelId,
|
||||
guardasGramaje: guardasGramaje,
|
||||
papelGuardasId: papelGuardasId,
|
||||
gramajeGuardas: gramajeGuardas,
|
||||
guardasImpresas: guardasImpresas,
|
||||
cabezada: cabezada,
|
||||
papelCubiertaId: papelCubiertaId,
|
||||
@ -1141,11 +1179,11 @@ class PresupuestoCliente {
|
||||
#updateCubiertaData(data) {
|
||||
|
||||
this.formData.cubierta.tipoCubierta = data.tipoCubierta;
|
||||
this.formData.cubierta.solapas = data.solapas;
|
||||
this.formData.cubierta.tamanioSolapas = data.tamanioSolapas;
|
||||
this.formData.cubierta.solapasCubierta = data.solapasCubierta;
|
||||
this.formData.cubierta.tamanioSolapasCubierta = data.tamanioSolapasCubierta;
|
||||
this.formData.cubierta.cubiertaCaras = data.cubiertaCaras;
|
||||
this.formData.cubierta.guardasPapelId = data.guardasPapelId;
|
||||
this.formData.cubierta.guardasGramaje = data.guardasGramaje;
|
||||
this.formData.cubierta.papelGuardasId = data.papelGuardasId;
|
||||
this.formData.cubierta.gramajeGuardas = data.gramajeGuardas;
|
||||
this.formData.cubierta.guardasImpresas = data.guardasImpresas;
|
||||
this.formData.cubierta.cabezada = data.cabezada;
|
||||
this.formData.cubierta.papelCubiertaId = data.papelCubiertaId;
|
||||
@ -1202,15 +1240,15 @@ class PresupuestoCliente {
|
||||
$('.tapa-dura-options').removeClass('d-none');
|
||||
$('.tapa-blanda-options').addClass('d-none');
|
||||
$('#papel-guardas option[data-papel-id="' +
|
||||
this.formData.cubierta.guardasPapelId + '"][data-gramaje="' +
|
||||
this.formData.cubierta.guardasGramaje + '"]').prop('selected', true).trigger('change');
|
||||
this.formData.cubierta.papelGuardasId + '"][data-gramaje="' +
|
||||
this.formData.cubierta.gramajeGuardas + '"]').prop('selected', true).trigger('change');
|
||||
this.guardasImpresas.val(this.formData.cubierta.guardasImpresas);
|
||||
this.cabezada.val(this.formData.cubierta.cabezada);
|
||||
}
|
||||
|
||||
$(`#${this.formData.cubierta.tipoCubierta}`).trigger('click');
|
||||
|
||||
if (this.formData.cubierta.solapas === 0) {
|
||||
if (this.formData.cubierta.solapasCubierta === 0) {
|
||||
$('.solapas-cubierta#sin-solapas').addClass('selected');
|
||||
this.divSolapasCubierta.addClass('d-none');
|
||||
}
|
||||
@ -1219,7 +1257,7 @@ class PresupuestoCliente {
|
||||
$(`.solapas-cubierta#con-solapas`).addClass('selected');
|
||||
this.divSolapasCubierta.removeClass('d-none');
|
||||
this.carasImpresionCubierta.val(this.formData.cubierta.cubiertaCaras);
|
||||
this.tamanioSolapasCubierta.val(this.formData.cubierta.tamanioSolapas);
|
||||
this.tamanioSolapasCubierta.val(this.formData.cubierta.tamanioSolapasCubierta);
|
||||
}
|
||||
|
||||
this.carasImpresionCubierta.val(this.formData.cubierta.cubiertaCaras);
|
||||
@ -1346,6 +1384,8 @@ class PresupuestoCliente {
|
||||
******************************/
|
||||
#initExtras() {
|
||||
|
||||
const self = this;
|
||||
|
||||
$(document).on('click', '.btn-change-tab-extras', (e) => {
|
||||
|
||||
const id = e.currentTarget.id;
|
||||
@ -1354,13 +1394,53 @@ class PresupuestoCliente {
|
||||
this.#changeTab('pills-seleccion-tirada');
|
||||
this.summaryTableExtras.addClass('d-none');
|
||||
} else {
|
||||
//this.#changeTab('pills-finalizar');
|
||||
|
||||
const servicios = [];
|
||||
$('.service-checkbox:checked').each(function () {
|
||||
const $servicio = $(this);
|
||||
servicios.push({
|
||||
id: $servicio.attr('id') ?? $(`label[for="${$servicio.attr('id')}"] .service-title`).text().trim(),
|
||||
label: $(`label[for="${$servicio.attr('id')}"] .service-title`).text().trim(),
|
||||
price: $servicio.data('price') ?? $(`label[for="${$servicio.attr('id')}"] .service-price`).text().trim().replace(" " + self.divExtras.data('currency'), ''),
|
||||
});
|
||||
});
|
||||
|
||||
const body = {
|
||||
presupuesto: this.#getPresupuestoData(),
|
||||
servicios: servicios
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: '/presupuesto/public/resumen',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(body)
|
||||
}).then((data) => {
|
||||
$('#resumen-titulo').text(data.titulo);
|
||||
this.#updateResumenTable(data);
|
||||
}).catch((error) => {
|
||||
console.error("Error obtener resumen: ", error);
|
||||
});
|
||||
|
||||
this.#changeTab('pills-resumen');
|
||||
}
|
||||
});
|
||||
|
||||
// Eventos para el resumen
|
||||
$(document).on('change', '.service-checkbox', (e) => {
|
||||
|
||||
const $target = $(e.currentTarget);
|
||||
|
||||
if ($target.prop('checked')) {
|
||||
this.formData.servicios.servicios.push($target.val());
|
||||
} else {
|
||||
const index = this.formData.servicios.servicios.indexOf($target.val());
|
||||
if (index > -1) {
|
||||
this.formData.servicios.servicios.splice(index, 1);
|
||||
}
|
||||
}
|
||||
Summary.updateExtras();
|
||||
this.#cacheFormData();
|
||||
});
|
||||
}
|
||||
|
||||
@ -1372,16 +1452,111 @@ class PresupuestoCliente {
|
||||
|
||||
this.divExtras.addClass('animate-fadeInUpBounce');
|
||||
for (const extra of servicios) {
|
||||
if (this.formData.servicios.servicios.includes(extra.id) && !extra.checked) {
|
||||
extra.checked = true;
|
||||
if (extra.id === "marcapaginas" || extra.id === "maquetacion") {
|
||||
extra.price = extra.id === "marcapaginas" ?
|
||||
this.formData.servicios.datosMarcapaginas.resultado.precio :
|
||||
this.formData.servicios.datosMaquetacion.resultado.precio;
|
||||
extra.priceUnit = this.divExtras.data('currency');
|
||||
}
|
||||
}
|
||||
const item = new ServiceOptionCard(extra);
|
||||
this.divExtras.append(item.render());
|
||||
if (item.checked) {
|
||||
if (!this.formData.servicios.servicios.includes(extra.id)) {
|
||||
this.formData.servicios.servicios.push(extra.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.#cacheFormData();
|
||||
Summary.updateExtras();
|
||||
}
|
||||
/******************************
|
||||
* END EXTRAS
|
||||
******************************/
|
||||
|
||||
|
||||
/******************************
|
||||
* END EXTRAS
|
||||
* RESUMEN
|
||||
******************************/
|
||||
#initResumen() {
|
||||
const $row = $('#presupuesto-row');
|
||||
|
||||
// 1) Transición al cambiar de pestaña (click o programático)
|
||||
$(document).on('shown.bs.tab', '.custom-nav .nav-link', (e) => {
|
||||
const targetSelector = $(e.target).data('bs-target'); // ej: "#pills-resumen"
|
||||
if (targetSelector === '#pills-resumen') {
|
||||
$row.addClass('expanded');
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
} else {
|
||||
$row.removeClass('expanded');
|
||||
}
|
||||
});
|
||||
|
||||
// 2) Botón "atrás" en Resumen
|
||||
$(document).on('click', '.btn-change-tab-resumen', (e) => {
|
||||
if (e.currentTarget.id === 'btn-prev-resumen') {
|
||||
this.#changeTab('pills-extras');
|
||||
}
|
||||
});
|
||||
|
||||
// 3) Estado inicial si ya cargas en Resumen
|
||||
$(function () {
|
||||
const activeTarget = $('.custom-nav .nav-link.active').data('bs-target');
|
||||
$('#presupuesto-row').toggleClass('expanded', activeTarget === '#pills-resumen');
|
||||
});
|
||||
}
|
||||
|
||||
#updateResumenTable(data) {
|
||||
this.tablaResumen.find('tbody').empty();
|
||||
|
||||
const lineas = Object.keys(data).filter(k => k.startsWith("linea")).sort((a, b) => {
|
||||
const numA = parseInt(a.replace("linea", ""), 10);
|
||||
const numB = parseInt(b.replace("linea", ""), 10);
|
||||
return numA - numB;
|
||||
});
|
||||
const servicios = data.servicios || [];
|
||||
|
||||
let total = 0;
|
||||
|
||||
const locale = document.documentElement.lang || 'es-ES';
|
||||
|
||||
for (const l of lineas) {
|
||||
const row = `
|
||||
<tr>
|
||||
<td>${l=="linea0" ? `<img style="max-width: 60px; height: auto;" src="${data.imagen}" alt="${data.imagen_alt}" class="img-fluid" />` : ''}</td>
|
||||
<td>${data[l].descripcion}</td>
|
||||
<td class="text-center">${data[l].cantidad}</td>
|
||||
<td class="text-center">${formateaMoneda(data[l].precio_unitario, 4, locale)}</td>
|
||||
<td class="text-end">${formateaMoneda(data[l].precio_total, 2, locale)}</td>
|
||||
</tr>
|
||||
`;
|
||||
total += data[l].precio_total;
|
||||
this.tablaResumen.find('tbody').append(row);
|
||||
}
|
||||
for (const s of servicios) {
|
||||
const row = `
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>${s.descripcion}</td>
|
||||
<td class="text-center">1</td>
|
||||
<td class="text-center">${formateaMoneda(s.precio, 2, locale)}</td>
|
||||
<td class="text-end">${formateaMoneda(s.precio, 2, locale)}</td>
|
||||
</tr>
|
||||
`;
|
||||
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));
|
||||
}
|
||||
|
||||
/******************************
|
||||
* END RESUMEN
|
||||
******************************/
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +1,14 @@
|
||||
import * as Summary from "./summary.js";
|
||||
import { formateaMoneda } from "../utils.js";
|
||||
|
||||
|
||||
$(document).on('change', '#maquetacion', function (e) {
|
||||
|
||||
e.preventDefault();
|
||||
if ($('#maquetacion').is(':checked')) {
|
||||
$.get("/presupuesto/public/maquetacion/form", function (data) {
|
||||
$("#maquetacionModalBody").html(data);
|
||||
loadMaquetacionData();
|
||||
$("#maquetacionModal").modal("show");
|
||||
});
|
||||
} else {
|
||||
@ -16,7 +20,9 @@ $(document).on('change', '#maquetacion', function (e) {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$(document).on("submit", "#maquetacionForm", function (e) {
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
const $form = $(this);
|
||||
@ -32,16 +38,15 @@ $(document).on("submit", "#maquetacionForm", function (e) {
|
||||
const modal = bootstrap.Modal.getInstance(modalEl) || new bootstrap.Modal(modalEl);
|
||||
modal.hide();
|
||||
|
||||
|
||||
const resumenHtml = `
|
||||
<div class="text-start">
|
||||
<p>Páginas calculadas: ${json.numPaginasEstimadas ?? "-"}</p>
|
||||
<p>Precio por página estimado: ${formateaMoneda(json.precioPaginaEstimado) || "-"}</p>
|
||||
<hr class="my-2">
|
||||
${json.precio ?
|
||||
`<h3 class="mb-0"><strong>Precio:</strong> ${formateaMoneda(json.precio)}</h3>` : ""}
|
||||
</div>
|
||||
`;
|
||||
<div class="text-start">
|
||||
<p>${json.language.num_paginas_estimadas || 'Páginas calculadas'}: ${json.numPaginasEstimadas ?? "-"}</p>
|
||||
<p>${json.language.precio_por_pagina_estimado || 'Precio por página estimado'}: ${formateaMoneda(json.precioPaginaEstimado) || "-"}</p>
|
||||
<hr class="my-2">
|
||||
${json.precio ?
|
||||
`<h3 class="mb-0"><strong>Precio:</strong> ${formateaMoneda(json.precio)}</h3>` : ""}
|
||||
</div>
|
||||
`;
|
||||
|
||||
Swal.fire({
|
||||
title: json.language.presupuesto_maquetacion || 'Presupuesto Maquetación',
|
||||
@ -64,6 +69,16 @@ $(document).on("submit", "#maquetacionForm", function (e) {
|
||||
$('label[for="maquetacion"] .service-price')
|
||||
.text(formateaMoneda(json.precio));
|
||||
Summary.updateExtras();
|
||||
|
||||
// guardamos los datos del formulario en sessionStorage
|
||||
const stored = JSON.parse(sessionStorage.getItem("formData"));
|
||||
stored.servicios.datosMaquetacion.resultado.num_paginas_estimadas = json.numPaginasEstimadas;
|
||||
stored.servicios.datosMaquetacion.resultado.precio_pagina_estimado = json.precioPaginaEstimado;
|
||||
stored.servicios.datosMaquetacion.resultado.precio = json.precio;
|
||||
if(stored.servicios.servicios.includes("maquetacion") === false) {
|
||||
stored.servicios.servicios.push("maquetacion");
|
||||
}
|
||||
sessionStorage.setItem("formData", JSON.stringify(stored));
|
||||
}
|
||||
else {
|
||||
const calcularStr = $('#div-extras').data('language-calcular');
|
||||
@ -97,19 +112,56 @@ $(document).on("submit", "#maquetacionForm", function (e) {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
$(document).on('hidden.bs.modal', '#maquetacionModal', function () {
|
||||
|
||||
const calcularStr = $('#div-extras').data('language-calcular');
|
||||
$('#maquetacion').prop('checked', false);
|
||||
$('#maquetacion').data('price', calcularStr);
|
||||
$('label[for="maquetacion"] .service-price').text(calcularStr);
|
||||
$('#maquetacion').prop('checked', false).trigger('change');
|
||||
|
||||
Summary.updateExtras();
|
||||
});
|
||||
|
||||
function formateaMoneda(valor) {
|
||||
try {
|
||||
return new Intl.NumberFormat('es-ES', { style: 'currency', currency: 'EUR' }).format(valor);
|
||||
} catch {
|
||||
return valor;
|
||||
}
|
||||
|
||||
$(document).on('change', '.maquetacion-item', () => {
|
||||
|
||||
saveMaquetacionData();
|
||||
});
|
||||
|
||||
|
||||
function loadMaquetacionData() {
|
||||
|
||||
const stored = JSON.parse(sessionStorage.getItem("formData")).servicios.datosMaquetacion;
|
||||
$('#num-caracteres').val(stored.num_caracteres);
|
||||
$('#formato-maquetacion').val(stored.formato_maquetacion);
|
||||
$('#cuerpo-texto').val(stored.cuerpo_texto);
|
||||
$('#num-columnas').val(stored.num_columnas);
|
||||
$('#num-tablas').val(stored.num_tablas);
|
||||
$('#num-fotos').val(stored.num_fotos);
|
||||
$('#correccion-ortotipografica').prop('checked', stored.correccion_ortotipografica);
|
||||
$('#texto-mecanografiado').prop('checked', stored.texto_mecanografiado);
|
||||
$('#disenio-portada').prop('checked', stored.disenio_portada);
|
||||
$('#epub').prop('checked', stored.epub);
|
||||
|
||||
Summary.updateExtras();
|
||||
}
|
||||
|
||||
|
||||
function saveMaquetacionData() {
|
||||
|
||||
const stored = JSON.parse(sessionStorage.getItem("formData"));
|
||||
stored.servicios.datosMaquetacion.num_caracteres = parseInt($('#num-caracteres').val()) || 0;
|
||||
stored.servicios.datosMaquetacion.formato_maquetacion = $('#formato-maquetacion').val() || 'A5';
|
||||
stored.servicios.datosMaquetacion.cuerpo_texto = $('#cuerpo-texto').val() || 'medium';
|
||||
stored.servicios.datosMaquetacion.num_columnas = parseInt($('#num-columnas').val()) || 1;
|
||||
stored.servicios.datosMaquetacion.num_tablas = parseInt($('#num-tablas').val()) || 0;
|
||||
stored.servicios.datosMaquetacion.num_fotos = parseInt($('#num-fotos').val()) || 0;
|
||||
stored.servicios.datosMaquetacion.correccion_ortotipografica = $('#correccion-ortotipografica').is(':checked');
|
||||
stored.servicios
|
||||
.datosMaquetacion.texto_mecanografiado = $('#texto-mecanografiado').is(':checked');
|
||||
stored.servicios.datosMaquetacion.disenio_portada = $('#disenio-portada').is(':checked');
|
||||
stored.servicios.datosMaquetacion.epub = $('#epub').is(':checked');
|
||||
sessionStorage.setItem("formData", JSON.stringify(stored));
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,186 @@
|
||||
import * as Summary from "./summary.js";
|
||||
import { formateaMoneda } from "../utils.js";
|
||||
|
||||
$(document).on('change', '#marcapaginas', function (e) {
|
||||
e.preventDefault();
|
||||
if ($('#marcapaginas').is(':checked')) {
|
||||
$.get("/presupuesto/public/marcapaginas/form", function (data) {
|
||||
$("#marcapaginasModalBody").html(data);
|
||||
|
||||
loadMarcapaginasData();
|
||||
|
||||
// init marcapaginas form
|
||||
filtrarAcabados();
|
||||
|
||||
$("#marcapaginasModal").modal("show");
|
||||
});
|
||||
} else {
|
||||
const calcularStr = $('#div-extras').data('language-calcular');
|
||||
$('#marcapaginas').data('price', calcularStr);
|
||||
$('label[for="marcapaginas"] .service-price')
|
||||
.text(calcularStr);
|
||||
$('#marcapaginas').prop('checked', false);
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on("submit", "#marcapaginasForm", function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const $form = $(this);
|
||||
|
||||
$.ajax({
|
||||
url: $form.attr("action"),
|
||||
type: $form.attr("method"),
|
||||
data: $form.serialize(),
|
||||
dataType: "json",
|
||||
success: function (json) {
|
||||
|
||||
const modalEl = document.getElementById("marcapaginasModal");
|
||||
const modal = bootstrap.Modal.getInstance(modalEl) || new bootstrap.Modal(modalEl);
|
||||
modal.hide();
|
||||
|
||||
|
||||
const resumenHtml = `
|
||||
<div class="text-start">
|
||||
<p>${json.language.precio_unidad || 'Precio por unidad'}: ${formateaMoneda(json.precio_unitario, 6) || "-"}</p>
|
||||
<h3 class="mb-0">${json.language.precio_total || 'Precio total'}: ${formateaMoneda(json.precio_total) || "-"}</h3>
|
||||
</div>
|
||||
`;
|
||||
|
||||
Swal.fire({
|
||||
title: json.language.presupuesto_marcapaginas || 'Presupuesto Marcapáginas',
|
||||
html: resumenHtml,
|
||||
icon: 'info',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: json.language.add_to_presupuesto || 'Añadir al presupuesto',
|
||||
cancelButtonText: json.language.cancel || 'Cancelar',
|
||||
customClass: {
|
||||
confirmButton: 'btn btn-secondary me-2', // clases para el botón confirmar
|
||||
cancelButton: 'btn btn-light' // clases para cancelar
|
||||
},
|
||||
buttonsStyling: false,
|
||||
reverseButtons: false,
|
||||
allowOutsideClick: false
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
$('#marcapaginas').prop('checked', true);
|
||||
$('#marcapaginas').data('price', json.precio_total);
|
||||
$('label[for="marcapaginas"] .service-price')
|
||||
.text(formateaMoneda(json.precio_total));
|
||||
Summary.updateExtras();
|
||||
|
||||
// guardamos los datos del formulario en sessionStorage
|
||||
const stored = JSON.parse(sessionStorage.getItem("formData"));
|
||||
stored.servicios.datosMarcapaginas.resultado.precio_unitario = json.precio_unitario;
|
||||
stored.servicios.datosMarcapaginas.resultado.precio = json.precio_total;
|
||||
if(stored.servicios.servicios.includes("marcapaginas") === false) {
|
||||
stored.servicios.servicios.push("marcapaginas");
|
||||
}
|
||||
sessionStorage.setItem("formData", JSON.stringify(stored));
|
||||
}
|
||||
else {
|
||||
const calcularStr = $('#div-extras').data('language-calcular');
|
||||
$('#marcapaginas').prop('checked', false);
|
||||
$('#marcapaginas').data('price', calcularStr);
|
||||
$('label[for="marcapaginas"] .service-price').text(calcularStr);
|
||||
}
|
||||
});
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
try {
|
||||
const errs = JSON.parse(xhr.responseText); // { numCaracteres: "…" , … }
|
||||
// limpia errores previos
|
||||
$form.find(".is-invalid").removeClass("is-invalid");
|
||||
$form.find(".invalid-feedback").text("");
|
||||
|
||||
// recorre los errores y los muestra
|
||||
Object.entries(errs).forEach(([field, msg]) => {
|
||||
const $input = $form.find(`[name="${field}"]`);
|
||||
if ($input.length) {
|
||||
$input.addClass("is-invalid");
|
||||
$input.siblings(".invalid-feedback").text(msg);
|
||||
}
|
||||
});
|
||||
} catch {
|
||||
$("#marcapaginasModalBody").html(
|
||||
"<div class='alert alert-danger'>" + xhr.responseText + "</div>"
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
$(document).on("change", "#caras-impresion", function (e) {
|
||||
e.preventDefault();
|
||||
filtrarAcabados();
|
||||
});
|
||||
|
||||
|
||||
$(document).on('hidden.bs.modal', '#marcapaginasModal', function () {
|
||||
|
||||
const calcularStr = $('#div-extras').data('language-calcular');
|
||||
$('#marcapaginas').data('price', calcularStr);
|
||||
$('label[for="marcapaginas"] .service-price').text(calcularStr);
|
||||
$('#marcapaginas').prop('checked', false).trigger('change');
|
||||
Summary.updateExtras();
|
||||
});
|
||||
|
||||
|
||||
function filtrarAcabados() {
|
||||
const $select = $("#acabado-marcapaginas");
|
||||
const caras = $("#caras-impresion").val(); // "una_cara" o "dos_caras"
|
||||
|
||||
$select.find("option")
|
||||
.prop("disabled", true)
|
||||
.attr("hidden", true);
|
||||
|
||||
if (caras === "una_cara") {
|
||||
$select.find("option.marcapaginas-1cara")
|
||||
.prop("disabled", false)
|
||||
.attr("hidden", false);
|
||||
} else {
|
||||
$select.find("option.marcapaginas-2caras")
|
||||
.prop("disabled", false)
|
||||
.attr("hidden", false);
|
||||
}
|
||||
|
||||
if ($select.find("option:selected").prop("disabled")) {
|
||||
const firstEnabled = $select.find("option:not([disabled]):first").val();
|
||||
$select.val(firstEnabled).trigger("change");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$(document).on('change', '.marcapaginas-item', () => {
|
||||
|
||||
saveMarcapaginasData();
|
||||
});
|
||||
|
||||
|
||||
function loadMarcapaginasData() {
|
||||
|
||||
const stored = JSON.parse(sessionStorage.getItem("formData")).servicios.datosMarcapaginas;
|
||||
$('#marcapaginas-tirada').val(stored.marcapaginas_tirada);
|
||||
$('#tamanio-marcapaginas').val(stored.tamanio_marcapaginas);
|
||||
$('#caras-impresion').val(stored.caras_impresion);
|
||||
$('#papel-marcapaginas').val(stored.papel_marcapaginas);
|
||||
$('#gramaje-marcapaginas').val(stored.gramaje_marcapaginas);
|
||||
$('#acabado-marcapaginas').val(stored.acabado_marcapaginas);
|
||||
}
|
||||
|
||||
|
||||
function saveMarcapaginasData() {
|
||||
|
||||
const stored = JSON.parse(sessionStorage.getItem("formData"));
|
||||
stored.servicios.datosMarcapaginas.marcapaginas_tirada = parseInt($('#marcapaginas-tirada').val()) || 100;
|
||||
stored.servicios.datosMarcapaginas.tamanio_marcapaginas = $('#tamanio-marcapaginas').val() || '_50x140_';
|
||||
stored.servicios.datosMarcapaginas.caras_impresion = $('#caras-impresion').val() || 'una_cara';
|
||||
stored.servicios.datosMarcapaginas.papel_marcapaginas = $('#papel-marcapaginas').val() || 'cartulina_grafica';
|
||||
stored.servicios.datosMarcapaginas.gramaje_marcapaginas = parseInt($('#gramaje-marcapaginas').val()) || 300;
|
||||
stored.servicios.datosMarcapaginas.acabado_marcapaginas = $('#acabado-marcapaginas').val() || 'ninguno';
|
||||
sessionStorage.setItem("formData", JSON.stringify(stored));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import {formateaMoneda, isNumber} from '../utils.js';
|
||||
|
||||
class ServiceOptionCard {
|
||||
|
||||
constructor({ id, title, description = '', price = '', priceUnit = '', checked = false, allowChange = true, ribbonText }) {
|
||||
@ -23,7 +25,7 @@ class ServiceOptionCard {
|
||||
${ribbonHtml}
|
||||
<h5 class="service-title mb-1">${this.title}</h5>
|
||||
<p class="service-desc mb-1 small">${this.description}</p>
|
||||
<h4 class="service-price fw-semibold mt-2 h4 mb-0">${this.price} ${this.priceUnit}</h4>
|
||||
<h4 class="service-price fw-semibold mt-2 h4 mb-0">${isNumber(this.price) ? formateaMoneda(this.price, 2, this.locale) : this.price}</h4>
|
||||
</label>
|
||||
</div>
|
||||
`);
|
||||
|
||||
@ -152,7 +152,7 @@ export function updateExtras() {
|
||||
const price = $(`label[for="${$servicio.attr('id')}"] .service-price`).text().trim() || $servicio.attr('price');
|
||||
const $row = $('<tr>').append(
|
||||
$('<td>').append($('<span>').text(resumen)),
|
||||
$('<td class="text-end">').text(price)
|
||||
$('<td class="text-end data-summary" data-id-summary="servicio-' + $servicio.attr('id') + '">').text(price)
|
||||
);
|
||||
$tbody.append($row);
|
||||
});
|
||||
@ -161,4 +161,5 @@ export function updateExtras() {
|
||||
} else {
|
||||
$table.addClass('d-none');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { formateaMoneda, formateaNumero } from "../utils.js";
|
||||
|
||||
// ===== Clase =====
|
||||
class TiradaCard {
|
||||
constructor({ id, titulo, unidades, precioUnidad, precioTotal, selected = false, moneda = '€', name = 'tirada',
|
||||
constructor({ id, titulo, unidades, precioUnidad, precioTotal, selected = false, name = 'tirada',
|
||||
labels = {}, locale = (document.documentElement.lang || navigator.language || 'es-ES')
|
||||
}) {
|
||||
this.id = id;
|
||||
@ -9,7 +11,6 @@ class TiradaCard {
|
||||
this.precioUnidad = precioUnidad;
|
||||
this.precioTotal = precioTotal;
|
||||
this.selected = selected;
|
||||
this.moneda = moneda;
|
||||
this.name = name;
|
||||
this.locale = locale;
|
||||
this.labels = Object.assign({
|
||||
@ -22,16 +23,10 @@ class TiradaCard {
|
||||
this.$container = null; // se establece al renderizar
|
||||
}
|
||||
|
||||
#formatMoneyES(value, decimals = 2) {
|
||||
return new Intl.NumberFormat(this.locale, {
|
||||
minimumFractionDigits: decimals,
|
||||
maximumFractionDigits: decimals
|
||||
}).format(Number(value));
|
||||
}
|
||||
|
||||
#title() {
|
||||
if (this.titulo) return this.titulo;
|
||||
if (this.unidades != null) return `${this.unidades} ${this.labels.units}`;
|
||||
if (this.unidades != null) return `${formateaNumero({valor: this.unidades, locale: this.locale, digits: 0})} ${this.labels.units}`;
|
||||
return '';
|
||||
}
|
||||
|
||||
@ -46,11 +41,11 @@ class TiradaCard {
|
||||
<div class="title">${this.#title()}</div>
|
||||
|
||||
<div class="per muted">${this.labels.perUnit}</div>
|
||||
<div class="price-big">${this.#formatMoneyES(this.precioUnidad, 4)} ${this.moneda}</div>
|
||||
<div class="price-big">${formateaMoneda(this.precioUnidad, 4, this.locale)}</div>
|
||||
|
||||
<div class="price-row">
|
||||
<div class="muted">${this.labels.total}</div>
|
||||
<div class="price-total fw-bold">${this.#formatMoneyES(this.precioTotal, 2)} ${this.moneda}</div>
|
||||
<div class="price-total fw-bold">${formateaMoneda(this.precioTotal, 2, this.locale)}</div>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-select-tirada">${this.labels.select}</button>
|
||||
|
||||
@ -0,0 +1,67 @@
|
||||
export function formateaMoneda(valor, digits = 2, locale = 'es-ES', currency = 'EUR') {
|
||||
try {
|
||||
return new Intl.NumberFormat(locale, { style: 'currency', currency, minimumFractionDigits: digits, useGrouping: true }).format(valor);
|
||||
} catch {
|
||||
return valor;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function formateaNumero({
|
||||
valor,
|
||||
digits = 2,
|
||||
style = 'decimal',
|
||||
locale = 'es-ES',
|
||||
currency = 'EUR'
|
||||
}) {
|
||||
const n = Number(valor);
|
||||
if (!Number.isFinite(n)) return valor;
|
||||
|
||||
const opts = {
|
||||
useGrouping: true,
|
||||
minimumFractionDigits: digits,
|
||||
maximumFractionDigits: digits,
|
||||
};
|
||||
|
||||
if (style === 'currency') {
|
||||
opts.style = 'currency';
|
||||
opts.currency = currency;
|
||||
}
|
||||
|
||||
return new Intl.NumberFormat(locale, opts).format(n);
|
||||
}
|
||||
|
||||
|
||||
export function isNumber(value) {
|
||||
|
||||
if(typeof value === 'string') {
|
||||
if(value.trim() === '') return false;
|
||||
}
|
||||
return !isNaN(Number(value));
|
||||
}
|
||||
|
||||
// Aplana un objeto a "prefijo.clave" (sin arrays)
|
||||
export function dotify(obj, prefix = '') {
|
||||
const out = {};
|
||||
const walk = (o, path) => {
|
||||
Object.entries(o).forEach(([k, v]) => {
|
||||
const key = path ? `${path}.${k}` : k;
|
||||
if (v !== null && typeof v === 'object' && !Array.isArray(v)) {
|
||||
walk(v, key);
|
||||
} else {
|
||||
out[key] = v;
|
||||
}
|
||||
});
|
||||
};
|
||||
walk(obj, prefix);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Convierte {a:1, b:2} en {"summary[a]":1, "summary[b]":2}
|
||||
export function bracketPrefix(obj, prefix) {
|
||||
const out = {};
|
||||
Object.entries(obj).forEach(([k, v]) => {
|
||||
out[`${prefix}[${k}]`] = v;
|
||||
});
|
||||
return out;
|
||||
}
|
||||
@ -37,6 +37,7 @@
|
||||
<script th:src="@{/assets/js/pages/imprimelibros/presupuestador/imagen-selector.js}"></script>
|
||||
<script type="module" th:src="@{/assets/js/pages/imprimelibros/presupuestador/presupuestador.js}"></script>
|
||||
<script type="module" th:src="@{/assets/js/pages/imprimelibros/presupuestador/presupuesto-maquetacion.js}"></script>
|
||||
<script type="module" th:src="@{/assets/js/pages/imprimelibros/presupuestador/presupuesto-marcapaginas.js}"></script>
|
||||
</div>
|
||||
<script th:inline="javascript">
|
||||
window.languageBundle = /*[[${languageBundle}]]*/ {};
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
<label for="titulo" class="form-label" th:text="#{presupuesto.titulo}">
|
||||
>Título*</label>
|
||||
<input type="text" class="form-control datos-generales-data" id="titulo" placeholder=""
|
||||
value="">
|
||||
th:value="${presupuesto?.titulo} ?: ''">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -33,13 +33,13 @@
|
||||
<div class="col-sm-6">
|
||||
<div class="mb-3">
|
||||
<label for="autor" class="form-label" th:text="#{presupuesto.autor}">Autor</label>
|
||||
<input type="text" class="form-control datos-generales-data" id="autor" value="">
|
||||
<input type="text" class="form-control datos-generales-data" id="autor" th:value="${presupuesto?.autor} ?: ''">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="mb-3">
|
||||
<label for="isbn" class="form-label" th:text="#{presupuesto.isbn}">ISBN</label>
|
||||
<input type="text" class="form-control datos-generales-data" id="isbn" value="">
|
||||
<input type="text" class="form-control datos-generales-data" id="isbn" th:value="${presupuesto?.isbn} ?: ''" >
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
</div>
|
||||
|
||||
<div class="ribbon-content mt-4">
|
||||
<div id="div-extras" class="hstack gap-2 justify-content-center flex-wrap">
|
||||
<div id="div-extras" class="hstack gap-2 justify-content-center flex-wrap" th:data-currency="#{app.currency-symbol}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -24,7 +24,7 @@
|
||||
</button>
|
||||
<button id="btn-next-extras" type="button"
|
||||
class="btn btn-secondary d-flex align-items-center btn-change-tab-extras">
|
||||
<span><b th:text="#{presupuesto.calcular-presupuesto}">Calcular presupuesto</b></span>
|
||||
<span><b th:text="#{presupuesto.resumen}">Resumen</b></span>
|
||||
<i class="ri-arrow-right-circle-line fs-16 ms-2"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -0,0 +1,70 @@
|
||||
<div class="animate-fadeInUpBounce">
|
||||
|
||||
<!-- Ribbon Shape -->
|
||||
<div class="card ribbon-box border shadow-none mb-lg-0 material-shadow">
|
||||
<div class="card-body">
|
||||
<div class="ribbon ribbon-primary ribbon-shape" th:text="#{presupuesto.resumen}">Resumen
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ribbon-content mt-4">
|
||||
<div id="div-extras" class="hstack gap-2 justify-content-center flex-wrap">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End Ribbon Shape -->
|
||||
|
||||
<div class="col-9 mx-auto mt-4">
|
||||
<h5 id="resumen-titulo" class="text-center"></h5>
|
||||
<table id="resumen-tabla-final" class="table table-borderless table-striped mt-3"
|
||||
th:data-currency="#{app.currency}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th th:text="#{presupuesto.resumen.tabla.descripcion}">Descripción</th>
|
||||
<th th:text="#{presupuesto.resumen.tabla.cantidad}">Cantidad</th>
|
||||
<th th:text="#{presupuesto.resumen.tabla.precio-unidad}">Precio unitario</th>
|
||||
<th th:text="#{presupuesto.resumen.tabla.precio-total}">Precio total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="table-active">
|
||||
<th colspan="4" class="text-end" th:text="#{presupuesto.resumen.tabla.base}">Total</th>
|
||||
<th class="text-end" id="resumen-base">0,00 €</th>
|
||||
</tr>
|
||||
<tr class="table-active">
|
||||
<th colspan="4" class="text-end" th:text="#{presupuesto.resumen.tabla.iva}">IVA (4%)</th>
|
||||
<th class="text-end" id="resumen-iva">0,00 €</th>
|
||||
</tr>
|
||||
<tr class="table-active">
|
||||
<th colspan="4" class="text-end" th:text="#{presupuesto.resumen.tabla.total}">Total con IVA</th>
|
||||
<th class="text-end" id="resumen-total">0,00 €</th>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mt-4 w-100">
|
||||
<button id="btn-prev-resumen" type="button"
|
||||
class="btn btn-light d-flex align-items-center btn-change-tab-resumen">
|
||||
<i class=" ri-arrow-left-circle-line label-icon align-middle fs-16 me-2"></i>
|
||||
<span th:text="#{presupuesto.volver-extras}">Volver a extras</span>
|
||||
</button>
|
||||
|
||||
<div th:unless="${#authorization.expression('isAuthenticated()')}">
|
||||
<button id="btn-add-cart" type="button"
|
||||
class="btn btn-secondary d-flex align-items-center btn-change-tab-resumen">
|
||||
<i class="mdi mdi-login label-icon align-middle fs-16 me-2"></i>
|
||||
<span th:text="#{presupuesto.resumen.inicie-sesion}">Inicie sesión para continuar</span>
|
||||
</button>
|
||||
</div>
|
||||
<div th:if="${#authorization.expression('isAuthenticated()')}">
|
||||
<button id="btn-add-cart" type="button"
|
||||
class="btn btn-secondary d-flex align-items-center">
|
||||
<span th:text="#{presupuesto.resumen.agregar-cesta}">Agregar a la cesta</span>
|
||||
<i class="ri-shopping-cart-2-line fs-16 ms-2"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,4 +1,4 @@
|
||||
<div class="col-xl-3">
|
||||
<div class="col-xl-3 summary-col">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="d-flex">
|
||||
@ -20,19 +20,19 @@
|
||||
<td class="align-items-center">
|
||||
<span th:text="#{presupuesto.resumen-encuadernacion}"></span>
|
||||
</td>
|
||||
<td id="summary-encuadernacion" class="text-end">Fresado</td>
|
||||
<td id="summary-encuadernacion" class="text-end data-summary" data-id-summary="encuadernacion">Fresado</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span th:text="#{presupuesto.formato}"></span>
|
||||
</td>
|
||||
<td id="summary-formato" class="text-end"></td>
|
||||
<td id="summary-formato" class="text-end data-summary" data-id-summary="formato"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span th:text="#{presupuesto.paginas}"></span>
|
||||
</td>
|
||||
<td id="summary-paginas" class="text-end"></td>
|
||||
<td id="summary-paginas" class="text-end data-summary" data-id-summary="paginas"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="ps-3">
|
||||
@ -60,19 +60,19 @@
|
||||
<td>
|
||||
<span th:text="#{presupuesto.tipo-interior}"></span>
|
||||
</td>
|
||||
<td id="summary-tipo-interior" class="text-end"></td>
|
||||
<td id="summary-tipo-interior" class="text-end data-summary" data-id-summary="tipo-interior"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span th:text="#{presupuesto.papel-interior}"></span>
|
||||
</td>
|
||||
<td id="summary-papel-interior" class="text-end"></td>
|
||||
<td id="summary-papel-interior" class="text-end data-summary" data-id-summary="papel-interior"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span th:text="#{presupuesto.gramaje-interior}"></span>
|
||||
</td>
|
||||
<td id="summary-gramaje-interior" class="text-end"></td>
|
||||
<td id="summary-gramaje-interior" class="text-end data-summary" data-id-summary="gramaje-interior"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@ -87,61 +87,61 @@
|
||||
<td>
|
||||
<span th:text="#{presupuesto.tipo-cubierta}"></span>
|
||||
</td>
|
||||
<td id="summary-tapa-cubierta" class="text-end"></td>
|
||||
<td id="summary-tapa-cubierta" class="text-end data-summary" data-id-summary="tapa-cubierta"></td>
|
||||
</tr>
|
||||
<tr class="tapa-blanda-row d-none">
|
||||
<td>
|
||||
<span th:text="#{presupuesto.solapas}"></span>
|
||||
</td>
|
||||
<td id="summary-cubierta-solapas" class="text-end"></td>
|
||||
<td id="summary-cubierta-solapas" class="text-end data-summary" data-id-summary="cubierta-solapas"></td>
|
||||
</tr>
|
||||
<tr class="tapa-blanda-row d-none">
|
||||
<td class="ps-3">
|
||||
<span class="ps-3" th:text="#{presupuesto.tamanio-solapa}"></span>
|
||||
</td>
|
||||
<td id="summary-tamanio-solapa" class="text-end"></td>
|
||||
<td id="summary-tamanio-solapa" class="text-end data-summary" data-id-summary="tamanio-solapa"></td>
|
||||
</tr>
|
||||
<tr class="tapa-blanda-row d-none">
|
||||
<td>
|
||||
<span th:text="#{presupuesto.impresion-cubierta}"></span>
|
||||
</td>
|
||||
<td id="summary-impresion-cubierta" class="text-end"></td>
|
||||
<td id="summary-impresion-cubierta" class="text-end data-summary" data-id-summary="impresion-cubierta"></td>
|
||||
</tr>
|
||||
<tr class="tapa-dura-row d-none">
|
||||
<td>
|
||||
<span th:text="#{presupuesto.papel-guardas}"></span>
|
||||
</td>
|
||||
<td id="summary-papel-guardas" class="text-end"></td>
|
||||
<td id="summary-papel-guardas" class="text-end data-summary" data-id-summary="papel-guardas"></td>
|
||||
</tr>
|
||||
<tr class="tapa-dura-row d-none">
|
||||
<td>
|
||||
<span th:text="#{presupuesto.guardas-impresas}"></span>
|
||||
</td>
|
||||
<td id="summary-guardas-impresas" class="text-end"></td>
|
||||
<td id="summary-guardas-impresas" class="text-end data-summary" data-id-summary="guardas-impresas"></td>
|
||||
</tr>
|
||||
<tr class="tapa-dura-row d-none">
|
||||
<td>
|
||||
<span th:text="#{presupuesto.cabezada}"></span>
|
||||
</td>
|
||||
<td id="summary-cabezada" class="text-end"></td>
|
||||
<td id="summary-cabezada" class="text-end data-summary" data-id-summary="cabezada"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span th:text="#{presupuesto.papel-cubierta}"></span>
|
||||
</td>
|
||||
<td id="summary-papel-cubierta" class="text-end"></td>
|
||||
<td id="summary-papel-cubierta" class="text-end data-summary" data-id-summary="papel-cubierta"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span th:text="#{presupuesto.gramaje-cubierta}"></span>
|
||||
</td>
|
||||
<td id="summary-gramaje-cubierta" class="text-end"></td>
|
||||
<td id="summary-gramaje-cubierta" class="text-end data-summary" data-id-summary="gramaje-cubierta"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span th:text="#{presupuesto.acabado}"></span>
|
||||
</td>
|
||||
<td id="summary-acabado-cubierta" class="text-end"></td>
|
||||
<td id="summary-acabado-cubierta" class="text-end data-summary" data-id-summary="acabado-cubierta"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@ -156,19 +156,19 @@
|
||||
<td>
|
||||
<span th:text="#{presupuesto.papel-gramaje}"></span>
|
||||
</td>
|
||||
<td id="summary-sobrecubierta-papel-gramaje" class="text-end"></td>
|
||||
<td id="summary-sobrecubierta-papel-gramaje" class="text-end data-summary" data-id-summary="sobrecubierta-papel-gramaje"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span th:text="#{presupuesto.tamanio-solapa}"></span>
|
||||
</td>
|
||||
<td id="summary-sobrecubierta-tamanio-solapa" class="text-end"></td>
|
||||
<td id="summary-sobrecubierta-tamanio-solapa" class="text-end data-summary" data-id-summary="sobrecubierta-tamanio-solapa"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span th:text="#{presupuesto.acabado}"></span>
|
||||
</td>
|
||||
<td id="summary-sobrecubierta-acabado" class="text-end"></td>
|
||||
<td id="summary-sobrecubierta-acabado" class="text-end data-summary" data-id-summary="sobrecubierta-acabado"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@ -183,25 +183,25 @@
|
||||
<td>
|
||||
<span th:text="#{presupuesto.papel-gramaje}"></span>
|
||||
</td>
|
||||
<td id="summary-faja-papel-gramaje" class="text-end"></td>
|
||||
<td id="summary-faja-papel-gramaje" class="text-end data-summary" data-id-summary="faja-papel-gramaje"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span th:text="#{presupuesto.faja-alto}"></span>
|
||||
</td>
|
||||
<td id="summary-faja-alto-faja" class="text-end"></td>
|
||||
<td id="summary-faja-alto-faja" class="text-end data-summary" data-id-summary="faja-alto-faja"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span th:text="#{presupuesto.tamanio-solapa}"></span>
|
||||
</td>
|
||||
<td id="summary-faja-tamanio-solapa" class="text-end"></td>
|
||||
<td id="summary-faja-tamanio-solapa" class="text-end data-summary" data-id-summary="faja-tamanio-solapa"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span th:text="#{presupuesto.acabado}"></span>
|
||||
</td>
|
||||
<td id="summary-faja-acabado" class="text-end"></td>
|
||||
<td id="summary-faja-acabado" class="text-end data-summary" data-id-summary="faja-acabado"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
<div th:fragment="presupuestador">
|
||||
<!-- Modales-->
|
||||
<div
|
||||
th:replace="imprimelibros/partials/modal-form :: modal('maquetacionModal', 'presupuesto.maquetacion', 'modal-lg', 'maquetacionModalBody')">
|
||||
th:replace="imprimelibros/partials/modal-form :: modal('maquetacionModal', 'presupuesto.maquetacion', 'modal-md', 'maquetacionModalBody')">
|
||||
</div>
|
||||
|
||||
<div
|
||||
th:replace="imprimelibros/partials/modal-form :: modal('marcapaginasModal', 'presupuesto.marcapaginas', 'modal-lg', 'marcapaginasModalBody')">
|
||||
th:replace="imprimelibros/partials/modal-form :: modal('marcapaginasModal', 'presupuesto.marcapaginas', 'modal-md', 'marcapaginasModalBody')">
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xl-9">
|
||||
<div class="row" id="presupuesto-row">
|
||||
<div class="col-xl-9 col-main">
|
||||
<div class="card">
|
||||
<div class="card-body checkout-tab">
|
||||
|
||||
@ -61,6 +61,15 @@
|
||||
<label th:text="#{presupuesto.extras}">Extras</label>
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link fs-15 p-3" id="pills-resumen-tab"
|
||||
data-bs-target="#pills-resumen" type="button" role="tab"
|
||||
aria-controls="pills-resumen" aria-selected="false">
|
||||
<i
|
||||
class="ri-add-box-line fs-16 p-2 bg-soft-primary text-primary rounded-circle align-middle me-2"></i>
|
||||
<label th:text="#{presupuesto.resumen}">Resumen</label>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@ -106,6 +115,13 @@
|
||||
</div>
|
||||
<!-- end tab pane -->
|
||||
|
||||
<div class="tab-pane fade" id="pills-resumen" role="tabpanel"
|
||||
aria-labelledby="pills-resumen-tab">
|
||||
|
||||
<div th:include="~{imprimelibros/presupuestos/presupuestador-items/_resumen_final.html}"></div>
|
||||
</div>
|
||||
<!-- end tab pane -->
|
||||
|
||||
</div>
|
||||
<!-- end tab content -->
|
||||
</form>
|
||||
|
||||
@ -2,13 +2,13 @@
|
||||
<form id="maquetacionForm" novalidate th:action="@{/presupuesto/public/maquetacion}" th:object="${presupuestoMaquetacion}" method="get">
|
||||
<div class="form-group">
|
||||
<label th:text="#{presupuesto.maquetacion.num-caracteres}" for="num-caracteres">Número de caracteres</label>
|
||||
<input type="number" class="form-control" id="num-caracteres" th:field="*{numCaracteres}" min="1" required>
|
||||
<input type="number" class="form-control maquetacion-item" id="num-caracteres" th:field="*{numCaracteres}" min="1" required>
|
||||
<div class="invalid-feedback"></div>
|
||||
<label th:text="#{presupuesto.maquetacion.num-caracteres-descripcion}" class="form-text text-muted"></label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label th:text="#{presupuesto.maquetacion.formato}" for="formato-maquetacion">Formato</label>
|
||||
<select class="form-control" id="formato-maquetacion" th:field="*{formato}" required>
|
||||
<select class="form-control maquetacion-item" id="formato-maquetacion" th:field="*{formato}" required>
|
||||
<option value="A5" selected>A5</option>
|
||||
<option value="_17x24_">170x240 mm</option>
|
||||
<option value="A4">A4</option>
|
||||
@ -17,52 +17,52 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label th:text="#{presupuesto.maquetacion.cuerpo-texto}" for="cuerpo-texto">Cuerpo de texto</label>
|
||||
<select class="form-control" id="cuerpo-texto" th:field="*{cuerpoTexto}" required>
|
||||
<select class="form-control maquetacion-item" id="cuerpo-texto" th:field="*{cuerpoTexto}" required>
|
||||
<option th:text="#{presupuesto.maquetacion.cuerpo-texto-pequeño}" value="small"></option>
|
||||
<option th:text="#{presupuesto.maquetacion.cuerpo-texto-medio}" value="medium" selected>Medio (12pt)
|
||||
</option>
|
||||
<option th:text="#{presupuesto.maquetacion.cuerpo-texto-grande}" value="large">Grande</option>
|
||||
<option th:text="#{presupuesto.maquetacion.cuerpo-texto-grande}" value="big">Grande</option>
|
||||
</select>
|
||||
<label th:text="#{presupuesto.maquetacion.cuerpo-texto-descripcion}" class="form-text text-muted"></label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label th:text="#{presupuesto.maquetacion.num-columnas}" for="num-columnas">Número de columnas</label>
|
||||
<input type="number" class="form-control" id="num-columnas" th:field="*{numColumnas}" min="1" required>
|
||||
<input type="number" class="form-control maquetacion-item" id="num-columnas" th:field="*{numColumnas}" min="1" required>
|
||||
<div class="invalid-feedback"></div>
|
||||
<label th:text="#{presupuesto.maquetacion.num-columnas-descripcion}" class="form-text text-muted"></label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label th:text="#{presupuesto.maquetacion.num-tablas}" for="num-tablas">Número de tablas</label>
|
||||
<input type="number" class="form-control" id="num-tablas" th:field="*{numTablas}" min="0" required>
|
||||
<input type="number" class="form-control maquetacion-item" id="num-tablas" th:field="*{numTablas}" min="0" required>
|
||||
<div class="invalid-feedback"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label th:text="#{presupuesto.maquetacion.num-fotos}" for="num-fotos">Número de fotos</label>
|
||||
<input type="number" class="form-control" id="num-fotos" th:field="*{numFotos}" min="0" required>
|
||||
<input type="number" class="form-control maquetacion-item" id="num-fotos" th:field="*{numFotos}" min="0" required>
|
||||
<div class="invalid-feedback"></div>
|
||||
</div>
|
||||
<div class="form-check form-switch form-switch-custom mb-3 mt-3">
|
||||
<input type="checkbox" class="form-check-input form-switch-custom-primary"
|
||||
<input type="checkbox" class="form-check-input form-switch-custom-primary maquetacion-item"
|
||||
id="correccion-ortotipografica" name="correccion-ortotipografica"
|
||||
th:field="*{correccionOrtotipografica}">
|
||||
<label class="form-check-label" for="correccion-ortotipografica">Corrección ortotipográfica</label>
|
||||
</div>
|
||||
<div class="form-check form-switch form-switch-custom mb-3">
|
||||
<input type="checkbox" class="form-check-input form-switch-custom-primary"
|
||||
<input type="checkbox" class="form-check-input form-switch-custom-primary maquetacion-item"
|
||||
id="texto-mecanografiado" name="texto-mecanografiado" th:field="*{textoMecanografiado}">
|
||||
<label for="texto-mecanografiado" class="form-check-label" th:text="#{presupuesto.maquetacion.texto-mecanografiado}">
|
||||
Texto mecanografiado
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check form-switch form-switch-custom mb-3">
|
||||
<input type="checkbox" class="form-check-input form-switch-custom-primary" id="disenio-portada"
|
||||
<input type="checkbox" class="form-check-input form-switch-custom-primary maquetacion-item" id="disenio-portada"
|
||||
name="disenio-portada" th:field="*{disenioPortada}">
|
||||
<label for="disenio-portada" class="form-check-label" th:text="#{presupuesto.maquetacion.diseno-portada}">
|
||||
Diseño de portada
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check form-switch form-switch-custom">
|
||||
<input type="checkbox" class="form-check-input form-switch-custom-primary" id="epub" name="epub"
|
||||
<input type="checkbox" class="form-check-input form-switch-custom-primary maquetacion-item" id="epub" name="epub"
|
||||
th:field="*{epub}">
|
||||
<label for="epub" class="form-check-label" th:text="#{presupuesto.maquetacion.epub}">ePub</label>
|
||||
</div>
|
||||
@ -70,8 +70,4 @@
|
||||
<button type="submit" class="btn btn-secondary mt-3">Calcular Presupuesto</button>
|
||||
|
||||
</form>
|
||||
<div th:if="${resultado != null}" class="mt-4">
|
||||
<h3>Resultado del Presupuesto</h3>
|
||||
<pre th:text="${resultado}"></pre>
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,51 @@
|
||||
<div th:fragment="marcapaginasForm">
|
||||
<form id="marcapaginasForm" novalidate th:action="@{/presupuesto/public/marcapaginas}" th:object="${presupuestoMarcapaginas}" method="get">
|
||||
<div class="form-group mb-3">
|
||||
<label th:text="#{presupuesto.tirada}" for="marcapaginas-tirada">Tirada</label>
|
||||
<input type="number" class="form-control marcapaginas-item" id="marcapaginas-tirada" th:field="*{unidades}" min="100" required>
|
||||
<div class="invalid-feedback"></div>
|
||||
</div>
|
||||
<div class="form-group mb-3">
|
||||
<label th:text="#{presupuesto.marcapaginas.tamanio}" for="tamanio-marcapaginas">Tamaño</label>
|
||||
<select class="form-control marcapaginas-item" id="tamanio-marcapaginas" th:field="*{tamanio}" required>
|
||||
<option value="_50x140_" selected>50x140 mm</option>
|
||||
<option value="_50x170_">50x170 mm</option>
|
||||
<option value="_50x210_">50x210 mm</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group mb-3">
|
||||
<label th:text="#{presupuesto.marcapaginas.caras-impresion}" for="caras-impresion">Caras impresión</label>
|
||||
<select class="form-control marcapaginas-item" id="caras-impresion" th:field="*{carasImpresion}" required>
|
||||
<option th:text="#{presupuesto.marcapaginas.caras-impresion-1}" value="una_cara">Una cara</option>
|
||||
<option th:text="#{presupuesto.marcapaginas.caras-impresion-2}" value="dos_caras" selected>Dos caras</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group mb-3">
|
||||
<label th:text="#{presupuesto.marcapaginas.papel}" for="papel-marcapaginas">Papel marcapáginas</label>
|
||||
<select class="form-control marcapaginas-item" id="papel-marcapaginas" th:field="*{papel}" required>
|
||||
<option th:text="#{presupuesto.marcapaginas.papel.cartulina-grafica}" value="cartulina_grafica">Cartulina gráfica</option>
|
||||
<option th:text="#{presupuesto.marcapaginas.papel.estucado-mate}" value="estucado_mate" selected>Estucado mate</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group mb-3">
|
||||
<label th:text="#{presupuesto.marcapaginas.gramaje}" for="gramaje-marcapaginas">Gramaje marcapáginas</label>
|
||||
<select class="form-control marcapaginas-item" id="gramaje-marcapaginas" th:field="*{gramaje}" required>
|
||||
<option value="300" selected>300</option>
|
||||
<option value="350">350</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group mb-3">
|
||||
<label th:text="#{presupuesto.marcapaginas.acabado}" for="acabado-marcapaginas">Acabado marcapáginas</label>
|
||||
<select class="form-control marcapaginas-item" id="acabado-marcapaginas" th:field="*{acabado}" required>
|
||||
<option class="marcapaginas-1cara marcapaginas-2caras" value="ninguno" th:text="#{presupuesto.marcapaginas.acabado.ninguno}" selected>Sin acabado</option>
|
||||
<option class="marcapaginas-1cara" value="plastificado_brillo_1c" th:text="#{presupuesto.marcapaginas.acabado.plastificado-brillo-1c}">Plastificado brillo</option>
|
||||
<option class="marcapaginas-1cara" value="plastificado_mate_1c" th:text="#{presupuesto.marcapaginas.acabado.plastificado-mate-1c}">Plastificado mate</option>
|
||||
<option class="marcapaginas-2caras" value="plastificado_brillo_2c" th:text="#{presupuesto.marcapaginas.acabado.plastificado-brillo-2c}">Plastificado brillo</option>
|
||||
<option class="marcapaginas-2caras" value="plastificado_mate_2c" th:text="#{presupuesto.marcapaginas.acabado.plastificado-mate-2c}">Plastificado mate</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-secondary mt-3">Calcular Presupuesto</button>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
@ -0,0 +1,66 @@
|
||||
package com.imprimelibros.erp;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.util.Map;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import com.imprimelibros.erp.presupuesto.PresupuestoService;
|
||||
import com.imprimelibros.erp.presupuesto.classes.PresupuestoMarcapaginas;
|
||||
import com.imprimelibros.erp.presupuesto.marcapaginas.Marcapaginas;
|
||||
|
||||
import com.imprimelibros.erp.presupuesto.marcapaginas.MarcapaginasRepository;
|
||||
|
||||
@SpringBootTest
|
||||
class presupuestoMarcapaginasTest {
|
||||
|
||||
@Autowired
|
||||
protected PresupuestoService presupuestoService;
|
||||
|
||||
@Autowired
|
||||
protected MarcapaginasRepository marcapaginasRepository;
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(presupuestoMarcapaginasTest.class);
|
||||
|
||||
@Test
|
||||
void testCalculoMaquetacion() {
|
||||
Map<String, Object> resultado = this.test();
|
||||
|
||||
System.out.println("Resultado:");
|
||||
System.out.println(resultado);
|
||||
log.info("📦 Resultado: {}", resultado);
|
||||
|
||||
assertNotNull(resultado, "El resultado no debe ser null");
|
||||
assertFalse(resultado.containsKey("error"), "El resultado no debe contener error");
|
||||
// assertFalse((Double)resultado.get("precio") == 0.0, "El precio debe ser mayor
|
||||
// que 0");
|
||||
}
|
||||
|
||||
public HashMap<String, Object> test() {
|
||||
|
||||
PresupuestoMarcapaginas presupuestoMarcapaginas = new PresupuestoMarcapaginas();
|
||||
presupuestoMarcapaginas.setUnidades(1000);
|
||||
presupuestoMarcapaginas.setTamanio(Marcapaginas.Tamanios._50x140_);
|
||||
presupuestoMarcapaginas.setAcabado(Marcapaginas.Acabado.ninguno);
|
||||
presupuestoMarcapaginas.setCarasImpresion(Marcapaginas.Caras_Impresion.una_cara);
|
||||
presupuestoMarcapaginas.setPapel(Marcapaginas.Papeles.cartulina_grafica);
|
||||
presupuestoMarcapaginas.setGramaje(300);
|
||||
|
||||
Locale locale = Locale.getDefault();
|
||||
|
||||
return presupuestoService.getPrecioMarcapaginas(presupuestoMarcapaginas, locale);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user