reimpresion a SK

This commit is contained in:
2025-12-01 22:31:40 +01:00
parent c6e2322132
commit 3b9f446195
14 changed files with 1111 additions and 8083 deletions

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.7</version> <version>3.5.8</version>
<relativePath /> <!-- lookup parent from repository --> <relativePath /> <!-- lookup parent from repository -->
</parent> </parent>
<groupId>com.imprimelibros</groupId> <groupId>com.imprimelibros</groupId>

View File

@ -530,7 +530,16 @@ public class PresupuestoController {
"presupuesto.duplicar.success.title", "presupuesto.duplicar.success.title",
"presupuesto.duplicar.success.text", "presupuesto.duplicar.success.text",
"presupuesto.duplicar.error.title", "presupuesto.duplicar.error.title",
"presupuesto.duplicar.error.internal" "presupuesto.duplicar.error.internal",
"presupuesto.reimprimir.title",
"presupuesto.reimprimir.text",
"presupuesto.reimprimir.confirm",
"presupuesto.reimprimir.cancelar",
"presupuesto.reimprimir.aceptar",
"presupuesto.reimprimir.success.title",
"presupuesto.reimprimir.success.text",
"presupuesto.reimprimir.error.title",
"presupuesto.reimprimir.error.internal"
); );
Map<String, String> translations = translationService.getTranslations(locale, keys); Map<String, String> translations = translationService.getTranslations(locale, keys);
@ -555,7 +564,26 @@ public class PresupuestoController {
"presupuesto.exito.guardado", "presupuesto.exito.guardado",
"presupuesto.add.error.save.title", "presupuesto.add.error.save.title",
"presupuesto.iva-reducido", "presupuesto.iva-reducido",
"presupuesto.iva-reducido-descripcion"); "presupuesto.iva-reducido-descripcion",
"presupuesto.duplicar.title",
"presupuesto.duplicar.text",
"presupuesto.duplicar.confirm",
"presupuesto.duplicar.cancelar",
"presupuesto.duplicar.aceptar",
"presupuesto.duplicar.required",
"presupuesto.duplicar.success.title",
"presupuesto.duplicar.success.text",
"presupuesto.duplicar.error.title",
"presupuesto.duplicar.error.internal",
"presupuesto.reimprimir.title",
"presupuesto.reimprimir.text",
"presupuesto.reimprimir.confirm",
"presupuesto.reimprimir.cancelar",
"presupuesto.reimprimir.aceptar",
"presupuesto.reimprimir.success.title",
"presupuesto.reimprimir.success.text",
"presupuesto.reimprimir.error.title",
"presupuesto.reimprimir.error.internal");
Map<String, String> translations = translationService.getTranslations(locale, keys); Map<String, String> translations = translationService.getTranslations(locale, keys);
model.addAttribute("languageBundle", translations); model.addAttribute("languageBundle", translations);
@ -801,16 +829,22 @@ public class PresupuestoController {
return "OK"; return "OK";
} }
@PostMapping("/duplicar/{id}") @PostMapping("/api/duplicar/{id}")
@ResponseBody @ResponseBody
public Map<String, Object> duplicarPresupuesto( public Map<String, Object> duplicarPresupuesto(
@PathVariable Long id, @PathVariable Long id,
@RequestParam(name = "titulo", defaultValue = "") String titulo) { @RequestParam(name = "titulo", defaultValue = "") String titulo) {
Long entity = presupuestoService.duplicarPresupuesto(id, titulo); Long entity = presupuestoService.duplicarPresupuesto(id, titulo);
return Map.of("id", entity); return Map.of("id", entity);
} }
@PostMapping("/api/reimprimir/{id}")
@ResponseBody
public Map<String, Object> reimprimirPresupuesto(@PathVariable Long id) {
Long entity = presupuestoService.reimprimirPresupuesto(id);
return Map.of("id", entity);
}
} }

View File

