terminado margenes presupuesto e incluido en la api

This commit is contained in:
2025-10-02 20:50:39 +02:00
parent 460d2cfc01
commit 1e24065fb7
18 changed files with 663 additions and 101 deletions

View File

@ -1,6 +1,9 @@
margenes-presupuesto.titulo=Márgenes de presupuesto
margenes-presupuesto.breadcrumb=Márgenes de presupuesto
margenes-presupuesto.add=Añadir
margenes-presupuesto.nuevo=Nuevo margen
margenes-presupuesto.editar=Editar margen
margenes-presupuesto.eliminar=Eliminar
margenes-presupuesto.tabla.id=ID
margenes-presupuesto.tabla.tipo_encuadernacion=Tipo encuadernación
@ -11,4 +14,22 @@ margenes-presupuesto.tabla.margen_minimo=Margen Mín.
margenes-presupuesto.tabla.margen_maximo=Margen Máx.
margenes-presupuesto.tabla.acciones=Acciones
margenes-presupuesto.todos=Todos
margenes-presupuesto.form.tipo_encuadernacion=Tipo de encuadernación
margenes-presupuesto.form.tipo_cubierta=Tipo de cubierta
margenes-presupuesto.form.tirada_minima=Tirada mínima
margenes-presupuesto.form.tirada_maxima=Tirada máxima
margenes-presupuesto.form.margen_minimo=Margen mínimo (%)
margenes-presupuesto.form.margen_maximo=Margen máximo (%)
margenes-presupuesto.todos=Todos
margenes-presupuesto.delete.title=Eliminar margen
margenes-presupuesto.delete.button=Si, ELIMINAR
margenes-presupuesto.delete.text=¿Está seguro de que desea eliminar este margen?<br>Esta acción no se puede deshacer.
margenes-presupuesto.delete.ok.title=Margen eliminado
margenes-presupuesto.delete.ok.text=El margen ha sido eliminado con éxito.
margenes-presupuesto.exito.eliminado=Margen eliminado con éxito.
margenes-presupuesto.error.delete-internal-error=No se puede eliminar: error interno.
margenes-presupuesto.error.delete-not-found=No se puede eliminar: margen no encontrado.

View File

@ -42,6 +42,7 @@ presupuesto.grapado-descripcion=Grapado (entre 12 y 40 páginas)
presupuesto.espiral=Espiral
presupuesto.espiral-descripcion=Espiral (a partir de 20 páginas)
presupuesto.wire-o=Wire-O
presupuesto.wireo=Wire-O
presupuesto.wire-o-descripcion=Wire-O (a partir de 20 páginas)
presupuesto.encuadernacion-descripcion=Seleccione la encuadernación del libro
presupuesto.continuar-interior=Continuar a diseño interior

View File

@ -8,4 +8,5 @@ validation.unique=El valor ya existe y debe ser único
validation.email=El correo electrónico no es válido
validation.range.overlaps=El rango se solapa con otro existente.
validation.range.invalid=El valor máximo debe ser mayor o igual que el mínimo.
validation.range.invalid2=Rango no válido.
validation.db=Error de base de datos: {message}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 925 KiB

View File

