mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-21 08:10:20 +00:00
implementado duplicar en la lista
This commit is contained in:
@ -492,7 +492,8 @@ public class PresupuestoController {
|
||||
String sessionId = request.getSession(true).getId();
|
||||
String ip = IpUtils.getClientIp(request);
|
||||
|
||||
var resumen = presupuestoService.getResumen(p, serviciosList, datosMaquetacion, datosMarcapaginas, save, mode, locale, sessionId, ip);
|
||||
var resumen = presupuestoService.getResumen(p, serviciosList, datosMaquetacion, datosMarcapaginas, save, mode,
|
||||
locale, sessionId, ip);
|
||||
|
||||
return ResponseEntity.ok(resumen);
|
||||
}
|
||||
@ -519,7 +520,18 @@ public class PresupuestoController {
|
||||
"presupuesto.add.cancel",
|
||||
"presupuesto.add.select-client",
|
||||
"presupuesto.add.error.options",
|
||||
"presupuesto.add.error.options-client");
|
||||
"presupuesto.add.error.options-client",
|
||||
"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"
|
||||
);
|
||||
|
||||
Map<String, String> translations = translationService.getTranslations(locale, keys);
|
||||
model.addAttribute("languageBundle", translations);
|
||||
@ -562,15 +574,15 @@ public class PresupuestoController {
|
||||
return "redirect:/presupuesto";
|
||||
}
|
||||
|
||||
if(presupuestoOpt.get().getEstado() == Presupuesto.Estado.aceptado){
|
||||
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);
|
||||
|
||||
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";
|
||||
@ -595,6 +607,7 @@ public class PresupuestoController {
|
||||
model.addAttribute("appMode", "edit");
|
||||
}
|
||||
model.addAttribute("id", presupuestoOpt.get().getId());
|
||||
model.addAttribute("presupuesto", presupuestoOpt.get());
|
||||
return "imprimelibros/presupuestos/presupuesto-form";
|
||||
}
|
||||
|
||||
@ -780,4 +793,24 @@ public class PresupuestoController {
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/comentario")
|
||||
@ResponseBody
|
||||
public String actualizarComentario(@PathVariable Long id,
|
||||
@RequestParam String comentario) {
|
||||
presupuestoService.updateComentario(id, comentario);
|
||||
return "OK";
|
||||
}
|
||||
|
||||
@PostMapping("/duplicar/{id}")
|
||||
@ResponseBody
|
||||
public Map<String, Object> duplicarPresupuesto(
|
||||
@PathVariable Long id,
|
||||
@RequestParam(name = "titulo", defaultValue = "") String titulo) {
|
||||
|
||||
Long entity = presupuestoService.duplicarPresupuesto(id, titulo);
|
||||
|
||||
return Map.of("id", entity);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ public class PresupuestoDatatableService {
|
||||
.addIf(publico, "ciudad", Presupuesto::getCiudad)
|
||||
.add("updatedAt", p -> formatDate(p.getUpdatedAt(), locale))
|
||||
.addIf(!publico, "user", p -> p.getUser() != null ? p.getUser().getFullName() : "")
|
||||
.add("actions", this::generarBotones)
|
||||
.add("actions", p -> generarBotones(p, locale))
|
||||
.where(base)
|
||||
.toJson(count);
|
||||
}
|
||||
@ -115,18 +115,27 @@ public class PresupuestoDatatableService {
|
||||
return df.format(instant);
|
||||
}
|
||||
|
||||
private String generarBotones(Presupuesto p) {
|
||||
private String generarBotones(Presupuesto p, Locale locale) {
|
||||
boolean borrador = p.getEstado() == Presupuesto.Estado.borrador;
|
||||
String id = String.valueOf(p.getId());
|
||||
String editBtn = "<a href=\"javascript:void(0);\" data-id=\"" + id + "\" class=\"link-success btn-edit-" +
|
||||
(p.getOrigen().equals(Presupuesto.Origen.publico) ? "anonimo" : "privado") + " fs-15\"><i class=\"ri-" +
|
||||
(p.getOrigen().equals(Presupuesto.Origen.publico) || p.getEstado() == Presupuesto.Estado.aceptado ? "eye" : "pencil") + "-line\"></i></a>";
|
||||
(p.getOrigen().equals(Presupuesto.Origen.publico) || p.getEstado() == Presupuesto.Estado.aceptado ? "eye" : "pencil") + "-line\" " +
|
||||
"data-bs-toggle=\"tooltip\" data-bs-placement=\"top\" title=\"" +
|
||||
msg(p.getEstado() == Presupuesto.Estado.aceptado ? "presupuesto.ver" : "presupuesto.editar", locale) + "\"></i></a>";
|
||||
|
||||
String duplicarBtn = !p.getOrigen().equals(Presupuesto.Origen.publico) ? "<a href=\"javascript:void(0);\" data-id=\"" + id
|
||||
+ "\" class=\"link-success btn-duplicate-privado fs-15\"><i class=\"ri-file-copy-2-line\" data-bs-toggle=\"tooltip\" data-bs-placement=\"top\" title=\"" +
|
||||
msg("presupuesto.duplicar", locale) + "\"></i></a>" : "";
|
||||
String reimprimirBtn = p.getEstado() == Presupuesto.Estado.aceptado && !p.getOrigen().equals(Presupuesto.Origen.publico) ? "<a href=\"javascript:void(0);\" data-id=\"" + id
|
||||
+ "\" class=\"link-success btn-reprint-privado fs-15\"><i class=\"ri-printer-line\" data-bs-toggle=\"tooltip\" data-bs-placement=\"top\" title=\"" +
|
||||
msg("presupuesto.reimprimir", locale) + "\"></i></a>" : "";
|
||||
String deleteBtn = borrador ? "<a href=\"javascript:void(0);\" data-id=\"" + id
|
||||
+ "\" class=\"link-danger btn-delete-"
|
||||
+ (p.getOrigen().equals(Presupuesto.Origen.publico) ? "anonimo" : "privado")
|
||||
+ " fs-15\"><i class=\"ri-delete-bin-5-line\"></i></a>" : "";
|
||||
+ " fs-15\"><i class=\"ri-delete-bin-5-line\" data-bs-toggle=\"tooltip\" data-bs-placement=\"top\" title=\"" +
|
||||
msg("presupuesto.borrar", locale) + "\"></i></a>" : "";
|
||||
|
||||
return "<div class=\"hstack gap-3 flex-wrap\">" + editBtn + deleteBtn + "</div>";
|
||||
return "<div class=\"hstack gap-3 flex-wrap\">" + editBtn + duplicarBtn + reimprimirBtn + deleteBtn + "</div>";
|
||||
}
|
||||
}
|
||||
|
||||
@ -1361,6 +1361,29 @@ public class PresupuestoService {
|
||||
return presupuestoRepository.findById(id).orElse(null);
|
||||
}
|
||||
|
||||
public void updateComentario(Long presupuestoId, String comentario) {
|
||||
Presupuesto presupuesto = presupuestoRepository.findById(presupuestoId).orElse(null);
|
||||
if (presupuesto != null) {
|
||||
presupuesto.setComentario(comentario);
|
||||
presupuestoRepository.saveAndFlush(presupuesto);
|
||||
}
|
||||
}
|
||||
|
||||
public long duplicarPresupuesto(Long presupuestoId, String titulo) {
|
||||
|
||||
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(titulo != null && !titulo.isEmpty() ? titulo : "[D] " + presupuesto.getTitulo());
|
||||
nuevo.setIsReimpresion(false);
|
||||
presupuestoRepository.saveAndFlush(nuevo);
|
||||
return nuevo.getId();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// =======================================================================
|
||||
// Métodos privados
|
||||
// =======================================================================
|
||||
|
||||
@ -11,6 +11,11 @@ presupuesto.add-to-presupuesto=Añadir al presupuesto
|
||||
presupuesto.calcular=Calcular
|
||||
presupuesto.add=Añadir presupuesto
|
||||
presupuesto.guardar=Guardar
|
||||
presupuesto.duplicar=Duplicar
|
||||
presupuesto.reimprimir=Reimprimir
|
||||
presupuesto.editar=Editar
|
||||
presupuesto.ver=Ver
|
||||
presupuesto.borrar=Eliminar
|
||||
presupuesto.add-to-cart=Añadir a la cesta
|
||||
|
||||
presupuesto.nav.presupuestos-cliente=Presupuestos cliente
|
||||
@ -296,6 +301,18 @@ presupuesto.error.delete-permission-denied=No se puede eliminar: permiso denegad
|
||||
presupuesto.error.delete-not-found=No se puede eliminar: presupuesto no encontrado.
|
||||
presupuesto.error.delete-not-draft=Solo se pueden eliminar presupuestos en estado Borrador.
|
||||
|
||||
# Mensajes de duplicar presupuesto
|
||||
presupuesto.duplicar.title=Duplicar presupuesto
|
||||
presupuesto.duplicar.confirm=Si, DUPLICAR
|
||||
presupuesto.duplicar.cancelar=Cancelar
|
||||
presupuesto.duplicar.text=¿Está seguro de que desea duplicar este presupuesto?<br>Se creará una copia exacta del mismo en estado Borrador con el título introducido a continuación.
|
||||
presupuesto.duplicar.required=El título es obligatorio.
|
||||
presupuesto.duplicar.success.title=Presupuesto duplicado
|
||||
presupuesto.duplicar.success.text=El presupuesto ha sido duplicado con éxito.
|
||||
presupuesto.duplicar.aceptar=Aceptar.
|
||||
presupuesto.duplicar.error.title=Error al duplicar presupuesto
|
||||
presupuesto.duplicar.error.internal=No se puede duplicar: error interno.
|
||||
|
||||
# Añadir presupuesto
|
||||
presupuesto.add.tipo=Tipo de presupuesto
|
||||
presupuesto.add.anonimo=Anónimo
|
||||
|
||||
@ -42,7 +42,41 @@ $(() => {
|
||||
]
|
||||
}
|
||||
}
|
||||
new Quill(item, snowEditorData);
|
||||
var quill = new Quill(item, snowEditorData);
|
||||
|
||||
var initialContent = item.dataset.contenido || "";
|
||||
// Contenido inicial desde Thymeleaf
|
||||
var initialContent = item.dataset.contenido || "";
|
||||
if (initialContent) {
|
||||
if(initialContent.trim() !== "" && initialContent.trim() !== "<p><br></p>")
|
||||
$('.badge-comentario').removeClass('d-none');
|
||||
quill.clipboard.dangerouslyPasteHTML(initialContent);
|
||||
}
|
||||
|
||||
quill.root.addEventListener("blur", function () {
|
||||
|
||||
let contenido = quill.root.innerHTML;
|
||||
if(contenido.trim() !== "" && contenido.trim() !== "<p><br></p>"){
|
||||
$('.badge-comentario').removeClass('d-none');
|
||||
} else {
|
||||
$('.badge-comentario').addClass('d-none');
|
||||
}
|
||||
if ($('#presupuesto_id').length > 0 && $('#presupuesto_id').val() !== "") {
|
||||
|
||||
$.ajax({
|
||||
url: "/presupuesto/" + $('#presupuesto_id').val() + "/comentario",
|
||||
method: "POST",
|
||||
data: {
|
||||
comentario: contenido
|
||||
},
|
||||
success: function () {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
@ -402,8 +402,6 @@ export default class PresupuestoWizard {
|
||||
...this.#getInteriorData(),
|
||||
...this.#getCubiertaData(),
|
||||
selectedTirada: this.formData.selectedTirada
|
||||
|
||||
|
||||
};
|
||||
|
||||
const sobrecubierta = data.sobrecubierta;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { preguntarTipoPresupuesto } from './presupuesto-utils.js';
|
||||
import { preguntarTipoPresupuesto, duplicar } from './presupuesto-utils.js';
|
||||
|
||||
(() => {
|
||||
// si jQuery está cargado, añade CSRF a AJAX
|
||||
@ -200,6 +200,17 @@ import { preguntarTipoPresupuesto } from './presupuesto-utils.js';
|
||||
}
|
||||
});
|
||||
|
||||
$('#presupuestos-clientes-datatable').on('click', '.btn-duplicate-privado', function (e) {
|
||||
|
||||
e.preventDefault();
|
||||
const id = $(this).data('id');
|
||||
let data = table_clientes.row($(this).parents('tr')).data();
|
||||
const tituloOriginal = data.titulo;
|
||||
|
||||
duplicar(id, tituloOriginal);
|
||||
});
|
||||
|
||||
|
||||
$('#presupuestos-clientes-datatable').on('click', '.btn-delete-privado', function (e) {
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
@ -75,3 +75,94 @@ export async function preguntarTipoPresupuesto() {
|
||||
}
|
||||
}).then((r) => (r.isConfirmed ? r.value : null));
|
||||
}
|
||||
|
||||
export function duplicar(id, titulo) {
|
||||
|
||||
// swal with input
|
||||
Swal.fire({
|
||||
title: window.languageBundle.get(['presupuesto.duplicar.title']) || 'Duplicar presupuesto',
|
||||
html: window.languageBundle.get(['presupuesto.duplicar.text']) || `¿Deseas duplicar el presupuesto "${titulo}"?`,
|
||||
icon: 'question',
|
||||
input: 'text',
|
||||
inputValue: `[D] ${titulo}`,
|
||||
showCancelButton: true,
|
||||
confirmButtonText: window.languageBundle.get(['presupuesto.duplicar.confirm']) || 'Sí, duplicar',
|
||||
cancelButtonText: window.languageBundle.get(['presupuesto.duplicar.cancelar']) || 'Cancelar',
|
||||
buttonsStyling: false,
|
||||
customClass: {
|
||||
confirmButton: 'btn btn-secondary me-2', // clases para el botón confirmar
|
||||
cancelButton: 'btn btn-light' // clases para cancelar
|
||||
},
|
||||
inputValidator: (value) => {
|
||||
if (!value) {
|
||||
return window.languageBundle.get(['presupuesto.duplicar.required']) || 'El título no puede estar vacío';
|
||||
}
|
||||
},
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
|
||||
const tituloNuevo = result.value;
|
||||
|
||||
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/duplicar/${id}`,
|
||||
data: {
|
||||
titulo: tituloNuevo,
|
||||
},
|
||||
method: 'POST',
|
||||
success: function (response) {
|
||||
|
||||
if (response.id && response.id > 0) {
|
||||
|
||||
Swal.fire({
|
||||
title: window.languageBundle.get(['presupuesto.duplicar.success.title']) || 'Presupuesto duplicado',
|
||||
text: window.languageBundle.get(['presupuesto.duplicar.success.text']) || `El presupuesto "${titulo}" ha sido duplicado correctamente.`,
|
||||
icon: 'success',
|
||||
confirmButtonText: window.languageBundle.get(['presupuesto.duplicar.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.duplicar.error.title']) || 'Error al duplicar',
|
||||
text: response.message || window.languageBundle.get(['presupuesto.duplicar.error.internal']) || 'Ha ocurrido un error al duplicar el presupuesto.',
|
||||
icon: 'error',
|
||||
confirmButtonText: window.languageBundle.get(['presupuesto.duplicar.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.duplicar.error.title']) || 'Error al duplicar',
|
||||
text: xhr.responseJSON?.message || window.languageBundle.get(['presupuesto.duplicar.error.internal']) || 'Ha ocurrido un error al duplicar el presupuesto.',
|
||||
icon: 'error',
|
||||
confirmButtonText: window.languageBundle.get(['presupuesto.duplicar.aceptar']) || 'Aceptar',
|
||||
buttonsStyling: false,
|
||||
customClass: {
|
||||
confirmButton: 'btn btn-secondary' // clases para el botón confirmar
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@ -77,7 +77,9 @@
|
||||
</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><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>
|
||||
@ -165,6 +167,38 @@
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<div sec:authorize="isAuthenticated() and hasAnyRole('SUPERADMIN','ADMIN')">
|
||||
<div class="accordion lefticon-accordion custom-accordionwithicon accordion-border-box mt-3"
|
||||
id="accordionlefticon">
|
||||
<div class="accordion-item material-shadow">
|
||||
<h2 class="accordion-header" id="accordionComentario">
|
||||
<button class="accordion-button collapsed" type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#accor_accordionComentario" aria-expanded="false"
|
||||
aria-controls="accor_accordionComentario">
|
||||
<span
|
||||
th:text="#{presupuesto.comentario-administrador}">Comentario</span>
|
||||
<span class="d-none badge badge-comentario bg-danger ms-1">!</span>
|
||||
</button>
|
||||
|
||||
</h2>
|
||||
|
||||
<div id="accor_accordionComentario" class="accordion-collapse collapse"
|
||||
aria-labelledby="accordionComentario"
|
||||
data-bs-parent="#accordionlefticon">
|
||||
<div class="accordion-body">
|
||||
<div class="snow-editor" id="comentario" name="comentario"
|
||||
th:attr="data-contenido=${presupuesto.comentario} "
|
||||
style=" height: 300px;">
|
||||
</div> <!-- end Snow-editor-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -147,16 +147,19 @@
|
||||
<h2 class="accordion-header" id="accordionComentario">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
||||
data-bs-target="#accor_accordionComentario" aria-expanded="false"
|
||||
aria-controls="accor_accordionComentario"
|
||||
th:text="#{presupuesto.comentario-administrador}">Comentario
|
||||
aria-controls="accor_accordionComentario">
|
||||
<span th:text="#{presupuesto.comentario-administrador}">Comentario</span>
|
||||
<span class="d-none badge badge-comentario bg-danger ms-1">!</span>
|
||||
</button>
|
||||
|
||||
</h2>
|
||||
|
||||
<div id="accor_accordionComentario" class="accordion-collapse collapse"
|
||||
aria-labelledby="accordionComentario" data-bs-parent="#accordionlefticon">
|
||||
<div class="accordion-body">
|
||||
<div class="snow-editor" name="comentario"
|
||||
th:text="@{presupuesto.comentario}" style=" height: 300px;">
|
||||
<div class="snow-editor" id="comentario" name="comentario"
|
||||
th:attr="data-contenido=${presupuesto.comentario} "
|
||||
style=" height: 300px;">
|
||||
</div> <!-- end Snow-editor-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
<th scope="col" th:text="#{presupuesto.tabla.estado}">Estado</th>
|
||||
<th scope="col" th:text="#{presupuesto.tabla.total-iva}">Total con IVA</th>
|
||||
<th scope="col" th:text="#{presupuesto.tabla.updated-at}">Actualizado el</th>
|
||||
<th scope="col" th:text="#{presupuesto.tabla.acciones}">Acciones</th>
|
||||
<th style="min-width: 100px;" scope="col" th:text="#{presupuesto.tabla.acciones}">Acciones</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><input type="text" class="form-control form-control-sm presupuesto-filter" data-col="id" /></th>
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
<th scope="col" th:text="#{presupuesto.tabla.estado}">Estado</th>
|
||||
<th scope="col" th:text="#{presupuesto.tabla.total-iva}">Total con IVA</th>
|
||||
<th scope="col" th:text="#{presupuesto.tabla.updated-at}">Actualizado el</th>
|
||||
<th scope="col" th:text="#{presupuesto.tabla.acciones}">Acciones</th>
|
||||
<th style="min-width: 100px;" scope="col" th:text="#{presupuesto.tabla.acciones}">Acciones</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><input type="text" class="form-control form-control-sm presupuesto-filter" data-col="id" /></th>
|
||||
|
||||
Reference in New Issue
Block a user