@ -1023,6 +1023,7 @@ public class PresupuestoService {
resumen.put("iva_importe_4", presupuesto.getIvaImporte4()); resumen.put("iva_importe_4", presupuesto.getIvaImporte4());
resumen.put("iva_importe_21", presupuesto.getIvaImporte21()); resumen.put("iva_importe_21", presupuesto.getIvaImporte21());
resumen.put("total_con_iva", presupuesto.getTotalConIva()); resumen.put("total_con_iva", presupuesto.getTotalConIva());
resumen.put("isReimpresion", presupuesto.getIsReimpresion());
return resumen; return resumen;
} }
@ -1226,6 +1227,18 @@ public class PresupuestoService {
HashMap<String, Object> result = new HashMap<>(); HashMap<String, Object> result = new HashMap<>();
try { try {
Presupuesto presupuestoExistente = null;
if (id != null) {
presupuestoExistente = presupuestoRepository.findById(id).orElse(null);
}
if (presupuestoExistente != null) {
// merge de datos que no están en el formulario
presupuesto.setIsReimpresion(presupuestoExistente.getIsReimpresion());
presupuesto.setProveedor(presupuestoExistente.getProveedor());
presupuesto.setProveedorRef1(presupuestoExistente.getProveedorRef1());
presupuesto.setProveedorRef2(presupuestoExistente.getProveedorRef2());
}
presupuesto.setDatosMaquetacionJson( presupuesto.setDatosMaquetacionJson(
datosMaquetacion != null ? new ObjectMapper().writeValueAsString(datosMaquetacion) : null); datosMaquetacion != null ? new ObjectMapper().writeValueAsString(datosMaquetacion) : null);
presupuesto.setDatosMarcapaginasJson( presupuesto.setDatosMarcapaginasJson(
@ -1378,6 +1391,24 @@ public class PresupuestoService {
nuevo.setEstado(Presupuesto.Estado.borrador); nuevo.setEstado(Presupuesto.Estado.borrador);
nuevo.setTitulo(titulo != null && !titulo.isEmpty() ? titulo : "[D] " + presupuesto.getTitulo()); nuevo.setTitulo(titulo != null && !titulo.isEmpty() ? titulo : "[D] " + presupuesto.getTitulo());
nuevo.setIsReimpresion(false); nuevo.setIsReimpresion(false);
nuevo.setProveedor(null);
nuevo.setProveedorRef1(null);
nuevo.setProveedorRef2(null);
presupuestoRepository.saveAndFlush(nuevo);
return nuevo.getId();
}
return -1;
}
public long reimprimirPresupuesto(Long presupuestoId) {
Presupuesto presupuesto = presupuestoRepository.findById(presupuestoId).orElse(null);
if (presupuesto != null) {
Presupuesto nuevo = presupuesto.clone();
nuevo.setId(null); // para que se genere uno nuevo
nuevo.setEstado(Presupuesto.Estado.borrador);
nuevo.setTitulo("[R] " + presupuesto.getTitulo());
nuevo.setIsReimpresion(true);
presupuestoRepository.saveAndFlush(nuevo); presupuestoRepository.saveAndFlush(nuevo);
return nuevo.getId(); return nuevo.getId();
} }

View File

@ -13,6 +13,7 @@ presupuesto.add=Añadir presupuesto
presupuesto.guardar=Guardar presupuesto.guardar=Guardar
presupuesto.duplicar=Duplicar presupuesto.duplicar=Duplicar
presupuesto.reimprimir=Reimprimir presupuesto.reimprimir=Reimprimir
presupuesto.reimpresion=Reimpresión
presupuesto.editar=Editar presupuesto.editar=Editar
presupuesto.ver=Ver presupuesto.ver=Ver
presupuesto.borrar=Eliminar presupuesto.borrar=Eliminar
@ -309,10 +310,22 @@ presupuesto.duplicar.text=¿Está seguro de que desea duplicar este presupuesto?
presupuesto.duplicar.required=El título es obligatorio. presupuesto.duplicar.required=El título es obligatorio.
presupuesto.duplicar.success.title=Presupuesto duplicado presupuesto.duplicar.success.title=Presupuesto duplicado
presupuesto.duplicar.success.text=El presupuesto ha sido duplicado con éxito. presupuesto.duplicar.success.text=El presupuesto ha sido duplicado con éxito.
presupuesto.duplicar.aceptar=Aceptar. presupuesto.duplicar.aceptar=Aceptar
presupuesto.duplicar.error.title=Error al duplicar presupuesto presupuesto.duplicar.error.title=Error al duplicar presupuesto
presupuesto.duplicar.error.internal=No se puede duplicar: error interno. presupuesto.duplicar.error.internal=No se puede duplicar: error interno.
# Mensajes de reimprimir presupuesto
presupuesto.reimprimir.title=Reimprimir presupuesto
presupuesto.reimprimir.confirm=Si, REIMPRIMIR
presupuesto.reimprimir.cancelar=Cancelar
presupuesto.reimprimir.text=¿Está seguro de que desea reimprimir este presupuesto?<br>Se generará una nuevo presupuesto usando los mismos ficheros para su impresión.
presupuesto.reimprimir.success.title=Presupuesto generado
presupuesto.reimprimir.success.text=El presupuesto ha sido generado con éxito.
presupuesto.reimprimir.aceptar=Aceptar
presupuesto.reimprimir.error.title=Error al generar el presupuesto
presupuesto.reimprimir.error.internal=No se puede generar el nuevo presupuesto: error interno.
# Añadir presupuesto # Añadir presupuesto
presupuesto.add.tipo=Tipo de presupuesto presupuesto.add.tipo=Tipo de presupuesto
presupuesto.add.anonimo=Anónimo presupuesto.add.anonimo=Anónimo

View File

@ -0,0 +1,16 @@
import { duplicar, reimprimir } from './presupuesto-utils.js';
$(()=> {
$('.duplicar-btn').on('click', function(e) {
e.preventDefault();
const id = $('#presupuesto_id').val();
const tituloOriginal = $('#titulo').text().trim() == '' ? $('#titulo').val().trim() : $('#titulo').text().trim();
duplicar(id, tituloOriginal);
})
$('.reimprimir-btn').on('click', function(e) {
e.preventDefault();
const id = $('#presupuesto_id').val();
reimprimir(id);
})
})

View File

@ -1,4 +1,4 @@
import { preguntarTipoPresupuesto, duplicar } from './presupuesto-utils.js'; import { preguntarTipoPresupuesto, duplicar, reimprimir } from './presupuesto-utils.js';
(() => { (() => {
// si jQuery está cargado, añade CSRF a AJAX // si jQuery está cargado, añade CSRF a AJAX
@ -210,6 +210,15 @@ import { preguntarTipoPresupuesto, duplicar } from './presupuesto-utils.js';
duplicar(id, tituloOriginal); duplicar(id, tituloOriginal);
}); });
$('#presupuestos-clientes-datatable').on('click', '.btn-reprint-privado', function (e) {
e.preventDefault();
const id = $(this).data('id');
let data = table_clientes.row($(this).parents('tr')).data();
const tituloOriginal = data.titulo;
reimprimir(id, tituloOriginal);
});
$('#presupuestos-clientes-datatable').on('click', '.btn-delete-privado', function (e) { $('#presupuestos-clientes-datatable').on('click', '.btn-delete-privado', function (e) {

View File

@ -114,7 +114,7 @@ export function duplicar(id, titulo) {
} }
$.ajax({ $.ajax({
url: `presupuesto/duplicar/${id}`, url: `/presupuesto/api/duplicar/${id}`,
data: { data: {
titulo: tituloNuevo, titulo: tituloNuevo,
}, },
@ -166,3 +166,80 @@ export function duplicar(id, titulo) {
}; };
}); });
} }
export function reimprimir(id) {
Swal.fire({
title: window.languageBundle.get(['presupuesto.reimprimir.title']) || 'Reimprimir presupuesto',
html: window.languageBundle.get(['presupuesto.reimprimir.text']) || `¿Deseas reimprimir el presupuesto?`,
icon: 'question',
showCancelButton: true,
confirmButtonText: window.languageBundle.get(['presupuesto.reimprimir.confirm']) || 'Sí, reimprimir',
cancelButtonText: window.languageBundle.get(['presupuesto.reimprimir.cancelar']) || 'Cancelar',
buttonsStyling: false,
customClass: {
confirmButton: 'btn btn-secondary me-2', // clases para el botón confirmar
cancelButton: 'btn btn-light' // clases para cancelar
},
}).then((result) => {
if (result.isConfirmed) {
const csrfToken = document.querySelector('meta[name="_csrf"]')?.getAttribute('content');
const csrfHeader = document.querySelector('meta[name="_csrf_header"]')?.getAttribute('content');
if (window.$ && csrfToken && csrfHeader) {
$.ajaxSetup({
beforeSend: function (xhr) {
xhr.setRequestHeader(csrfHeader, csrfToken);
}
});
}
$.ajax({
url: `/presupuesto/api/reimprimir/${id}`,
method: 'POST',
success: function (response) {
if (response.id && response.id > 0) {
Swal.fire({
title: window.languageBundle.get(['presupuesto.reimprimir.success.title']) || 'Presupuesto reimpreso',
text: window.languageBundle.get(['presupuesto.reimprimir.success.text']) || `El presupuesto ha sido reimpreso correctamente.`,
icon: 'success',
confirmButtonText: window.languageBundle.get(['presupuesto.reimprimir.aceptar']) || 'Aceptar',
buttonsStyling: false,
customClass: {
confirmButton: 'btn btn-secondary' // clases para el botón confirmar
},
}).then(() => {
// Recargar la página o redirigir a la lista de presupuestos
window.location.href = '/presupuesto/edit/' + response.id;
});
} else {
Swal.fire({
title: window.languageBundle.get(['presupuesto.reimprimir.error.title']) || 'Error al reimprimir',
text: response.message || window.languageBundle.get(['presupuesto.reimprimir.error.internal']) || 'Ha ocurrido un error al reimprimir el presupuesto.',
icon: 'error',
confirmButtonText: window.languageBundle.get(['presupuesto.reimprimir.aceptar']) || 'Aceptar',
buttonsStyling: false,
customClass: {
confirmButton: 'btn btn-secondary' // clases para el botón confirmar
},
});
}
},
error: function (xhr) {
Swal.fire({
title: window.languageBundle.get(['presupuesto.reimprimir.error.title']) || 'Error al reimprimir',
text: xhr.responseJSON?.message || window.languageBundle.get(['presupuesto.reimprimir.error.internal']) || 'Ha ocurrido un error al reimprimir el presupuesto.',
icon: 'error',
confirmButtonText: window.languageBundle.get(['presupuesto.reimprimir.aceptar']) || 'Aceptar',
buttonsStyling: false,
customClass: {
confirmButton: 'btn btn-secondary' // clases para el botón confirmar
},
});
}
});
};
});
}

View File

@ -31,7 +31,7 @@ $(() => {
e.preventDefault(); e.preventDefault();
// obtén el id de donde lo tengas (data-attr o variable global) // obtén el id de donde lo tengas (data-attr o variable global)
const id = $('#presupuesto-id').val(); const id = $('#presupuesto_id').val();
const url = `/api/pdf/presupuesto/${id}?mode=download`; const url = `/api/pdf/presupuesto/${id}?mode=download`;
@ -47,7 +47,7 @@ $(() => {
$('.add-cart-btn').on('click', async () => { $('.add-cart-btn').on('click', async () => {
const presupuestoId = $('#presupuesto-id').val(); const presupuestoId = $('#presupuesto_id').val();
const res = await $.ajax({ const res = await $.ajax({
url: `/cart/add/${presupuestoId}`, url: `/cart/add/${presupuestoId}`,
method: 'POST', method: 'POST',

View File

@ -13,6 +13,12 @@
<span th:text="#{app.imprimir}">Imprimir</span> <span th:text="#{app.imprimir}">Imprimir</span>
</button> </button>
<button th:if="${appMode == 'edit'}" type="button"
class="btn btn-secondary d-flex align-items-center mx-2 duplicar-btn">
<i class="ri-file-copy-2-line me-2"></i>
<span th:text="#{presupuesto.duplicar}">Duplicar</span>
</button>
<button th:if="${appMode == 'add' or appMode == 'edit'}" type="button" <button th:if="${appMode == 'add' or appMode == 'edit'}" type="button"
class="btn btn-secondary d-flex align-items-center mx-2 add-cart-btn"> class="btn btn-secondary d-flex align-items-center mx-2 add-cart-btn">
<i class="ri-shopping-cart-line me-2"></i> <i class="ri-shopping-cart-line me-2"></i>

View File

@ -16,6 +16,8 @@
<div class="col-9 mx-auto mt-4"> <div class="col-9 mx-auto mt-4">
<h5 id="resumen-titulo" class="text-center"></h5> <h5 id="resumen-titulo" class="text-center"></h5>
<h6 th:if="${presupuesto.isReimpresion}" th:text="#{presupuesto.reimpresion}"
class="text-uppercase bg-danger text-white text-center">REIMPRESION</h6>
<table id="resumen-tabla-final" class="table table-borderless table-striped mt-3" <table id="resumen-tabla-final" class="table table-borderless table-striped mt-3"
th:data-currency="#{app.currency}"> th:data-currency="#{app.currency}">
<thead> <thead>

View File

@ -4,6 +4,8 @@
<div class="d-flex"> <div class="d-flex">
<div class="flex-grow-1"> <div class="flex-grow-1">
<h5 th:text="#{presupuesto.resumen-presupuesto}">Resumen presupuesto</h5> <h5 th:text="#{presupuesto.resumen-presupuesto}">Resumen presupuesto</h5>
<h6 th:if="${presupuesto.isReimpresion}" th:text="#{presupuesto.reimpresion}"
class="text-uppercase bg-danger text-white text-center">REIMPRESION</h6>
</div> </div>
</div> </div>
</div> </div>

View File

@ -42,18 +42,21 @@
<div class="container-fluid"> <div class="container-fluid">
<input type="hidden" id="presupuesto-id" th:value="${presupuesto.id}" /> <input type="hidden" id="presupuesto_id" th:value="${presupuesto.id}" />
<div class="row" id="card presupuesto-row animate-fadeInUpBounce"> <div class="row" id="card presupuesto-row animate-fadeInUpBounce">
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h4 class="card-title mb-0 text-uppercase" th:text="${resumen.titulo}">Resumen del <h4 id="titulo" class="card-title mb-0 text-uppercase" th:text="${resumen.titulo}">Resumen del
presupuesto</h4> presupuesto</h4>
</div> </div>
<div class="card-body"> <div class="card-body">
<div th:if="${presupuesto.isReimpresion}" class="row">
<h6 th:text="#{presupuesto.reimpresion}" class="bg-danger py-2 text-center text-white text-uppercase">REIMPRESION</h6>
</div>
<div class="card col-12 col-sm-9 mx-auto"> <div class="card col-12 col-sm-9 mx-auto">
<h5 id="resumen-titulo" class="text-center"></h5> <h5 id="resumen-titulo" class="text-center"></h5>
@ -161,9 +164,15 @@
</button> </button>
<button type="button" <button type="button"
class="btn btn-secondary d-flex align-items-center mx-2 add-cart-btn"> class="btn btn-secondary d-flex align-items-center mx-2 duplicar-btn">
<i class="ri-shopping-cart-line me-2"></i> <i class="ri-file-copy-2-line me-2"></i>
<span th:text="#{presupuesto.add-to-cart}">Añadir a la cesta</span> <span th:text="#{presupuesto.duplicar}">Duplicar</span>
</button>
<button type="button"
class="btn btn-secondary d-flex align-items-center mx-2 reimprimir-btn">
<i class="ri-printer-line me-2"></i>
<span th:text="#{presupuesto.reimprimir}">Reimprimir</span>
</button> </button>
</div> </div>
@ -223,6 +232,7 @@
</div> </div>
<script type="module" th:src="@{/assets/js/pages/imprimelibros/presupuestos/resumen-view.js}"></script> <script type="module" th:src="@{/assets/js/pages/imprimelibros/presupuestos/resumen-view.js}"></script>
<script type="module" th:src="@{/assets/js/pages/imprimelibros/presupuestos/duplicate-reprint.js}"></script>
</th:block> </th:block>
</body> </body>

View File

@ -89,6 +89,8 @@
th:src="@{/assets/libs/quill/quill.min.js}"></script> th:src="@{/assets/libs/quill/quill.min.js}"></script>
<script sec:authorize="isAuthenticated() and hasAnyRole('SUPERADMIN','ADMIN')" <script sec:authorize="isAuthenticated() and hasAnyRole('SUPERADMIN','ADMIN')"
th:src="@{/assets/js/pages/imprimelibros/presupuestador/text-editor.js}"></script> th:src="@{/assets/js/pages/imprimelibros/presupuestador/text-editor.js}"></script>
<script type="module" th:src="@{/assets/js/pages/imprimelibros/presupuestos/duplicate-reprint.js}"></script>
</th:block> </th:block>
</body> </body>