@ -18,18 +18,20 @@
return;
}
new DataTable('#margenes-datatable', {
const table = new DataTable('#margenes-datatable', {
processing: true,
serverSide: true,
orderCellsTop: true,
stateSave: true,
pageLength: 50,
language: { url: '/assets/libs/datatables/i18n/' + language + '.json' },
responsive: true,
ajax: {
url: '/configuracion/margenes-presupuestos/datatable',
url: '/configuracion/margenes-presupuesto/datatable',
method: 'GET',
data: function (d) {
// filtros si los necesitas
d.f_encuadernacion = $('#search-encuadernacion').val() || ''; // 'USER' | 'ADMIN' | 'SUPERADMIN' | ''
d.f_cubierta = $('#search-cubierta').val() || ''; // 'true' | 'false' | ''
}
},
order: [[0, 'asc']],
@ -45,4 +47,126 @@
],
columnDefs: [{ targets: -1, orderable: false, searchable: false }]
});
table.on("keyup", ".margenes-presupuesto-filter", function () {
const colName = $(this).data("col");
const colIndex = table.settings()[0].aoColumns.findIndex(c => c.name === colName);
if (colIndex >= 0) {
table.column(colIndex).search(this.value).draw();
}
});
table.on("change", ".margenes-presupuesto-select-filter", function () {
table.draw();
});
const modalEl = document.getElementById('margenesPresupuestoFormModal');
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
// Abrir "Crear"
$('#addButton').on('click', (e) => {
e.preventDefault();
$.get('/configuracion/margenes-presupuesto/form', function (html) {
$('#margenesPresupuestoModalBody').html(html);
const title = $('#margenesPresupuestoModalBody #margenesPresupuestoForm').data('add');
$('#margenesPresupuestoModal .modal-title').text(title);
modal.show();
});
});
// Abrir "Editar"
$(document).on('click', '.btn-edit-margen', function (e) {
e.preventDefault();
const id = $(this).data('id');
$.get('/configuracion/margenes-presupuesto/form', { id }, function (html) {
$('#margenesPresupuestoModalBody').html(html);
const title = $('#margenesPresupuestoModalBody #margenesPresupuestoForm').data('edit');
$('#margenesPresupuestoModal .modal-title').text(title);
modal.show();
});
});
// Botón "Eliminar"
$(document).on('click', '.btn-delete-margen', function (e) {
e.preventDefault();
const id = $(this).data('id');
Swal.fire({
title: window.languageBundle.get(['margenes-presupuesto.delete.title']) || 'Eliminar margen',
html: window.languageBundle.get(['margenes-presupuesto.delete.text']) || 'Esta acción no se puede deshacer.',
icon: 'warning',
showCancelButton: true,
buttonsStyling: false,
customClass: {
confirmButton: 'btn btn-danger w-xs mt-2',
cancelButton: 'btn btn-light w-xs mt-2'
},
confirmButtonText: window.languageBundle.get(['margenes-presupuesto.delete.button']) || 'Eliminar',
cancelButtonText: window.languageBundle.get(['app.cancelar']) || 'Cancelar',
}).then((result) => {
if (!result.isConfirmed) return;
$.ajax({
url: '/configuracion/margenes-presupuesto/' + id,
type: 'DELETE',
success: function () {
Swal.fire({
icon: 'success', title: window.languageBundle.get(['margenes-presupuesto.delete.ok.title']) || 'Eliminado',
text: window.languageBundle.get(['margenes-presupuesto.delete.ok.text']) || 'El margen ha sido eliminado con éxito.',
showConfirmButton: true,
customClass: {
confirmButton: 'btn btn-secondary w-xs mt-2',
},
});
$('#margenes-datatable').DataTable().ajax.reload(null, false);
},
error: function (xhr) {
// usa el mensaje del backend; fallback genérico por si no llega JSON
const msg = (xhr.responseJSON && xhr.responseJSON.message)
|| 'Error al eliminar el usuario.';
Swal.fire({ icon: 'error', title: 'No se pudo eliminar', text: msg });
}
});
});
});
// Submit del form en el modal
$(document).on('submit', '#margenesPresupuestoForm', function (e) {
e.preventDefault();
const $form = $(this);
$.ajax({
url: $form.attr('action'),
type: 'POST', // PUT simulado via _method
data: $form.serialize(),
dataType: 'html',
success: function (html) {
// Si por cualquier motivo llega 200 con fragmento, lo insertamos igual
if (typeof html === 'string' && html.indexOf('id="margenesPresupuestoForm"') !== -1 && html.indexOf('<html') === -1) {
$('#margenesPresupuestoModalBody').html(html);
const isEdit = $('#margenesPresupuestoModalBody #margenesPresupuestoForm input[name="_method"][value="PUT"]').length > 0;
const title = $('#margenesPresupuestoModalBody #margenesPresupuestoForm').data(isEdit ? 'edit' : 'add');
$('#margenesPresupuestoModal .modal-title').text(title);
return;
}
// Éxito real: cerrar y recargar tabla
modal.hide();
table.ajax.reload(null, false);
},
error: function (xhr) {
// Con 422 devolvemos el fragmento con errores aquí
if (xhr.status === 422 && xhr.responseText) {
$('#margenesPresupuestoModalBody').html(xhr.responseText);
const isEdit = $('#margenesPresupuestoModalBody #margenesPresupuestoForm input[name="_method"][value="PUT"]').length > 0;
const title = $('#margenesPresupuestoModalBody #margenesPresupuestoForm').data(isEdit ? 'edit' : 'add');
$('#margenesPresupuestoModal .modal-title').text(title);
return;
}
// Fallback
$('#margenesPresupuestoModalBody').html('<div class="p-3 text-danger">Error inesperado.</div>');
}
});
});
})();

