mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-12 16:38:48 +00:00
primera versión de pedido realizada
This commit is contained in:
@ -11,8 +11,6 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
import com.imprimelibros.erp.presupuesto.classes.PresupuestoFormatter;
|
||||
import com.imprimelibros.erp.presupuesto.dto.Presupuesto;
|
||||
@ -327,7 +325,6 @@ public class CartService {
|
||||
return summary;
|
||||
}
|
||||
|
||||
|
||||
public Map<String, Object> getCartSummary(Cart cart, Locale locale) {
|
||||
Map<String, Object> raw = getCartSummaryRaw(cart, locale);
|
||||
|
||||
@ -502,6 +499,7 @@ public class CartService {
|
||||
p.setProveedorRef2(presId);
|
||||
p.setEstado(Presupuesto.Estado.aceptado);
|
||||
presupuestoRepo.save(p);
|
||||
|
||||
presupuestoIds.add(p.getId());
|
||||
|
||||
presupuestoRequests.add(dataMap);
|
||||
|
||||
@ -47,6 +47,26 @@ public class Utils {
|
||||
this.messageSource = messageSource;
|
||||
}
|
||||
|
||||
public static List<Map<String, Object>> decodeJsonList(String json) {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
try {
|
||||
return mapper.readValue(json, new TypeReference<List<Map<String, Object>>>() {
|
||||
});
|
||||
} catch (JsonProcessingException e) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<String, Object> decodeJsonMap(String json) {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
try {
|
||||
return mapper.readValue(json, new TypeReference<Map<String, Object>>() {
|
||||
});
|
||||
} catch (JsonProcessingException e) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
public static double round2(double value) {
|
||||
return BigDecimal.valueOf(value)
|
||||
.setScale(2, RoundingMode.HALF_UP)
|
||||
|
||||
@ -52,6 +52,7 @@ import com.imprimelibros.erp.users.UserDao;
|
||||
import com.imprimelibros.erp.users.UserDetailsImpl;
|
||||
import com.imprimelibros.erp.presupuesto.service.PresupuestoFormDataMapper;
|
||||
import com.imprimelibros.erp.presupuesto.service.PresupuestoFormDataMapper.PresupuestoFormDataDto;
|
||||
import com.imprimelibros.erp.common.Utils;
|
||||
import com.imprimelibros.erp.common.web.IpUtils;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
@ -561,6 +562,20 @@ public class PresupuestoController {
|
||||
return "redirect:/presupuesto";
|
||||
}
|
||||
|
||||
if(presupuestoOpt.get().getEstado() == Presupuesto.Estado.aceptado){
|
||||
|
||||
Map<String, Object> resumen = presupuestoService.getTextosResumen(
|
||||
presupuestoOpt.get(),
|
||||
Utils.decodeJsonList(presupuestoOpt.get().getServiciosJson()),
|
||||
Utils.decodeJsonMap(presupuestoOpt.get().getDatosMaquetacionJson()),
|
||||
Utils.decodeJsonMap(presupuestoOpt.get().getDatosMarcapaginasJson()),
|
||||
locale);
|
||||
|
||||
model.addAttribute("resumen", resumen);
|
||||
model.addAttribute("presupuesto", presupuestoOpt.get());
|
||||
return "imprimelibros/presupuestos/presupuestador-view";
|
||||
}
|
||||
|
||||
if (!presupuestoService.canAccessPresupuesto(presupuestoOpt.get(), authentication)) {
|
||||
// Añadir mensaje flash para mostrar alerta
|
||||
redirectAttributes.addFlashAttribute("errorMessage",
|
||||
|
||||
@ -918,6 +918,7 @@ public class PresupuestoService {
|
||||
/ Double.parseDouble(servicio.get("units").toString())
|
||||
: servicio.get("price"));
|
||||
servicioData.put("unidades", servicio.get("units"));
|
||||
servicioData.put("id", servicio.get("id"));
|
||||
serviciosExtras.add(servicioData);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,22 @@
|
||||
package com.imprimelibros.erp.redsys;
|
||||
|
||||
import com.imprimelibros.erp.common.Utils;
|
||||
import com.imprimelibros.erp.payments.PaymentService;
|
||||
import com.imprimelibros.erp.payments.model.Payment;
|
||||
import com.imprimelibros.erp.redsys.RedsysService.FormPayload;
|
||||
|
||||
import jakarta.servlet.ServletContext;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.thymeleaf.context.WebContext;
|
||||
import org.thymeleaf.web.IWebExchange;
|
||||
import org.thymeleaf.web.servlet.JakartaServletWebApplication;
|
||||
import org.thymeleaf.spring6.SpringTemplateEngine;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@ -22,37 +35,51 @@ public class RedsysController {
|
||||
|
||||
private final PaymentService paymentService;
|
||||
private final MessageSource messageSource;
|
||||
private final SpringTemplateEngine templateEngine;
|
||||
private final ServletContext servletContext;
|
||||
|
||||
public RedsysController(PaymentService paymentService, MessageSource messageSource) {
|
||||
public RedsysController(PaymentService paymentService, MessageSource messageSource,
|
||||
SpringTemplateEngine templateEngine, ServletContext servletContext) {
|
||||
this.paymentService = paymentService;
|
||||
this.messageSource = messageSource;
|
||||
this.templateEngine = templateEngine;
|
||||
this.servletContext = servletContext;
|
||||
}
|
||||
|
||||
@PostMapping(value = "/crear", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||||
@ResponseBody
|
||||
public ResponseEntity<byte[]> crearPago(@RequestParam("amountCents") Long amountCents,
|
||||
@RequestParam("method") String method, @RequestParam("cartId") Long cartId) throws Exception {
|
||||
@RequestParam("method") String method, @RequestParam("cartId") Long cartId,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response, Locale locale)
|
||||
throws Exception {
|
||||
|
||||
if ("bank-transfer".equalsIgnoreCase(method)) {
|
||||
// 1) Creamos el Payment interno SIN orderId (null)
|
||||
Payment p = paymentService.createBankTransferPayment(cartId, amountCents, "EUR");
|
||||
|
||||
// 2) Mostramos instrucciones de transferencia
|
||||
String html = """
|
||||
<html><head><meta charset="utf-8"><title>Pago por transferencia</title></head>
|
||||
<body>
|
||||
<h2>Pago por transferencia bancaria</h2>
|
||||
<p>Hemos registrado tu intención de pedido.</p>
|
||||
<p><strong>Importe:</strong> %s €</p>
|
||||
<p><strong>IBAN:</strong> ES00 1234 5678 9012 3456 7890</p>
|
||||
<p><strong>Concepto:</strong> TRANSF-%d</p>
|
||||
<p>En cuanto recibamos la transferencia, procesaremos tu pedido.</p>
|
||||
<p><a href="/checkout/resumen">Volver al resumen</a></p>
|
||||
</body></html>
|
||||
""".formatted(
|
||||
String.format("%.2f", amountCents / 100.0),
|
||||
p.getId() // usamos el ID del Payment como referencia
|
||||
);
|
||||
// 1️⃣ Crear la "aplicación" web de Thymeleaf (Jakarta)
|
||||
JakartaServletWebApplication app = JakartaServletWebApplication.buildApplication(servletContext);
|
||||
|
||||
// 2️⃣ Construir el intercambio web desde request/response
|
||||
response.setContentType("text/html;charset=UTF-8");
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
IWebExchange exchange = app.buildExchange(request, response);
|
||||
|
||||
// 3️⃣ Crear el contexto WebContext con Locale
|
||||
WebContext ctx = new WebContext(exchange, locale);
|
||||
|
||||
String importeFormateado = Utils.formatCurrency(amountCents / 100.0, locale);
|
||||
ctx.setVariable("importe", importeFormateado);
|
||||
ctx.setVariable("concepto", "TRANSF-" + p.getId());
|
||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||
boolean isAuth = auth != null
|
||||
&& auth.isAuthenticated()
|
||||
&& !(auth instanceof AnonymousAuthenticationToken);
|
||||
ctx.setVariable("isAuth", isAuth);
|
||||
|
||||
// 3) Renderizamos la plantilla a HTML
|
||||
String html = templateEngine.process("imprimelibros/pagos/transfer", ctx);
|
||||
|
||||
byte[] body = html.getBytes(StandardCharsets.UTF_8);
|
||||
return ResponseEntity.ok()
|
||||
@ -92,7 +119,8 @@ public class RedsysController {
|
||||
// GET: cuando el usuario cae aquí sin parámetros, o Redsys redirige por GET
|
||||
@GetMapping("/ok")
|
||||
public String okGet(RedirectAttributes redirectAttrs, Model model, Locale locale) {
|
||||
String msg = messageSource.getMessage("checkout.success.payment", null, "Pago realizado con éxito. Gracias por su compra.", locale);
|
||||
String msg = messageSource.getMessage("checkout.success.payment", null,
|
||||
"Pago realizado con éxito. Gracias por su compra.", locale);
|
||||
model.addAttribute("successPago", msg);
|
||||
redirectAttrs.addFlashAttribute("successPago", msg);
|
||||
return "redirect:/cart";
|
||||
@ -117,7 +145,9 @@ public class RedsysController {
|
||||
@GetMapping("/ko")
|
||||
public String koGet(RedirectAttributes redirectAttrs, Model model, Locale locale) {
|
||||
|
||||
String msg = messageSource.getMessage("checkout.error.payment", null, "Error al procesar el pago: el pago ha sido cancelado o rechazado Por favor, inténtelo de nuevo.", locale);
|
||||
String msg = messageSource.getMessage("checkout.error.payment", null,
|
||||
"Error al procesar el pago: el pago ha sido cancelado o rechazado Por favor, inténtelo de nuevo.",
|
||||
locale);
|
||||
model.addAttribute("errorPago", msg);
|
||||
redirectAttrs.addFlashAttribute("errorPago", msg);
|
||||
return "redirect:/cart";
|
||||
|
||||
@ -7,6 +7,7 @@ app.seleccionar=Seleccionar
|
||||
app.guardar=Guardar
|
||||
app.editar=Editar
|
||||
app.add=Añadir
|
||||
app.back=Volver
|
||||
app.eliminar=Eliminar
|
||||
app.imprimir=Imprimir
|
||||
app.acciones.siguiente=Siguiente
|
||||
|
||||
@ -28,6 +28,9 @@ pagos.transferencia.finalizar.text=¿Estás seguro de que deseas marcar esta tra
|
||||
pagos.transferencia.finalizar.success=Transferencia bancaria marcada como completada con éxito.
|
||||
pagos.transferencia.finalizar.error.general=Error al finalizar la transferencia bancaria
|
||||
|
||||
pagos.transferencia.ok.title=Pago por transferencia bancaria
|
||||
pagos.transferencia.ok.text=Ha realizado su pedido correctamente. Para completar el pago, realice una transferencia bancaria con los siguientes datos:<br>Titular de la cuenta: Impresión Imprime Libros SL<br>IBAN: ES00 1234 5678 9012 3456 7890<br>Importe: {0}<br>Concepto: {1}<br>Le rogamos que nos envíe el justificante de la transferencia respondiendo al correo de confirmación de pedido que le acabamos de enviar.<br>Si no encuentra el mensaje, por favor revise la carpeta de correo no deseado y añada <a href="mailto:contacto@imprimelibros.com">contacto@imprimelibros.com</a>
|
||||
|
||||
pagos.refund.title=Devolución
|
||||
pagos.refund.text=Introduce la cantidad a devolver (en euros):
|
||||
pagos.refund.success=Devolución solicitada con éxito. Si no se refleja inmediatamente, espere unos minutos y actualiza la página.
|
||||
|
||||
@ -12,6 +12,11 @@ $(() => {
|
||||
// remove name from container . direccion-card
|
||||
container.find('.direccion-card input[type="hidden"]').removeAttr('name');
|
||||
|
||||
if (container.find('.direccion-card').length === 0) {
|
||||
// no addresses, no need to submit
|
||||
$("alert-empty").removeClass("d-none");
|
||||
}
|
||||
|
||||
container.find('.direccion-card').each(function (i) {
|
||||
$(this).find('.direccion-id').attr('name', 'direcciones[' + i + '].id');
|
||||
$(this).find('.direccion-cp').attr('name', 'direcciones[' + i + '].cp');
|
||||
|
||||
@ -0,0 +1,64 @@
|
||||
import { formateaMoneda } from "../utils.js";
|
||||
$(() => {
|
||||
|
||||
const locale = $("html").attr("lang") || "es-ES";
|
||||
$.ajaxSetup({
|
||||
beforeSend: function (xhr) {
|
||||
const token = document.querySelector('meta[name="_csrf"]')?.content;
|
||||
const header = document.querySelector('meta[name="_csrf_header"]')?.content;
|
||||
if (token && header) xhr.setRequestHeader(header, token);
|
||||
}
|
||||
});
|
||||
|
||||
$(".moneda").each((index, element) => {
|
||||
const valor = $(element).text().trim();
|
||||
const tr = $(element).closest(".tr");
|
||||
if (tr.data("servicio-id") == "marcapaginas") {
|
||||
$(element).text(formateaMoneda(valor, 4, locale, 'EUR'));
|
||||
}
|
||||
else {
|
||||
$(element).text(formateaMoneda(valor, 2, locale, 'EUR'));
|
||||
}
|
||||
});
|
||||
|
||||
$(".moneda4").each((index, element) => {
|
||||
const valor = $(element).text().trim();
|
||||
$(element).text(formateaMoneda(valor, 4, locale, 'EUR'));
|
||||
|
||||
});
|
||||
|
||||
$('.btn-imprimir').on('click', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// obtén el id de donde lo tengas (data-attr o variable global)
|
||||
const id = $('#presupuesto-id').val();
|
||||
|
||||
const url = `/api/pdf/presupuesto/${id}?mode=download`;
|
||||
|
||||
// Truco: crear <a> y hacer click
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.target = '_self'; // descarga en la misma pestaña
|
||||
// a.download = `presupuesto-${id}.pdf`; // opcional, tu server ya pone filename
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
});
|
||||
|
||||
$('.add-cart-btn').on('click', async () => {
|
||||
|
||||
const presupuestoId = $('#presupuesto-id').val();
|
||||
const res = await $.ajax({
|
||||
url: `/cart/add/${presupuestoId}`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
|
||||
// Si el backend devuelve { redirect: "/cart" }
|
||||
if (res?.redirect) {
|
||||
window.location.assign(res.redirect); // o replace()
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
@ -37,10 +37,8 @@
|
||||
</div>
|
||||
|
||||
<div th:if="${successPago}" class="alert alert-success alert-fadeout my-1" role="alert" th:text="${successPago}"></div>
|
||||
|
||||
<div th:if="${items.isEmpty()}">
|
||||
<div id="alert-empty"class="alert alert-info" role="alert" th:text="#{cart.empty}"></div>
|
||||
</div>
|
||||
|
||||
<div id="alert-empty" th:class="'alert alert-info ' + ${items.isEmpty() ? '' : 'd-none'}" role="alert" th:text="#{cart.empty}"></div>
|
||||
|
||||
<div th:insert="~{imprimelibros/cart/_cartContent :: cartContent(${items}, ${cartId})}"></div>
|
||||
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
<html th:lang="${#locale.country != '' ? #locale.language + '-' + #locale.country : #locale.language}"
|
||||
th:with="isAuth=${#authorization.expression('isAuthenticated()')}"
|
||||
<html th:lang="${#locale.country != '' ? #locale.language + '-' + #locale.country : #locale.language}" th:with="isAuth=${isAuth != null
|
||||
? isAuth
|
||||
: (#authorization == null ? false : #authorization.expression('isAuthenticated()'))}"
|
||||
th:attrappend="data-layout=${isAuth} ? 'semibox' : 'horizontal'" data-sidebar-visibility="show" data-topbar="light"
|
||||
data-sidebar="light" data-sidebar-size="lg" data-sidebar-image="none" data-preloader="disable"
|
||||
xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
|
||||
|
||||
<head>
|
||||
<meta name="_csrf" th:content="${_csrf.token}" />
|
||||
<meta name="_csrf_header" th:content="${_csrf.headerName}" />
|
||||
<meta name="_csrf" th:content="${_csrf != null ? _csrf.token : ''}" />
|
||||
<meta name="_csrf_header" th:content="${_csrf != null ? _csrf.headerName : ''}" />
|
||||
|
||||
<th:block layout:fragment="pagetitle" />
|
||||
<th:block th:replace="~{imprimelibros/partials/head-css :: head-css}" />
|
||||
@ -18,10 +19,11 @@
|
||||
|
||||
<body>
|
||||
<div th:replace="~{imprimelibros/partials/topbar :: topbar}" />
|
||||
<div th:if="${#authorization.expression('isAuthenticated()')}">
|
||||
<div th:if="${isAuth}">
|
||||
<div th:replace="~{imprimelibros/partials/sidebar :: sidebar}" />
|
||||
</div>
|
||||
|
||||
|
||||
<section class="main-content">
|
||||
<div class="page-content">
|
||||
<div class="container-fluid">
|
||||
@ -39,10 +41,11 @@
|
||||
<th:block layout:fragment="pagejs" />
|
||||
<script th:src="@{/assets/js/app.js}"></script>
|
||||
<script th:src="@{/assets/js/pages/imprimelibros/languageBundle.js}"></script>
|
||||
<th:block th:if="${#authorization.expression('isAuthenticated()')}">
|
||||
<th:block th:if="${isAuth}">
|
||||
<script src="/assets/js/pages/imprimelibros/cart-badge.js"></script>
|
||||
</th:block>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@ -0,0 +1,73 @@
|
||||
<!doctype html>
|
||||
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{imprimelibros/layout}">
|
||||
|
||||
<head>
|
||||
<th:block layout:fragment="pagetitle" />
|
||||
<th:block th:replace="~{imprimelibros/partials/head-css :: head-css}" />
|
||||
<th:block layout:fragment="pagecss">
|
||||
<link href="/assets/libs/datatables/dataTables.bootstrap5.min.css" rel="stylesheet" />
|
||||
</th:block>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div th:replace="~{imprimelibros/partials/topbar :: topbar}" />
|
||||
<div th:replace="~{imprimelibros/partials/sidebar :: sidebar}"
|
||||
sec:authorize="isAuthenticated() and hasAnyRole('SUPERADMIN','ADMIN')">
|
||||
|
||||
<th:block layout:fragment="content">
|
||||
<div th:if="${isAuth}">
|
||||
|
||||
|
||||
<div class="container-fluid">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/"><i class="ri-home-5-fill"></i></a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="container-fluid">
|
||||
|
||||
<div class="row" id="card">
|
||||
<div class="card">
|
||||
|
||||
<div class="card-body">
|
||||
<h3 th:text="#{pagos.transferencia.ok.title}"></h3>
|
||||
<span th:utext="#{pagos.transferencia.ok.text(${importe}, ${concepto})}"></span>
|
||||
<div class="d-flex flex-wrap justify-content-center">
|
||||
<a th:href="@{/}" class="btn btn-secondary mt-3">
|
||||
<i class="ri-home-5-fill me-1"></i>
|
||||
<span th:text="#{app.back}">Volver</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--end row-->
|
||||
</div>
|
||||
</div>
|
||||
</th:block>
|
||||
|
||||
<th:block th:replace="~{theme/partials/vendor-scripts :: scripts}" />
|
||||
<th:block layout:fragment="pagejs">
|
||||
<script th:inline="javascript">
|
||||
window.languageBundle = /*[[${languageBundle}]]*/ {};
|
||||
</script>
|
||||
|
||||
<!-- JS de Buttons y dependencias -->
|
||||
<div th:if="${appMode} == 'view'">
|
||||
<script type="module" th:src="@{/assets/js/pages/imprimelibros/presupuestador/wizard-publicos.js}"></script>
|
||||
</div>
|
||||
<div th:if="${appMode} == 'edit'">
|
||||
<script type="module" th:src="@{/assets/js/pages/imprimelibros/presupuestador/wizard-privado.js}"></script>
|
||||
</div>
|
||||
|
||||
<script type="module" th:src="@{/assets/js/pages/imprimelibros/presupuestos/resumen-view.js}"></script>
|
||||
|
||||
</th:block>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@ -64,7 +64,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div th:if="${#authorization.expression('isAuthenticated()')}"
|
||||
<div th:if="${isAuth}"
|
||||
class="ms-1 header-item d-none d-sm-flex">
|
||||
<button type="button" id="btn_cart"
|
||||
class="btn btn-icon btn-topbar material-shadow-none btn-ghost-secondary rounded-circle light-dark-mode">
|
||||
@ -80,7 +80,7 @@
|
||||
|
||||
|
||||
|
||||
<div th:if="${#authorization.expression('isAuthenticated()')}">
|
||||
<div th:if="${isAuth}">
|
||||
<div class="dropdown ms-sm-3 header-item topbar-user">
|
||||
<button type="button" class="btn" id="page-header-user-dropdown" data-bs-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false">
|
||||
@ -114,9 +114,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- Si NO está autenticado -->
|
||||
<div th:unless="${#authorization.expression('isAuthenticated()')}">
|
||||
<div th:unless="${isAuth}">
|
||||
<a href="/login" class="btn btn-outline-primary ms-sm-3">
|
||||
<i class="mdi mdi-login"></i> <label th:text="#{login.login}">Iniciar sesión</p>
|
||||
<i class="mdi mdi-login"></i> <label th:text="#{login.login}">Iniciar sesión</label>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,72 +1,196 @@
|
||||
<div id="presupuesto-app" th:data-mode="${appMode} ?: 'public'" th:data-id="${id} ?: ''" th:fragment="presupuestador">
|
||||
<!doctype html>
|
||||
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{imprimelibros/layout}">
|
||||
|
||||
<div class="row" id="presupuesto-row">
|
||||
<div class="animate-fadeInUpBounce">
|
||||
<head>
|
||||
<th:block layout:fragment="pagetitle" />
|
||||
<th:block th:replace="~{imprimelibros/partials/head-css :: head-css}" />
|
||||
<th:block layout:fragment="pagecss">
|
||||
<link th:href="@{/assets/libs/datatables/dataTables.bootstrap5.min.css}" rel="stylesheet" />
|
||||
</th:block>
|
||||
<th:block layout:fragment="pagecss">
|
||||
<link th:href="@{/assets/css/presupuestador.css}" rel="stylesheet" />
|
||||
</th:block>
|
||||
</head>
|
||||
|
||||
<!-- 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>
|
||||
<body>
|
||||
|
||||
<div th:replace="~{imprimelibros/partials/topbar :: topbar}" />
|
||||
<div th:replace="~{imprimelibros/partials/sidebar :: sidebar}"
|
||||
sec:authorize="isAuthenticated() and hasAnyRole('SUPERADMIN','ADMIN')">
|
||||
|
||||
<th:block layout:fragment="content">
|
||||
<div th:if="${#authorization.expression('isAuthenticated()')}">
|
||||
|
||||
|
||||
<div class="container-fluid">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/"><i class="ri-home-5-fill"></i></a></li>
|
||||
<li class="breadcrumb-item"><a href="/presupuesto" th:text="#{presupuesto.title}"></a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page" th:if="${appMode == 'add'}"
|
||||
th:text="#{presupuesto.add}">
|
||||
Nuevo presupuesto
|
||||
</li>
|
||||
<li class="breadcrumb-item active" aria-current="page" th:text="#{presupuesto.editar.title}"
|
||||
th:if="${appMode == 'edit'}">
|
||||
Editar presupuesto
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="ribbon-content mt-4">
|
||||
<div id="div-extras" class="hstack gap-2 justify-content-center flex-wrap">
|
||||
<div class="container-fluid">
|
||||
|
||||
<input type="hidden" id="presupuesto-id" th:value="${presupuesto.id}" />
|
||||
|
||||
<div class="row" id="card presupuesto-row animate-fadeInUpBounce">
|
||||
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4 class="card-title mb-0 text-uppercase" th:text="${resumen.titulo}">Resumen del
|
||||
presupuesto</h4>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
|
||||
<div class="card col-12 col-sm-9 mx-auto">
|
||||
<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 class="text-end" th:text="#{presupuesto.resumen.tabla.cantidad}">
|
||||
Cantidad</th>
|
||||
<th class="text-end"
|
||||
th:text="#{presupuesto.resumen.tabla.precio-unidad}">Precio
|
||||
unitario
|
||||
</th>
|
||||
<th class="text-end"
|
||||
th:text="#{presupuesto.resumen.tabla.precio-total}">Precio total
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:if="${resumen['linea0']}">
|
||||
<td><img style="max-width: 60px; height: auto;" th:src="${resumen['imagen']}" th:alt="${resumen['imagen_alt']}" class="img-fluid" /></td>
|
||||
<td class="text-start" th:utext="${resumen['linea0'].descripcion}">
|
||||
Descripción 1</td>
|
||||
<td class="text-end" th:text="${resumen['linea0'].cantidad}">1</td>
|
||||
<td class="text-end moneda4"
|
||||
th:text="${resumen['linea0'].precio_unitario}">
|
||||
100,00 €
|
||||
</td>
|
||||
<td class="text-end moneda" th:text="${resumen['linea0'].precio_total}">
|
||||
100,00
|
||||
€
|
||||
</td>
|
||||
</tr>
|
||||
<tr th:if="${resumen['linea1']}">
|
||||
<td></td>
|
||||
<td class="text-start" th:utext="${resumen['linea1'].descripcion}">
|
||||
Descripción 2</td>
|
||||
<td class="text-end" th:text="${resumen['linea1'].cantidad}">1</td>
|
||||
<td class="text-end moneda4"
|
||||
th:text="${resumen['linea1'].precio_unitario}">
|
||||
50,00 €
|
||||
</td>
|
||||
<td class="text-end moneda" th:text="${resumen['linea1'].precio_total}">
|
||||
50,00 €
|
||||
</td>
|
||||
</tr>
|
||||
<th:block th:each="servicio :${resumen['servicios']}">
|
||||
<tr th:attr="data-servicio-id=${servicio['id']}">
|
||||
<td></td>
|
||||
<td class="text-start" th:utext="${servicio['descripcion']}">
|
||||
Descripción 3</td>
|
||||
<td class="text-end" th:text="${servicio['unidades']}">1</td>
|
||||
<td class="text-end moneda" th:text="${servicio['precio']}">
|
||||
25,00 €
|
||||
</td>
|
||||
<td class="text-end moneda"
|
||||
th:text="${servicio['precio'] * servicio['unidades']}">
|
||||
25,00 €
|
||||
</td>
|
||||
</tr>
|
||||
</th:block>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="table-active">
|
||||
<th colspan="4" class="text-end"
|
||||
th:text="#{presupuesto.resumen.tabla.base}">Total</th>
|
||||
<th class="text-end moneda" id="resumen-base"
|
||||
th:text="${presupuesto.baseImponible}">0,00 €</th>
|
||||
</tr>
|
||||
<tr th:if="${presupuesto.ivaImporte4 > 0}" id="tr-resumen-iva4"
|
||||
class="table-active">
|
||||
<th colspan="4" class="text-end"
|
||||
th:text="#{presupuesto.resumen.tabla.iva4}">IVA (4%)</th>
|
||||
<th class="text-end moneda" id="resumen-iva4"
|
||||
th:text="${presupuesto.ivaImporte4}">0,00 €</th>
|
||||
</tr>
|
||||
<tr th:if="${presupuesto.ivaImporte21 > 0}" id="tr-resumen-iva21"
|
||||
class="table-active">
|
||||
<th colspan="4" class="text-end"
|
||||
th:text="#{presupuesto.resumen.tabla.iva21}">IVA (21%)</th>
|
||||
<th class="text-end moneda" id="resumen-iva21"
|
||||
th:text="${presupuesto.ivaImporte21}">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 moneda" id="resumen-total"
|
||||
th:text="${presupuesto.totalConIva}">0,00 €</th>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="buttons-row center">
|
||||
|
||||
<button type="button"
|
||||
class="btn btn-secondary d-flex align-items-center mx-2 btn-imprimir">
|
||||
<i class="ri-printer-line me-2"></i>
|
||||
<span th:text="#{app.imprimir}">Imprimir</span>
|
||||
</button>
|
||||
|
||||
<button type="button"
|
||||
class="btn btn-secondary d-flex align-items-center mx-2 add-cart-btn">
|
||||
<i class="ri-shopping-cart-line me-2"></i>
|
||||
<span th:text="#{presupuesto.add-to-cart}">Añadir a la cesta</span>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--end row-->
|
||||
</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 id="tr-resumen-iva4" class="table-active">
|
||||
<th colspan="4" class="text-end" th:text="#{presupuesto.resumen.tabla.iva4}">IVA (4%)</th>
|
||||
<th class="text-end" id="resumen-iva4">0,00 €</th>
|
||||
</tr>
|
||||
<tr id="tr-resumen-iva21" class="table-active">
|
||||
<th colspan="4" class="text-end" th:text="#{presupuesto.resumen.tabla.iva21}">IVA (21%)</th>
|
||||
<th class="text-end" id="resumen-iva21">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 type="button"
|
||||
class="btn btn-secondary d-flex align-items-center mx-2 btn-imprimir">
|
||||
<i class="ri-printer-line me-2"></i>
|
||||
<span th:text="#{app.imprimir}">Imprimir</span>
|
||||
</button>
|
||||
|
||||
<button type="button"
|
||||
class="btn btn-secondary d-flex align-items-center mx-2 add-cart-btn">
|
||||
<i class="ri-shopping-cart-line me-2"></i>
|
||||
<span th:text="#{presupuesto.add-to-cart}">Añadir a la cesta</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--end row-->
|
||||
</div>
|
||||
</th:block>
|
||||
|
||||
<th:block th:replace="~{theme/partials/vendor-scripts :: scripts}" />
|
||||
<th:block layout:fragment="pagejs">
|
||||
<script th:inline="javascript">
|
||||
window.languageBundle = /*[[${languageBundle}]]*/ {};
|
||||
</script>
|
||||
|
||||
<!-- JS de Buttons y dependencias -->
|
||||
<div th:if="${appMode} == 'view'">
|
||||
<script type="module" th:src="@{/assets/js/pages/imprimelibros/presupuestador/wizard-publicos.js}"></script>
|
||||
</div>
|
||||
<div th:if="${appMode} == 'edit'">
|
||||
<script type="module" th:src="@{/assets/js/pages/imprimelibros/presupuestador/wizard-privado.js}"></script>
|
||||
</div>
|
||||
|
||||
<script type="module" th:src="@{/assets/js/pages/imprimelibros/presupuestos/resumen-view.js}"></script>
|
||||
|
||||
</th:block>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user