mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-24 09:40:21 +00:00
Merge branch 'feat/presupuesto_maquetacion' into 'main'
Feat/presupuesto maquetacion See merge request jjimenez/erp-imprimelibros!3
This commit is contained in:
@ -22,15 +22,20 @@ public class InternationalizationConfig implements WebMvcConfigurer {
|
|||||||
@Bean
|
@Bean
|
||||||
public LocaleResolver localeResolver() {
|
public LocaleResolver localeResolver() {
|
||||||
SessionLocaleResolver slr = new SessionLocaleResolver();
|
SessionLocaleResolver slr = new SessionLocaleResolver();
|
||||||
slr.setDefaultLocale(Locale.of("es"));
|
slr.setDefaultLocale(Locale.forLanguageTag("es")); // idioma por defecto
|
||||||
return slr;
|
return slr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public LocaleChangeInterceptor localeChangeInterceptor() {
|
||||||
|
LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
|
||||||
|
lci.setParamName("lang"); // parámetro ?lang=en, ?lang=es
|
||||||
|
return lci;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addInterceptors(InterceptorRegistry registry) {
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
|
registry.addInterceptor(localeChangeInterceptor());
|
||||||
interceptor.setParamName("lang");
|
|
||||||
registry.addInterceptor(interceptor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ -40,22 +45,22 @@ public class InternationalizationConfig implements WebMvcConfigurer {
|
|||||||
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
|
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
|
||||||
Resource[] resources = resolver.getResources("classpath*:i18n/*.properties");
|
Resource[] resources = resolver.getResources("classpath*:i18n/*.properties");
|
||||||
|
|
||||||
// Extraer los nombres base sin extensión ni sufijos (_en, _es, etc.)
|
// Extraer nombres base sin sufijos de idioma
|
||||||
Set<String> basenames = Arrays.stream(resources)
|
Set<String> basenames = Arrays.stream(resources)
|
||||||
.map(res -> {
|
.map(res -> {
|
||||||
try {
|
try {
|
||||||
String uri = Objects.requireNonNull(res.getURI()).toString();
|
String uri = Objects.requireNonNull(res.getURI()).toString();
|
||||||
// Ej: file:/.../i18n/login_en.properties
|
// Ejemplo: file:/.../i18n/login_en.properties
|
||||||
String path = uri.substring(uri.indexOf("/i18n/") + 1); // i18n/login_en.properties
|
String path = uri.substring(uri.indexOf("/i18n/") + 1); // i18n/login_en.properties
|
||||||
String base = path.replaceAll("_[a-z]{2}\\.properties$", "") // login.properties
|
String base = path.replaceAll("_[a-z]{2}\\.properties$", "") // login.properties
|
||||||
.replaceAll("\\.properties$", "");
|
.replaceAll("\\.properties$", "");
|
||||||
return "classpath:" + base;
|
return "classpath:" + base;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
source.setBasenames(basenames.toArray(new String[0]));
|
source.setBasenames(basenames.toArray(new String[0]));
|
||||||
source.setDefaultEncoding("UTF-8");
|
source.setDefaultEncoding("UTF-8");
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import org.springframework.context.annotation.Bean;
|
|||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
|
|||||||
@ -171,6 +171,12 @@ public class Presupuesto implements Cloneable{
|
|||||||
@Column(name = "alto_faja")
|
@Column(name = "alto_faja")
|
||||||
private Integer altoFaja = 0;
|
private Integer altoFaja = 0;
|
||||||
|
|
||||||
|
@Column(name = "presupuesto_maquetacion")
|
||||||
|
private Boolean presupuestoMaquetacion = false;
|
||||||
|
@Column(name = "presupuesto_maquetacion_data")
|
||||||
|
private String presupuestoMaquetacionData;
|
||||||
|
|
||||||
|
|
||||||
// Getters y Setters
|
// Getters y Setters
|
||||||
public String getAutor() {
|
public String getAutor() {
|
||||||
return autor;
|
return autor;
|
||||||
@ -512,5 +518,16 @@ public class Presupuesto implements Cloneable{
|
|||||||
this.selectedTirada = selectedTirada;
|
this.selectedTirada = selectedTirada;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean getPresupuestoMaquetacion() {
|
||||||
|
return presupuestoMaquetacion;
|
||||||
|
}
|
||||||
|
public void setPresupuestoMaquetacion(Boolean presupuestoMaquetacion) {
|
||||||
|
this.presupuestoMaquetacion = presupuestoMaquetacion;
|
||||||
|
}
|
||||||
|
public String getPresupuestoMaquetacionData() {
|
||||||
|
return presupuestoMaquetacionData;
|
||||||
|
}
|
||||||
|
public void setPresupuestoMaquetacionData(String presupuestoMaquetacionData) {
|
||||||
|
this.presupuestoMaquetacionData = presupuestoMaquetacionData;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package com.imprimelibros.erp.presupuesto;
|
package com.imprimelibros.erp.presupuesto;
|
||||||
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@ -14,16 +15,22 @@ import org.springframework.validation.BindingResult;
|
|||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
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.PostMapping;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.imprimelibros.erp.externalApi.skApiClient;
|
import com.imprimelibros.erp.externalApi.skApiClient;
|
||||||
import com.imprimelibros.erp.presupuesto.classes.ImagenPresupuesto;
|
import com.imprimelibros.erp.presupuesto.classes.ImagenPresupuesto;
|
||||||
|
import com.imprimelibros.erp.presupuesto.classes.PresupuestoMaquetacion;
|
||||||
import com.imprimelibros.erp.presupuesto.validation.PresupuestoValidationGroups;
|
import com.imprimelibros.erp.presupuesto.validation.PresupuestoValidationGroups;
|
||||||
|
|
||||||
@RestController
|
import jakarta.validation.Valid;
|
||||||
|
|
||||||
|
@Controller
|
||||||
@RequestMapping("/presupuesto")
|
@RequestMapping("/presupuesto")
|
||||||
public class PresupuestoController {
|
public class PresupuestoController {
|
||||||
|
|
||||||
@ -89,8 +96,9 @@ public class PresupuestoController {
|
|||||||
@PostMapping("/public/validar/cubierta")
|
@PostMapping("/public/validar/cubierta")
|
||||||
public ResponseEntity<?> validarCubierta(
|
public ResponseEntity<?> validarCubierta(
|
||||||
@Validated(PresupuestoValidationGroups.Cubierta.class) Presupuesto presupuesto,
|
@Validated(PresupuestoValidationGroups.Cubierta.class) Presupuesto presupuesto,
|
||||||
|
BindingResult result,
|
||||||
@RequestParam(name = "calcular", defaultValue = "true") boolean calcular,
|
@RequestParam(name = "calcular", defaultValue = "true") boolean calcular,
|
||||||
BindingResult result, Locale locale) {
|
Locale locale) {
|
||||||
|
|
||||||
Map<String, String> errores = new HashMap<>();
|
Map<String, String> errores = new HashMap<>();
|
||||||
|
|
||||||
@ -145,6 +153,9 @@ public class PresupuestoController {
|
|||||||
Map<String, Object> resultado = new HashMap<>();
|
Map<String, Object> resultado = new HashMap<>();
|
||||||
// servicios extra
|
// servicios extra
|
||||||
resultado.putAll(presupuestoService.obtenerServiciosExtras(presupuesto, locale, apiClient));
|
resultado.putAll(presupuestoService.obtenerServiciosExtras(presupuesto, locale, apiClient));
|
||||||
|
Map<String, String> language = new HashMap<>();
|
||||||
|
language.put("calcular", messageSource.getMessage("presupuesto.calcular", null, locale));
|
||||||
|
resultado.put("language", language);
|
||||||
|
|
||||||
return ResponseEntity.ok(resultado);
|
return ResponseEntity.ok(resultado);
|
||||||
}
|
}
|
||||||
@ -301,4 +312,36 @@ public class PresupuestoController {
|
|||||||
return ResponseEntity.ok(price);
|
return ResponseEntity.ok(price);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = "/public/maquetacion/form", produces = MediaType.TEXT_HTML_VALUE)
|
||||||
|
public String getMaquetacionForm(Model model) {
|
||||||
|
model.addAttribute("presupuestoMaquetacion", new PresupuestoMaquetacion());
|
||||||
|
return "imprimelibros/presupuestos/presupuesto-maquetacion-form :: maquetacionForm";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/public/maquetacion")
|
||||||
|
public ResponseEntity<?> getPresupuestoMaquetacion(
|
||||||
|
@Valid @ModelAttribute PresupuestoMaquetacion presupuestoMaquetacion,
|
||||||
|
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.getPrecioMaquetacion(presupuestoMaquetacion, locale);
|
||||||
|
if ((Double) resultado.get("precio") == 0.0 && (Integer) resultado.get("numPaginasEstimadas") == 0
|
||||||
|
&& (Double) resultado.get("precioPaginaEstimado") == 0.0) {
|
||||||
|
return ResponseEntity.badRequest()
|
||||||
|
.body(messageSource.getMessage("presupuesto.errores.presupuesto-maquetacion", null, locale));
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(resultado);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,16 +12,24 @@ import java.text.NumberFormat;
|
|||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.MessageSource;
|
import org.springframework.context.MessageSource;
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
|
||||||
import com.imprimelibros.erp.configurationERP.VariableService;
|
import com.imprimelibros.erp.configurationERP.VariableService;
|
||||||
import com.imprimelibros.erp.presupuesto.Presupuesto.TipoCubierta;
|
import com.imprimelibros.erp.presupuesto.Presupuesto.TipoCubierta;
|
||||||
import com.imprimelibros.erp.presupuesto.classes.ImagenPresupuesto;
|
import com.imprimelibros.erp.presupuesto.classes.ImagenPresupuesto;
|
||||||
import com.imprimelibros.erp.presupuesto.classes.PresupuestadorItems;
|
import com.imprimelibros.erp.presupuesto.classes.PresupuestadorItems;
|
||||||
|
import com.imprimelibros.erp.presupuesto.maquetacion.MaquetacionPrecios;
|
||||||
|
import com.imprimelibros.erp.presupuesto.maquetacion.MaquetacionPreciosRepository;
|
||||||
|
import com.imprimelibros.erp.presupuesto.classes.PresupuestoMaquetacion;
|
||||||
|
import com.imprimelibros.erp.presupuesto.maquetacion.MaquetacionMatrices;
|
||||||
|
import com.imprimelibros.erp.presupuesto.maquetacion.MaquetacionMatricesRepository;
|
||||||
import com.imprimelibros.erp.externalApi.skApiClient;
|
import com.imprimelibros.erp.externalApi.skApiClient;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@ -36,6 +44,12 @@ public class PresupuestoService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
protected skApiClient skApiClient;
|
protected skApiClient skApiClient;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
protected MaquetacionPreciosRepository maquetacionPreciosRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
protected MaquetacionMatricesRepository maquetacionMatricesRepository;
|
||||||
|
|
||||||
private final PresupuestadorItems presupuestadorItems;
|
private final PresupuestadorItems presupuestadorItems;
|
||||||
|
|
||||||
public PresupuestoService(PresupuestadorItems presupuestadorItems) {
|
public PresupuestoService(PresupuestadorItems presupuestadorItems) {
|
||||||
@ -516,7 +530,7 @@ public class PresupuestoService {
|
|||||||
put("id", "ejemplar-prueba");
|
put("id", "ejemplar-prueba");
|
||||||
put("title", messageSource.getMessage("presupuesto.extras-ejemplar-prueba", null, locale));
|
put("title", messageSource.getMessage("presupuesto.extras-ejemplar-prueba", null, locale));
|
||||||
put("description", "");
|
put("description", "");
|
||||||
if(price_prototipo == 0.0) {
|
if (price_prototipo == 0.0) {
|
||||||
put("price", messageSource.getMessage("presupuesto.consultar-soporte", null, locale));
|
put("price", messageSource.getMessage("presupuesto.consultar-soporte", null, locale));
|
||||||
put("priceUnit", "");
|
put("priceUnit", "");
|
||||||
} else {
|
} else {
|
||||||
@ -578,4 +592,87 @@ public class PresupuestoService {
|
|||||||
}
|
}
|
||||||
return price_prototipo;
|
return price_prototipo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HashMap<String, Object> getPrecioMaquetacion(PresupuestoMaquetacion presupuestoMaquetacion, Locale locale) {
|
||||||
|
try {
|
||||||
|
List<MaquetacionPrecios> lista = maquetacionPreciosRepository.findAll();
|
||||||
|
|
||||||
|
// helper para obtener un precio por clave
|
||||||
|
java.util.function.Function<String, Double> price = key -> lista.stream()
|
||||||
|
.filter(p -> key.equals(p.getKey()))
|
||||||
|
.map(MaquetacionPrecios::getValue)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(0.0);
|
||||||
|
|
||||||
|
BigDecimal precio = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
// millar_maquetacion * (numCaracteres / 1000.0)
|
||||||
|
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"))));
|
||||||
|
|
||||||
|
// Numero de paginas estimado
|
||||||
|
int numPaginas = 0;
|
||||||
|
Integer matricesPorPagina = maquetacionMatricesRepository.findMatrices(
|
||||||
|
presupuestoMaquetacion.getFormato(),
|
||||||
|
presupuestoMaquetacion.getCuerpoTexto());
|
||||||
|
if (matricesPorPagina != null && matricesPorPagina > 0) {
|
||||||
|
numPaginas = presupuestoMaquetacion.getNumCaracteres() / matricesPorPagina;
|
||||||
|
}
|
||||||
|
// Precio por pagina estimado
|
||||||
|
BigDecimal precioRedondeado = precio.setScale(2, RoundingMode.HALF_UP);
|
||||||
|
double precioPaginaEstimado = 0.0;
|
||||||
|
if (numPaginas > 0) {
|
||||||
|
precioPaginaEstimado = precioRedondeado
|
||||||
|
.divide(BigDecimal.valueOf(numPaginas), 2, RoundingMode.HALF_UP)
|
||||||
|
.doubleValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
// tabla, columna, foto
|
||||||
|
precio = precio
|
||||||
|
.add(BigDecimal.valueOf(presupuestoMaquetacion.getNumTablas()).multiply(BigDecimal.valueOf(price.apply("tabla"))));
|
||||||
|
precio = precio.add(
|
||||||
|
BigDecimal.valueOf(presupuestoMaquetacion.getNumColumnas()).multiply(BigDecimal.valueOf(price.apply("columnas"))));
|
||||||
|
precio = precio
|
||||||
|
.add(BigDecimal.valueOf(presupuestoMaquetacion.getNumFotos()).multiply(BigDecimal.valueOf(price.apply("foto"))));
|
||||||
|
|
||||||
|
if (presupuestoMaquetacion.isCorreccionOrtotipografica()) {
|
||||||
|
precio = precio
|
||||||
|
.add(millares.multiply(BigDecimal.valueOf(price.apply("correccion_ortotipografica"))));
|
||||||
|
}
|
||||||
|
if (presupuestoMaquetacion.isTextoMecanografiado()) {
|
||||||
|
precio = precio.add(millares.multiply(BigDecimal.valueOf(price.apply("mecanoescritura_por_millar"))));
|
||||||
|
}
|
||||||
|
if (presupuestoMaquetacion.isDisenioPortada()) {
|
||||||
|
precio = precio.add(BigDecimal.valueOf(price.apply("disenio_portada")));
|
||||||
|
}
|
||||||
|
if (presupuestoMaquetacion.isEpub()) {
|
||||||
|
precio = precio.add(BigDecimal.valueOf(price.apply("epub")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// redondeo final
|
||||||
|
precioRedondeado = precio.setScale(2, RoundingMode.HALF_UP);
|
||||||
|
|
||||||
|
HashMap<String, Object> out = new HashMap<>();
|
||||||
|
out.put("precio", precioRedondeado.doubleValue());
|
||||||
|
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("cancel", messageSource.getMessage("app.cancelar", null, locale));
|
||||||
|
language.put("presupuesto_maquetacion", messageSource.getMessage("presupuesto.maquetacion", null, locale));
|
||||||
|
out.put("language", language);
|
||||||
|
return out;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("Error procesando presupuesto maquetacion: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
HashMap<String, Object> out = new HashMap<>();
|
||||||
|
out.put("precio", 0.0);
|
||||||
|
out.put("numPaginasEstimadas", 0);
|
||||||
|
out.put("precioPaginaEstimado", 0.0);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,92 @@
|
|||||||
|
package com.imprimelibros.erp.presupuesto.classes;
|
||||||
|
|
||||||
|
import com.imprimelibros.erp.presupuesto.maquetacion.MaquetacionMatrices.FontSize;
|
||||||
|
import com.imprimelibros.erp.presupuesto.maquetacion.MaquetacionMatrices.Formato;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.Min;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
public class PresupuestoMaquetacion {
|
||||||
|
|
||||||
|
@NotNull(message = "{validation.required}")
|
||||||
|
@Min(value = 1, message = "{validation.min}")
|
||||||
|
private Integer numCaracteres = 200000;
|
||||||
|
private Formato formato = Formato.A5;
|
||||||
|
private FontSize cuerpoTexto = FontSize.medium;
|
||||||
|
@Min(value = 0, message = "{validation.min}")
|
||||||
|
@NotNull(message = "{validation.required}")
|
||||||
|
private Integer numTablas = 0;
|
||||||
|
@Min(value = 1, message = "{validation.min}")
|
||||||
|
@NotNull(message = "{validation.required}")
|
||||||
|
private Integer numColumnas = 1;
|
||||||
|
@Min(value = 0, message = "{validation.min}")
|
||||||
|
@NotNull(message = "{validation.required}")
|
||||||
|
private Integer numFotos = 0;
|
||||||
|
private boolean correccionOrtotipografica = false;
|
||||||
|
private boolean textoMecanografiado = false;
|
||||||
|
private boolean disenioPortada = false;
|
||||||
|
private boolean epub = false;
|
||||||
|
|
||||||
|
public Integer getNumCaracteres() {
|
||||||
|
return numCaracteres;
|
||||||
|
}
|
||||||
|
public void setNumCaracteres(Integer numCaracteres) {
|
||||||
|
this.numCaracteres = numCaracteres;
|
||||||
|
}
|
||||||
|
public Formato getFormato() {
|
||||||
|
return formato;
|
||||||
|
}
|
||||||
|
public void setFormato(Formato formato) {
|
||||||
|
this.formato = formato;
|
||||||
|
}
|
||||||
|
public FontSize getCuerpoTexto() {
|
||||||
|
return cuerpoTexto;
|
||||||
|
}
|
||||||
|
public void setCuerpoTexto(FontSize cuerpoTexto) {
|
||||||
|
this.cuerpoTexto = cuerpoTexto;
|
||||||
|
}
|
||||||
|
public Integer getNumTablas() {
|
||||||
|
return numTablas;
|
||||||
|
}
|
||||||
|
public void setNumTablas(Integer numTablas) {
|
||||||
|
this.numTablas = numTablas;
|
||||||
|
}
|
||||||
|
public Integer getNumColumnas() {
|
||||||
|
return numColumnas;
|
||||||
|
}
|
||||||
|
public void setNumColumnas(Integer numColumnas) {
|
||||||
|
this.numColumnas = numColumnas;
|
||||||
|
}
|
||||||
|
public Integer getNumFotos() {
|
||||||
|
return numFotos;
|
||||||
|
}
|
||||||
|
public void setNumFotos(Integer numFotos) {
|
||||||
|
this.numFotos = numFotos;
|
||||||
|
}
|
||||||
|
public boolean isCorreccionOrtotipografica() {
|
||||||
|
return correccionOrtotipografica;
|
||||||
|
}
|
||||||
|
public void setCorreccionOrtotipografica(boolean correccionOrtotipografica) {
|
||||||
|
this.correccionOrtotipografica = correccionOrtotipografica;
|
||||||
|
}
|
||||||
|
public boolean isTextoMecanografiado() {
|
||||||
|
return textoMecanografiado;
|
||||||
|
}
|
||||||
|
public void setTextoMecanografiado(boolean textoMecanografiado) {
|
||||||
|
this.textoMecanografiado = textoMecanografiado;
|
||||||
|
}
|
||||||
|
public boolean isDisenioPortada() {
|
||||||
|
return disenioPortada;
|
||||||
|
}
|
||||||
|
public void setDisenioPortada(boolean disenioPortada) {
|
||||||
|
this.disenioPortada = disenioPortada;
|
||||||
|
}
|
||||||
|
public boolean isEpub() {
|
||||||
|
return epub;
|
||||||
|
}
|
||||||
|
public void setEpub(boolean epub) {
|
||||||
|
this.epub = epub;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
package com.imprimelibros.erp.presupuesto.maquetacion;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "maquetacion_matrices_formato")
|
||||||
|
public class MaquetacionMatrices {
|
||||||
|
|
||||||
|
public enum Formato{
|
||||||
|
A5, _17x24_, A4
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum FontSize{
|
||||||
|
small, medium, big
|
||||||
|
}
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
private Formato formato;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
private FontSize tamanio_letra;
|
||||||
|
|
||||||
|
private int matrices_pagina;
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Formato getFormato() {
|
||||||
|
return formato;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FontSize getTamanioLetra() {
|
||||||
|
return tamanio_letra;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMatricesPagina() {
|
||||||
|
return matrices_pagina;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
package com.imprimelibros.erp.presupuesto.maquetacion;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface MaquetacionMatricesRepository extends JpaRepository<MaquetacionMatrices, Long> {
|
||||||
|
|
||||||
|
@Query("SELECT m.matrices_pagina FROM MaquetacionMatrices m WHERE m.formato = :formato AND m.tamanio_letra = :tamanioLetra")
|
||||||
|
Integer findMatrices(@Param("formato") MaquetacionMatrices.Formato formato,
|
||||||
|
@Param("tamanioLetra") MaquetacionMatrices.FontSize tamanioLetra);
|
||||||
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
package com.imprimelibros.erp.presupuesto.maquetacion;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "maquetacion_precios")
|
||||||
|
public class MaquetacionPrecios {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "keyword")
|
||||||
|
private String key;
|
||||||
|
|
||||||
|
@Column(name = "value")
|
||||||
|
private Double value;
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
package com.imprimelibros.erp.presupuesto.maquetacion;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface MaquetacionPreciosRepository extends JpaRepository<MaquetacionPrecios, Long> {
|
||||||
|
|
||||||
|
MaquetacionPrecios findByKey(String key);
|
||||||
|
}
|
||||||
@ -4,13 +4,14 @@ logging.level.org.springframework.security=DEBUG
|
|||||||
logging.level.root=WARN
|
logging.level.root=WARN
|
||||||
logging.level.org.springframework=ERROR
|
logging.level.org.springframework=ERROR
|
||||||
|
|
||||||
spring.datasource.url=jdbc:mysql://127.0.0.1:3309/imprimelibros?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Europe/Madrid&characterEncoding=utf8
|
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.username=imprimelibros_user
|
||||||
spring.datasource.password=om91irrDctd
|
spring.datasource.password=om91irrDctd
|
||||||
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||||
|
|
||||||
#safekat.api.url=http://localhost:8000/
|
safekat.api.url=http://localhost:8000/
|
||||||
safekat.api.url=https://erp-dev.safekat.es/
|
#safekat.api.url=https://erp-dev.safekat.es/
|
||||||
safekat.api.email=imnavajas@coit.es
|
safekat.api.email=imnavajas@coit.es
|
||||||
safekat.api.password=Safekat2024
|
safekat.api.password=Safekat2024
|
||||||
|
|
||||||
|
|||||||
@ -1 +1,9 @@
|
|||||||
app.currency-symbol=€
|
app.currency-symbol=€
|
||||||
|
app.yes=Yes
|
||||||
|
app.no=No
|
||||||
|
app.aceptar=Accept
|
||||||
|
app.cancelar=Cancel
|
||||||
|
app.guardar=Save
|
||||||
|
app.editar=Edit
|
||||||
|
app.eliminar=Delete
|
||||||
|
app.imprimir=Print
|
||||||
@ -1,3 +1,9 @@
|
|||||||
app.currency-symbol=€
|
app.currency-symbol=€
|
||||||
app.yes=Sí
|
app.yes=Sí
|
||||||
app.no=No
|
app.no=No
|
||||||
|
app.aceptar=Aceptar
|
||||||
|
app.cancelar=Cancelar
|
||||||
|
app.guardar=Guardar
|
||||||
|
app.editar=Editar
|
||||||
|
app.eliminar=Eliminar
|
||||||
|
app.imprimir=Imprimir
|
||||||
@ -3,6 +3,7 @@ presupuesto.interior=Interior
|
|||||||
presupuesto.cubierta=Cubierta
|
presupuesto.cubierta=Cubierta
|
||||||
presupuesto.seleccion-tirada=Seleccion de tirada
|
presupuesto.seleccion-tirada=Seleccion de tirada
|
||||||
presupuesto.extras=Extras
|
presupuesto.extras=Extras
|
||||||
|
presupuesto.add-to-presupuesto=Añadir al presupuesto
|
||||||
|
|
||||||
# Pestaña datos generales de presupuesto
|
# Pestaña datos generales de presupuesto
|
||||||
presupuesto.informacion-libro=Información del libro
|
presupuesto.informacion-libro=Información del libro
|
||||||
@ -148,6 +149,7 @@ presupuesto.volver-cubierta=Volver a diseño cubierta
|
|||||||
presupuesto.finalizar=Finalizar presupuesto
|
presupuesto.finalizar=Finalizar presupuesto
|
||||||
presupuesto.calcular-presupuesto=Calcular presupuesto
|
presupuesto.calcular-presupuesto=Calcular presupuesto
|
||||||
presupuesto.consultar-soporte=Consultar con soporte
|
presupuesto.consultar-soporte=Consultar con soporte
|
||||||
|
presupuesto.calcular=Calcular
|
||||||
|
|
||||||
# Resumen del presupuesto
|
# Resumen del presupuesto
|
||||||
presupuesto.resumen-presupuesto=Resumen presupuesto
|
presupuesto.resumen-presupuesto=Resumen presupuesto
|
||||||
@ -160,6 +162,26 @@ presupuesto.paginas=Páginas
|
|||||||
presupuesto.solapas=Solapas
|
presupuesto.solapas=Solapas
|
||||||
presupuesto.papel-gramaje=Papel y gramaje
|
presupuesto.papel-gramaje=Papel y gramaje
|
||||||
|
|
||||||
|
# Presupuesto de maquetación
|
||||||
|
presupuesto.maquetacion=Presupuesto de maquetación
|
||||||
|
presupuesto.maquetacion.num-caracteres=Número de caracteres
|
||||||
|
presupuesto.maquetacion.num-caracteres-descripcion=Caracteres con espacios (obtenidos desde Word)
|
||||||
|
presupuesto.maquetacion.formato=Formato
|
||||||
|
presupuesto.maquetacion.formato-descripcion=Seleccione el tamaño que más se aproxime
|
||||||
|
presupuesto.maquetacion.cuerpo-texto=Cuerpo de texto
|
||||||
|
presupuesto.maquetacion.cuerpo-texto-pequeño=Pequeño
|
||||||
|
presupuesto.maquetacion.cuerpo-texto-medio=Medio (12pt)
|
||||||
|
presupuesto.maquetacion.cuerpo-texto-grande=Grande
|
||||||
|
presupuesto.maquetacion.cuerpo-texto-descripcion=Tamaño de letra usado (medio=12pt)
|
||||||
|
presupuesto.maquetacion.num-columnas=Número de columnas
|
||||||
|
presupuesto.maquetacion.num-columnas-descripcion=Número de columnas en las que se quiere maquetar el libro
|
||||||
|
presupuesto.maquetacion.num-fotos=Número total imágenes/figuras
|
||||||
|
presupuesto.maquetacion.num-tablas=Número de tablas
|
||||||
|
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
|
||||||
|
|
||||||
# Errores
|
# Errores
|
||||||
presupuesto.errores-title=Corrija los siguientes errores:
|
presupuesto.errores-title=Corrija los siguientes errores:
|
||||||
presupuesto.errores.titulo=El título es obligatorio
|
presupuesto.errores.titulo=El título es obligatorio
|
||||||
@ -181,4 +203,6 @@ presupuesto.errores.tipo-cubierta=Seleccione el tipo de cubierta
|
|||||||
presupuesto.errores.solapas-cubierta=Seleccione si desea o no solapas en la cubierta
|
presupuesto.errores.solapas-cubierta=Seleccione si desea o no solapas en la cubierta
|
||||||
presupuesto.errores.papel-cubierta=Seleccione el tipo de papel para la cubierta
|
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.gramaje-cubierta=Seleccione el gramaje del papel para la cubierta
|
||||||
presupuesto.errores.acabado-cubierta=Seleccione el acabado de 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.
|
||||||
6
src/main/resources/i18n/validation_en.properties
Normal file
6
src/main/resources/i18n/validation_en.properties
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
validation.required=Required field
|
||||||
|
validation.number=The field must be a valid number
|
||||||
|
validation.min=The minimum value is {value}
|
||||||
|
validation.max=The maximum value is {value}
|
||||||
|
validation.typeMismatchMsg=Invalid data type
|
||||||
|
validation.patternMsg=Invalid format
|
||||||
8
src/main/resources/i18n/validation_es.properties
Normal file
8
src/main/resources/i18n/validation_es.properties
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
validation.required=El campo es obligatorio
|
||||||
|
validation.number=El campo debe ser un número válido
|
||||||
|
validation.min=El valor mínimo es {value}
|
||||||
|
validation.max=El valor máximo es {value}
|
||||||
|
validation.typeMismatchMsg=Tipo de dato no válido
|
||||||
|
validation.patternMsg=El formato no es válido
|
||||||
|
|
||||||
|
|
||||||
@ -1,37 +1,54 @@
|
|||||||
(function () {
|
(function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const default_lang = "es";
|
const DEFAULT_LANG = "es";
|
||||||
const language = localStorage.getItem("language");
|
|
||||||
|
|
||||||
function initLanguage() {
|
function getCurrentLang() {
|
||||||
const saved = localStorage.getItem("language") || default_lang;
|
// Viene del servidor (Thymeleaf): <html th:lang="${#locale.language}">
|
||||||
setLanguage(saved, false); // solo actualiza bandera y lang
|
return document.documentElement.lang || DEFAULT_LANG;
|
||||||
document.querySelectorAll('.language').forEach(a => {
|
}
|
||||||
a.addEventListener('click', () => setLanguage(a.dataset.lang, true));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function setLanguage(lang, redirect = true) {
|
function setFlag(lang) {
|
||||||
const already = document.documentElement.lang === lang;
|
const img = document.getElementById("header-lang-img");
|
||||||
|
if (!img) return;
|
||||||
|
img.src = (lang === "en")
|
||||||
|
? "/assets/images/flags/gb.svg"
|
||||||
|
: "/assets/images/flags/spain.svg";
|
||||||
|
}
|
||||||
|
|
||||||
// Actualiza <html lang> y bandera
|
function onLanguageClick(e) {
|
||||||
document.documentElement.lang = lang;
|
e.preventDefault();
|
||||||
document.getElementById("header-lang-img").src =
|
const lang = this.dataset.lang;
|
||||||
lang === "en" ? "/assets/images/flags/gb.svg"
|
if (!lang || lang === getCurrentLang()) return;
|
||||||
: "/assets/images/flags/spain.svg";
|
|
||||||
localStorage.setItem("language", lang);
|
|
||||||
|
|
||||||
// Redirige si cambia el idioma
|
// Guarda la preferencia (opcional)
|
||||||
if (!already && redirect) {
|
try { localStorage.setItem("language", lang); } catch {}
|
||||||
const url = new URL(location.href);
|
|
||||||
url.searchParams.set("lang", lang);
|
|
||||||
location.href = url.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Llama al inicializador de idioma en cuanto el DOM esté listo
|
// Redirige con ?lang=... para que Spring cambie el Locale y renderice en ese idioma
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
const url = new URL(location.href);
|
||||||
initLanguage();
|
url.searchParams.set("lang", lang);
|
||||||
});
|
location.assign(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
function initLanguage() {
|
||||||
|
// Usa el idioma actual que viene del servidor
|
||||||
|
const current = getCurrentLang();
|
||||||
|
setFlag(current);
|
||||||
|
|
||||||
|
// Enlaces/ botones de idioma: .language[data-lang="en|es"]
|
||||||
|
document.querySelectorAll(".language").forEach(a => {
|
||||||
|
a.addEventListener("click", onLanguageClick);
|
||||||
|
});
|
||||||
|
|
||||||
|
// (Opcional) si guardaste algo en localStorage y quieres forzar
|
||||||
|
// la alineación al entrar por 1ª vez:
|
||||||
|
const saved = localStorage.getItem("language");
|
||||||
|
if (saved && saved !== current) {
|
||||||
|
const url = new URL(location.href);
|
||||||
|
url.searchParams.set("lang", saved);
|
||||||
|
location.replace(url); // alinea y no deja historial extra
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", initLanguage);
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -129,6 +129,7 @@ class PresupuestoCliente {
|
|||||||
this.summaryTableCubierta = $('#summary-cubierta');
|
this.summaryTableCubierta = $('#summary-cubierta');
|
||||||
this.summaryTableSobrecubierta = $('#summary-sobrecubierta');
|
this.summaryTableSobrecubierta = $('#summary-sobrecubierta');
|
||||||
this.summaryTableFaja = $('#summary-faja');
|
this.summaryTableFaja = $('#summary-faja');
|
||||||
|
this.summaryTableExtras = $('#summary-servicios-extras');
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
@ -1280,6 +1281,7 @@ class PresupuestoCliente {
|
|||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: data,
|
data: data,
|
||||||
success: (data) => {
|
success: (data) => {
|
||||||
|
this.divExtras.data('language-calcular', data.language.calcular);
|
||||||
this.#loadExtrasData(data.servicios_extra);
|
this.#loadExtrasData(data.servicios_extra);
|
||||||
this.#changeTab('pills-extras');
|
this.#changeTab('pills-extras');
|
||||||
},
|
},
|
||||||
@ -1350,15 +1352,22 @@ class PresupuestoCliente {
|
|||||||
|
|
||||||
if (id === 'btn-prev-extras') {
|
if (id === 'btn-prev-extras') {
|
||||||
this.#changeTab('pills-seleccion-tirada');
|
this.#changeTab('pills-seleccion-tirada');
|
||||||
|
this.summaryTableExtras.addClass('d-none');
|
||||||
} else {
|
} else {
|
||||||
//this.#changeTab('pills-finalizar');
|
//this.#changeTab('pills-finalizar');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Eventos para el resumen
|
||||||
|
$(document).on('change', '.service-checkbox', (e) => {
|
||||||
|
Summary.updateExtras();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#loadExtrasData(servicios) {
|
#loadExtrasData(servicios) {
|
||||||
|
|
||||||
this.divExtras.empty();
|
this.divExtras.empty();
|
||||||
|
this.summaryTableExtras.removeClass('d-none');
|
||||||
this.divExtras.removeClass('animate-fadeInUpBounce');
|
this.divExtras.removeClass('animate-fadeInUpBounce');
|
||||||
|
|
||||||
this.divExtras.addClass('animate-fadeInUpBounce');
|
this.divExtras.addClass('animate-fadeInUpBounce');
|
||||||
@ -1366,6 +1375,8 @@ class PresupuestoCliente {
|
|||||||
const item = new ServiceOptionCard(extra);
|
const item = new ServiceOptionCard(extra);
|
||||||
this.divExtras.append(item.render());
|
this.divExtras.append(item.render());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Summary.updateExtras();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,115 @@
|
|||||||
|
import * as Summary from "./summary.js";
|
||||||
|
|
||||||
|
$(document).on('change', '#maquetacion', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if ($('#maquetacion').is(':checked')) {
|
||||||
|
$.get("/presupuesto/public/maquetacion/form", function (data) {
|
||||||
|
$("#maquetacionModalBody").html(data);
|
||||||
|
$("#maquetacionModal").modal("show");
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const calcularStr = $('#div-extras').data('language-calcular');
|
||||||
|
$('#maquetacion').data('price', calcularStr);
|
||||||
|
$('label[for="maquetacion"] .service-price')
|
||||||
|
.text(calcularStr);
|
||||||
|
$('#maquetacion').prop('checked', false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on("submit", "#maquetacionForm", 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("maquetacionModal");
|
||||||
|
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>
|
||||||
|
`;
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title: json.language.presupuesto_maquetacion || 'Presupuesto Maquetación',
|
||||||
|
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) {
|
||||||
|
$('#maquetacion').prop('checked', true);
|
||||||
|
$('#maquetacion').data('price', json.precio);
|
||||||
|
$('label[for="maquetacion"] .service-price')
|
||||||
|
.text(formateaMoneda(json.precio));
|
||||||
|
Summary.updateExtras();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const calcularStr = $('#div-extras').data('language-calcular');
|
||||||
|
$('#maquetacion').prop('checked', false);
|
||||||
|
$('#maquetacion').data('price', calcularStr);
|
||||||
|
$('label[for="maquetacion"] .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 {
|
||||||
|
$("#maquetacionModalBody").html(
|
||||||
|
"<div class='alert alert-danger'>" + xhr.responseText + "</div>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$(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);
|
||||||
|
});
|
||||||
|
|
||||||
|
function formateaMoneda(valor) {
|
||||||
|
try {
|
||||||
|
return new Intl.NumberFormat('es-ES', { style: 'currency', currency: 'EUR' }).format(valor);
|
||||||
|
} catch {
|
||||||
|
return valor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ class ServiceOptionCard {
|
|||||||
: '';
|
: '';
|
||||||
const $card = $(`
|
const $card = $(`
|
||||||
<div class="col-lg-2 col-md-3 col-sm-6 mb-3">
|
<div class="col-lg-2 col-md-3 col-sm-6 mb-3">
|
||||||
<input type="checkbox" class="service-checkbox data-price=${this.price} btn-check-service" id="${this.id}" name="services[]" value="${this.id}" autocomplete="off" ${this.checked ? 'checked' : ''} />
|
<input type="checkbox" class="service-checkbox btn-check-service" data-price=${this.price} id="${this.id}" name="services[]" value="${this.id}" autocomplete="off" ${this.checked ? 'checked' : ''} />
|
||||||
<label class="btn btn-service-option w-100 text-center py-3 px-2 d-flex flex-column align-items-center justify-content-center h-100" for="${this.id}">
|
<label class="btn btn-service-option w-100 text-center py-3 px-2 d-flex flex-column align-items-center justify-content-center h-100" for="${this.id}">
|
||||||
${ribbonHtml}
|
${ribbonHtml}
|
||||||
<h5 class="service-title mb-1">${this.title}</h5>
|
<h5 class="service-title mb-1">${this.title}</h5>
|
||||||
|
|||||||
@ -138,4 +138,27 @@ export function updateFaja() {
|
|||||||
$('#summary-faja-tamanio-solapa').text($('#tamanio-solapas-faja').val() + ' mm');
|
$('#summary-faja-tamanio-solapa').text($('#tamanio-solapas-faja').val() + ' mm');
|
||||||
$('#summary-faja-acabado').text($('#faja-acabado option:selected').text());
|
$('#summary-faja-acabado').text($('#faja-acabado option:selected').text());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateExtras() {
|
||||||
|
const $table = $('#summary-servicios-extras');
|
||||||
|
const $tbody = $table.find('tbody');
|
||||||
|
$tbody.empty();
|
||||||
|
|
||||||
|
// Agregar las filas de servicios extras
|
||||||
|
$('.service-checkbox:checked').each(function() {
|
||||||
|
const $servicio = $(this);
|
||||||
|
const resumen = $(`label[for="${$servicio.attr('id')}"] .service-title`).text().trim() || $servicio.attr('id');
|
||||||
|
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)
|
||||||
|
);
|
||||||
|
$tbody.append($row);
|
||||||
|
});
|
||||||
|
if ($tbody.children().length > 0) {
|
||||||
|
$table.removeClass('d-none');
|
||||||
|
} else {
|
||||||
|
$table.addClass('d-none');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -36,6 +36,7 @@
|
|||||||
<div th:unless="${#authorization.expression('isAuthenticated()')}">
|
<div th:unless="${#authorization.expression('isAuthenticated()')}">
|
||||||
<script th:src="@{/assets/js/pages/imprimelibros/presupuestador/imagen-selector.js}"></script>
|
<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/presupuestador.js}"></script>
|
||||||
|
<script type="module" th:src="@{/assets/js/pages/imprimelibros/presupuestador/presupuesto-maquetacion.js}"></script>
|
||||||
</div>
|
</div>
|
||||||
<script th:inline="javascript">
|
<script th:inline="javascript">
|
||||||
window.languageBundle = /*[[${languageBundle}]]*/ {};
|
window.languageBundle = /*[[${languageBundle}]]*/ {};
|
||||||
|
|||||||
@ -0,0 +1,15 @@
|
|||||||
|
<div th:fragment="modal (id, title, size, bodyId)">
|
||||||
|
<div class="modal fade" th:id="${id}" tabindex="-1" aria-hidden="true">
|
||||||
|
<div th:class="'modal-dialog ' + ${size}">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" th:text="#{${title}}">Título</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Cerrar"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" th:id="${bodyId}">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@ -205,6 +205,15 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<table id="summary-servicios-extras" class="table table-responsive align-middle mb-0 d-none">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th th:text="#{presupuesto.extras}" scope="col" colspan="2">Servicios</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- end card body -->
|
<!-- end card body -->
|
||||||
|
|||||||
@ -1,4 +1,12 @@
|
|||||||
<div th:fragment="presupuestador">
|
<div th:fragment="presupuestador">
|
||||||
|
<!-- Modales-->
|
||||||
|
<div
|
||||||
|
th:replace="imprimelibros/partials/modal-form :: modal('maquetacionModal', 'presupuesto.maquetacion', 'modal-lg', 'maquetacionModalBody')">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
th:replace="imprimelibros/partials/modal-form :: modal('marcapaginasModal', 'presupuesto.marcapaginas', 'modal-lg', 'marcapaginasModalBody')">
|
||||||
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xl-9">
|
<div class="col-xl-9">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
|||||||
@ -0,0 +1,77 @@
|
|||||||
|
<div th:fragment="maquetacionForm">
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<option value="A5" selected>A5</option>
|
||||||
|
<option value="_17x24_">170x240 mm</option>
|
||||||
|
<option value="A4">A4</option>
|
||||||
|
</select>
|
||||||
|
<label th:text="#{presupuesto.maquetacion.formato-descripcion}" class="form-text text-muted"></label>
|
||||||
|
</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>
|
||||||
|
<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>
|
||||||
|
</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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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"
|
||||||
|
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"
|
||||||
|
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"
|
||||||
|
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"
|
||||||
|
th:field="*{epub}">
|
||||||
|
<label for="epub" class="form-check-label" th:text="#{presupuesto.maquetacion.epub}">ePub</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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,50 @@
|
|||||||
|
package com.imprimelibros.erp;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
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 com.imprimelibros.erp.presupuesto.PresupuestoService;
|
||||||
|
import com.imprimelibros.erp.presupuesto.Presupuesto;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
class presupuestoMaquetacionTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
protected PresupuestoService presupuestoService;
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(presupuestoMaquetacionTest.class);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCalculoMaquetacion() {
|
||||||
|
String resultado = this.test();
|
||||||
|
|
||||||
|
System.out.println("📦 Resultado:");
|
||||||
|
System.out.println(resultado);
|
||||||
|
log.info("📦 Resultado: {}", resultado);
|
||||||
|
|
||||||
|
assertNotNull(resultado, "El resultado no debe ser null");
|
||||||
|
assertTrue(resultado.trim().startsWith("{"), "El resultado debe comenzar con { (JSON)");
|
||||||
|
assertTrue(resultado.trim().endsWith("}"), "El resultado debe terminar con } (JSON)");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String test() {
|
||||||
|
|
||||||
|
/*Presupuesto presupuesto = new Presupuesto();
|
||||||
|
presupuesto.setPresupuestoMaquetacion(true);
|
||||||
|
presupuesto.setPresupuestoMaquetacionData(
|
||||||
|
"{\"numCaracteres\":200000,\"formato\":\"A5\",\"fontSize\":\"medium\",\"numTablas\":5,\"numColumnas\":1,\"numFotos\":10,\"correccionOrtotipografica\":true,\"textoMecanografiado\":false,\"disenioPortada\":true,\"epub\":true}");
|
||||||
|
|
||||||
|
Map<String, Object> resultado = presupuestoService.getPrecioMaquetacion(presupuesto);
|
||||||
|
return resultado.toString();*/
|
||||||
|
return "{}";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user