View File

@ -39,7 +39,7 @@ $(() => {
columnDefs: [{ targets: -1, orderable: false, searchable: false }]
});
table.on("keyup", ".user-filter", function () {
table.on("keyup", ".user-filter", function() {
const colName = $(this).data("col");
const colIndex = table.settings()[0].aoColumns.findIndex(c => c.name === colName);
@ -48,7 +48,7 @@ $(() => {
}
});
table.on("change", ".user-filter-select", function () {
table.on("change", ".user-filter-select", function() {
table.draw();
});

View File

@ -0,0 +1,67 @@
<div th:fragment="margenesPresupuestoForm">
<form id="margenesPresupuestoForm" novalidate th:action="${action}" th:object="${margenPresupuesto}" method="post" th:data-add="#{margenesPresupuesto.add}"
th:data-edit="#{margenesPresupuesto.editar}">
<input type="hidden" name="_method" value="PUT" th:if="${margenPresupuesto.id != null}" />
<div th:if="${#fields.hasGlobalErrors()}" class="alert alert-danger">
<div th:each="e : ${#fields.globalErrors()}" th:text="${e}"></div>
</div>
<div class="form-group">
<label th:text="#{margenes-presupuesto.form.tipo_encuadernacion}" for="tipo_encuadernacion">Tipo de Encuadernación</label>
<select class="form-control" id="tipo_encuadernacion" th:field="*{tipoEncuadernacion}" required
th:classappend="${#fields.hasErrors('tipoEncuadernacion')} ? ' is-invalid'">
<option value="fresado" th:text="#{presupuesto.fresado}" selected>Fresado</option>
<option value="cosido" th:text="#{presupuesto.cosido}">Cosido</option>
<option value="espiral" th:text="#{presupuesto.espiral}">Espiral</option>
<option value="wireo" th:text="#{presupuesto.wireo}">Wire-O</option>
<option value="grapado" th:text="#{presupuesto.grapado}">Grapado</option>
</select>
<div class="invalid-feedback" th:if="${#fields.hasErrors('tipoEncuadernacion')}" th:errors="*{tipoEncuadernacion}">Error</div>
</div>
<div class="form-group">
<label th:text="#{margenes-presupuesto.form.tipo_cubierta}" for="tipo_cubierta">Tipo de Cubierta</label>
<select class="form-control" id="tipo_cubierta" th:field="*{tipoCubierta}" required
th:classappend="${#fields.hasErrors('tipoCubierta')} ? ' is-invalid'">
<option value="tapaBlanda" th:text="#{presupuesto.tapaBlanda}" selected>Tapa Blanda</option>
<option value="tapaDura" th:text="#{presupuesto.tapaDura}">Tapa Dura</option>
<option value="tapaDuraLomoRedondo" th:text="#{presupuesto.tapaDuraLomoRedondo}">Tapa Dura Lomo Redondo</option>
</select>
<div class="invalid-feedback" th:if="${#fields.hasErrors('tipoCubierta')}" th:errors="*{tipoCubierta}">Error</div>
</div>
<div class="form-group">
<label th:text="#{margenes-presupuesto.form.tirada_minima}" for="tirada_minima">Tirada Mínima</label>
<input type="number" class="form-control" id="tirada_minima" th:field="*{tiradaMin}" min="1"
th:classappend="${#fields.hasErrors('tiradaMin')} ? ' is-invalid'" required>
<div class="invalid-feedback" th:if="${#fields.hasErrors('tiradaMin')}" th:errors="*{tiradaMin}">Error</div>
</div>
<div class="form-group">
<label th:text="#{margenes-presupuesto.form.tirada_maxima}" for="tirada_maxima">Tirada Máxima</label>
<input type="number" class="form-control" id="tirada_maxima" th:field="*{tiradaMax}" min="1"
th:classappend="${#fields.hasErrors('tiradaMax')} ? ' is-invalid'" required>
<div class="invalid-feedback" th:if="${#fields.hasErrors('tiradaMax')}" th:errors="*{tiradaMax}">Error</div>
</div>
<div class="form-group">
<label th:text="#{margenes-presupuesto.form.margen_maximo}" for="margen_maximo">Margen Máximo (%)</label>
<input type="number" class="form-control" id="margen_maximo" th:field="*{margenMax}" min="0" max="100" step="0.01"
th:classappend="${#fields.hasErrors('margenMax')} ? ' is-invalid'" required>
<div class="invalid-feedback" th:if="${#fields.hasErrors('margenMax')}" th:errors="*{margenMax}">Error</div>
</div>
<div class="form-group">
<label th:text="#{margenes-presupuesto.form.margen_minimo}" for="margen_minimo">Margen Mínimo (%)</label>
<input type="number" class="form-control" id="margen_minimo" th:field="*{margenMin}" min="0" max="100" step="0.01"
th:classappend="${#fields.hasErrors('margenMin')} ? ' is-invalid'" required>
<div class="invalid-feedback" th:if="${#fields.hasErrors('margenMin')}" th:errors="*{margenMin}">Error</div>
</div>
<div class="row mt-3 justified-content-center d-flex">
<button type="submit" class="btn btn-secondary" th:text="#{usuarios.guardar}">Guardar</button>
</div>
</form>
</div>

View File

@ -23,7 +23,7 @@
<!-- Modales-->
<div
th:replace="imprimelibros/partials/modal-form :: modal('userFormModal', 'usuarios.add', 'modal-md', 'userModalBody')">
th:replace="imprimelibros/partials/modal-form :: modal('margenesPresupuestoFormModal', 'margenes-presupuesto.add', 'modal-md', 'margenesPresupuestoModalBody')">
</div>
<nav aria-label="breadcrumb">
@ -72,16 +72,16 @@
</select>
</th>
<th>
<input type="text" class="form-control form-control-sm margenes-presupuesto-filter" data-col="tirada_min" />
<input type="text" class="form-control form-control-sm margenes-presupuesto-filter" data-col="tiradaMin" />
</th>
<th>
<input type="text" class="form-control form-control-sm margenes-presupuesto-filter" data-col="tirada_max" />
<input type="text" class="form-control form-control-sm margenes-presupuesto-filter" data-col="tiradaMax" />
</th>
<th>
<input type="text" class="form-control form-control-sm margenes-presupuesto-filter" data-col="margen_min" />
<input type="text" class="form-control form-control-sm margenes-presupuesto-filter" data-col="margenMax" />
</th>
<th>
<input type="text" class="form-control form-control-sm margenes-presupuesto-filter" data-col="margen_max" />
<input type="text" class="form-control form-control-sm margenes-presupuesto-filter" data-col="margenMin" />
</th>
<th></th>
</tr>

View File

@ -52,7 +52,7 @@
<div class="collapse menu-dropdown" id="sidebarConfig">
<ul class="nav nav-sm flex-column">
<li class="nav-item">
<a href="/configuracion/margenes-presupuestos" class="nav-link" th:text="#{margenes-presupuesto.titulo}">Márgenes de presupuesto</a>
<a href="/configuracion/margenes-presupuesto" class="nav-link" th:text="#{margenes-presupuesto.titulo}">Márgenes de presupuesto</a>
</li>
</ul>
</li>