diff --git a/src/main/java/com/imprimelibros/erp/config/BeanValidationConfig.java b/src/main/java/com/imprimelibros/erp/config/BeanValidationConfig.java index 1d72169..05a7273 100644 --- a/src/main/java/com/imprimelibros/erp/config/BeanValidationConfig.java +++ b/src/main/java/com/imprimelibros/erp/config/BeanValidationConfig.java @@ -1,5 +1,6 @@ package com.imprimelibros.erp.config; +import org.springframework.context.MessageSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; @@ -10,13 +11,16 @@ import jakarta.validation.ValidatorFactory; @Configuration public class BeanValidationConfig { - // Asegura que usamos la factory de Spring (con SpringConstraintValidatorFactory) + // Usa el MessageSource (messages*.properties) para resolver {códigos} @Bean - public LocalValidatorFactoryBean validator() { - return new LocalValidatorFactoryBean(); + public LocalValidatorFactoryBean validator(MessageSource messageSource) { + LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean(); + bean.setValidationMessageSource(messageSource); // <-- CLAVE + + return bean; } - // Inserta esa factory en Hibernate/JPA + // Inserta esa factory en Hibernate/JPA (opcional pero correcto) @Bean public HibernatePropertiesCustomizer hibernateValidationCustomizer(ValidatorFactory vf) { return props -> props.put("jakarta.persistence.validation.factory", vf); diff --git a/src/main/java/com/imprimelibros/erp/direcciones/Direccion.java b/src/main/java/com/imprimelibros/erp/direcciones/Direccion.java index e8f24bc..693b771 100644 --- a/src/main/java/com/imprimelibros/erp/direcciones/Direccion.java +++ b/src/main/java/com/imprimelibros/erp/direcciones/Direccion.java @@ -10,6 +10,9 @@ import com.imprimelibros.erp.common.jpa.AbstractAuditedEntity; import com.imprimelibros.erp.paises.Paises; import com.imprimelibros.erp.users.User; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank; + @Entity @Table(name = "direcciones", indexes = { @Index(name = "idx_direcciones_user", columnList = "user_id"), @@ -42,32 +45,41 @@ public class Direccion extends AbstractAuditedEntity implements Serializable { private Long id; // --- FK a users(id) + @NotNull(message = "{direcciones.form.error.required}") @ManyToOne(fetch = FetchType.LAZY, optional = false) @JoinColumn(name = "user_id", nullable = false) private User user; + @NotBlank(message = "{direcciones.form.error.required}") @Column(name = "alias", length = 100, nullable = false) private String alias; + @NotBlank(message = "{direcciones.form.error.required}") @Column(name = "att", length = 150, nullable = false) private String att; + @NotBlank(message = "{direcciones.form.error.required}") @Column(name = "direccion", length = 255, nullable = false) private String direccion; + @NotNull(message = "{direcciones.form.error.required}") @Column(name = "cp", length = 20, nullable = false) private Integer cp; + @NotBlank(message = "{direcciones.form.error.required}") @Column(name = "ciudad", length = 100, nullable = false) private String ciudad; + @NotBlank(message = "{direcciones.form.error.required}") @Column(name = "provincia", length = 100, nullable = false) private String provincia; // Usamos el code3 del país como FK lógica (String) + @NotBlank(message = "{direcciones.form.error.required}") @Column(name = "pais_code3", length = 3, nullable = false) private String paisCode3 = "esp"; + @NotBlank(message = "{direcciones.form.error.required}") @Column(name = "telefono", length = 30, nullable = false) private String telefono; diff --git a/src/main/java/com/imprimelibros/erp/direcciones/DireccionController.java b/src/main/java/com/imprimelibros/erp/direcciones/DireccionController.java index 2c42366..1141617 100644 --- a/src/main/java/com/imprimelibros/erp/direcciones/DireccionController.java +++ b/src/main/java/com/imprimelibros/erp/direcciones/DireccionController.java @@ -12,6 +12,8 @@ import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -21,11 +23,15 @@ import com.imprimelibros.erp.datatables.DataTable; import com.imprimelibros.erp.datatables.DataTablesParser; import com.imprimelibros.erp.datatables.DataTablesRequest; import com.imprimelibros.erp.datatables.DataTablesResponse; +import com.imprimelibros.erp.i18n.TranslationService; import com.imprimelibros.erp.paises.PaisesService; +import com.imprimelibros.erp.users.UserDao; +import com.imprimelibros.erp.users.UserDetailsImpl; import jakarta.persistence.criteria.Predicate; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; @Controller @RequestMapping("/direcciones") @@ -34,11 +40,16 @@ public class DireccionController { protected final DireccionRepository repo; protected final PaisesService paisesService; protected final MessageSource messageSource; + protected final UserDao userRepo; + protected final TranslationService translationService; - public DireccionController(DireccionRepository repo, PaisesService paisesService, MessageSource messageSource) { + public DireccionController(DireccionRepository repo, PaisesService paisesService, + MessageSource messageSource, UserDao userRepo, TranslationService translationService) { this.repo = repo; this.paisesService = paisesService; this.messageSource = messageSource; + this.userRepo = userRepo; + this.translationService = translationService; } @GetMapping() @@ -48,6 +59,19 @@ public class DireccionController { .anyMatch(a -> a.getAuthority().equals("ROLE_USER")); model.addAttribute("isUser", isUser ? 1 : 0); + List keys = List.of( + "margenes-presupuesto.delete.title", + "margenes-presupuesto.delete.text", + "margenes-presupuesto.eliminar", + "margenes-presupuesto.delete.button", + "app.yes", + "app.cancelar", + "margenes-presupuesto.delete.ok.title", + "margenes-presupuesto.delete.ok.text"); + + Map translations = translationService.getTranslations(locale, keys); + model.addAttribute("languageBundle", translations); + return "imprimelibros/direcciones/direccion-list"; } @@ -80,13 +104,13 @@ public class DireccionController { if (authentication != null && authentication.getAuthorities().stream() .anyMatch(a -> a.getAuthority().equals("ROLE_USER"))) { String username = authentication.getName(); - predicates.add(cb.equal(root.get("user").get("username"), username)); + predicates.add(cb.equal(root.get("user").get("userName"), username)); } return cb.and(predicates.toArray(new Predicate[0])); }; - long total = repo.count(); // total sin filtro global + long total = repo.count(base); // Construcción del datatable con entity + spec return DataTable @@ -168,11 +192,11 @@ public class DireccionController { return "imprimelibros/direcciones/direccion-form :: direccionForm"; } - model.addAttribute("direccion", opt.get()); + model.addAttribute("dirForm", opt.get()); model.addAttribute("action", "/direcciones/" + id); } else { - model.addAttribute("direccion", new Direccion()); + model.addAttribute("dirForm", new Direccion()); model.addAttribute("action", "/direcciones"); } return "imprimelibros/direcciones/direccion-form :: direccionForm"; @@ -180,7 +204,7 @@ public class DireccionController { @PostMapping public String create( - Direccion direccion, + @Valid @ModelAttribute("dirForm") Direccion direccion, BindingResult binding, Model model, HttpServletResponse response, @@ -188,130 +212,110 @@ public class DireccionController { if (binding.hasErrors()) { response.setStatus(422); - model.addAttribute("action", "/direcciones/"); + model.addAttribute("paises", paisesService.getForSelect("", "", locale).get("results")); + model.addAttribute("action", "/direcciones"); + model.addAttribute("dirForm", direccion); return "imprimelibros/direcciones/direccion-form :: direccionForm"; } var data = direccion; - - try { - repo.save(data); - } catch (jakarta.validation.ConstraintViolationException vex) { - // Errores de Bean Validation disparados al flush (incluye tu @NoRangeOverlap) - vex.getConstraintViolations().forEach(v -> { - // intenta asignar al campo si existe, si no, error global - String path = v.getPropertyPath() != null ? v.getPropertyPath().toString() : null; - String code = v.getMessage() != null ? v.getMessage().trim() : ""; - - if (code.startsWith("{") && code.endsWith("}")) { - code = code.substring(1, code.length() - 1); // -> "validation.required" - } - - if (path != null && binding.getFieldError(path) == null) { - - binding.rejectValue(path, "validation", messageSource.getMessage(code, null, locale)); - } else { - binding.reject("validation", messageSource.getMessage(code, null, locale)); - } - }); - response.setStatus(422); - model.addAttribute("action", "/direcciones/"); - return "imprimelibros/direcciones/direccion-form :: direccionForm"; - } + repo.save(data); response.setStatus(201); return null; } -/* - @PutMapping("/{id}") - public String edit( + + @PostMapping("/{id}") + public String update( @PathVariable Long id, - MargenPresupuesto form, + @Valid @ModelAttribute("dirForm") Direccion direccion, // <- nombre distinto BindingResult binding, Model model, + Authentication auth, HttpServletResponse response, Locale locale) { - var uOpt = repo.findById(id); - if (uOpt.isEmpty()) { - binding.reject("usuarios.error.noEncontrado", - messageSource.getMessage("usuarios.error.noEncontrado", null, locale)); + var opt = repo.findById(id); + if (opt.isEmpty()) { + binding.reject("direcciones.error.noEncontrado", + messageSource.getMessage("direcciones.error.noEncontrado", null, locale)); + response.setStatus(404); + model.addAttribute("dirForm", direccion); // por si re-renderiza + model.addAttribute("action", "/direcciones/" + id); + return "imprimelibros/direcciones/direccion-form :: direccionForm"; + } + + Long ownerId = opt.get().getUser() != null ? opt.get().getUser().getId() : null; + if (!isOwnerOrAdmin(auth, ownerId)) { + binding.reject("direcciones.error.sinPermiso", + messageSource.getMessage("direcciones.error.sinPermiso", null, locale)); + response.setStatus(403); + model.addAttribute("dirForm", direccion); // por si re-renderiza + model.addAttribute("action", "/direcciones/" + id); + return "imprimelibros/direcciones/direccion-form :: direccionForm"; } if (binding.hasErrors()) { response.setStatus(422); - model.addAttribute("action", "/configuracion/margenes-presupuesto/" + id); - return "imprimelibros/configuracion/margenes-presupuesto/margenes-presupuesto-form :: margenesPresupuestoForm"; + model.addAttribute("dirForm", direccion); // <- importante + model.addAttribute("paises", paisesService.getForSelect("", "", locale).get("results")); + model.addAttribute("action", "/direcciones/" + id); + return "imprimelibros/direcciones/direccion-form :: direccionForm"; } - var entity = uOpt.get(); - - // 3) Copiar solamente campos editables - entity.setImporteMin(form.getImporteMin()); - entity.setImporteMax(form.getImporteMax()); - entity.setMargenMax(form.getMargenMax()); - entity.setMargenMin(form.getMargenMin()); - - try { - repo.saveAndFlush(entity); - - } catch (jakarta.validation.ConstraintViolationException vex) { - // Errores de Bean Validation disparados al flush (incluye tu @NoRangeOverlap) - vex.getConstraintViolations().forEach(v -> { - // intenta asignar al campo si existe, si no, error global - String path = v.getPropertyPath() != null ? v.getPropertyPath().toString() : null; - String code = v.getMessage() != null ? v.getMessage().trim() : ""; - - if (code.startsWith("{") && code.endsWith("}")) { - code = code.substring(1, code.length() - 1); // -> "validation.required" - } - - if (path != null && binding.getFieldError(path) == null) { - - binding.rejectValue(path, "validation", messageSource.getMessage(code, null, locale)); - } else { - binding.reject("validation", messageSource.getMessage(code, null, locale)); - } - }); - response.setStatus(422); - model.addAttribute("action", "/configuracion/margenes-presupuesto/" + id); - return "imprimelibros/configuracion/margenes-presupuesto/margenes-presupuesto-form :: margenesPresupuestoForm"; - - } catch (org.springframework.dao.DataIntegrityViolationException dex) { - // Uniques, FKs, checks… mensajes de la BD - String msg = dex.getMostSpecificCause() != null ? dex.getMostSpecificCause().getMessage() - : dex.getMessage(); - binding.reject("db.error", messageSource.getMessage(msg, null, locale)); - response.setStatus(422); - model.addAttribute("action", "/configuracion/margenes-presupuesto/" + id); - return "imprimelibros/configuracion/margenes-presupuesto/margenes-presupuesto-form :: margenesPresupuestoForm"; - } - - response.setStatus(204); + repo.save(direccion); + response.setStatus(200); return null; } - @DeleteMapping("/{id}") - @Transactional - public ResponseEntity delete(@PathVariable Long id, Authentication auth, Locale locale) { + /* + * + * @DeleteMapping("/{id}") + * + * @Transactional + * public ResponseEntity delete(@PathVariable Long id, Authentication auth, + * Locale locale) { + * + * return repo.findById(id).map(u -> { + * try { + * u.setDeleted(true); + * u.setDeletedAt(LocalDateTime.now()); + * + * repo.save(u); // ← NO delete(); guardamos el soft delete con deleted_by + * relleno + * return ResponseEntity.ok(Map.of("message", + * messageSource.getMessage("margenes-presupuesto.exito.eliminado", null, + * locale))); + * } catch (Exception ex) { + * return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + * .body(Map.of("message", + * messageSource.getMessage("margenes-presupuesto.error.delete-internal-error", + * null, + * locale))); + * } + * }).orElseGet(() -> ResponseEntity.status(HttpStatus.NOT_FOUND) + * .body(Map.of("message", + * messageSource.getMessage("margenes-presupuesto.error.not-found", null, + * locale)))); + * } + */ - return repo.findById(id).map(u -> { - try { - u.setDeleted(true); - u.setDeletedAt(LocalDateTime.now()); - - repo.save(u); // ← NO delete(); guardamos el soft delete con deleted_by relleno - return ResponseEntity.ok(Map.of("message", - messageSource.getMessage("margenes-presupuesto.exito.eliminado", null, locale))); - } catch (Exception ex) { - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(Map.of("message", - messageSource.getMessage("margenes-presupuesto.error.delete-internal-error", null, - locale))); - } - }).orElseGet(() -> ResponseEntity.status(HttpStatus.NOT_FOUND) - .body(Map.of("message", - messageSource.getMessage("margenes-presupuesto.error.not-found", null, locale)))); + private boolean isOwnerOrAdmin(Authentication auth, Long ownerId) { + if (auth == null) { + return false; + } + boolean isAdmin = auth.getAuthorities().stream() + .anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN")); + if (isAdmin) { + return true; + } + // Aquí deberías obtener el ID del usuario actual desde tu servicio de usuarios + Long currentUserId = null; + if (auth != null && auth.getPrincipal() instanceof UserDetailsImpl udi) { + currentUserId = udi.getId(); + } else if (auth != null) { + currentUserId = userRepo.findIdByUserNameIgnoreCase(auth.getName()).orElse(null); + } + return currentUserId != null && currentUserId.equals(ownerId); } - */ } diff --git a/src/main/java/com/imprimelibros/erp/direcciones/DireccionRepository.java b/src/main/java/com/imprimelibros/erp/direcciones/DireccionRepository.java index 3158f42..fc45640 100644 --- a/src/main/java/com/imprimelibros/erp/direcciones/DireccionRepository.java +++ b/src/main/java/com/imprimelibros/erp/direcciones/DireccionRepository.java @@ -13,54 +13,22 @@ public interface DireccionRepository extends JpaRepository, JpaSpecificationExecutor { + @Query(""" - SELECT - d.id AS id, - d.alias AS alias, - d.att AS att, - d.direccion AS direccion, - d.cp AS cp, - d.ciudad AS ciudad, - d.provincia AS provincia, - d.paisCode3 AS paisCode3, - p.keyword AS paisKeyword, - d.telefono AS telefono, - d.direccionFacturacion AS direccionFacturacion, - d.razonSocial AS razonSocial, - d.tipoIdentificacionFiscal AS tipoIdentificacionFiscal, - d.identificacionFiscal AS identificacionFiscal, - u.fullName AS cliente - FROM Direccion d - JOIN d.user u - LEFT JOIN Paises p ON d.paisCode3 = p.code3 - WHERE (:userId IS NULL OR u.id = :userId) - """) + select d from Direccion d + left join fetch d.user + left join fetch d.pais + where d.user.id = :id + """) List findAllWithPaisAndUser(@Param("userId") Long userId); - //findbyidwithPaisAndUser @Query(""" - SELECT - d.id AS id, - d.alias AS alias, - d.att AS att, - d.direccion AS direccion, - d.cp AS cp, - d.ciudad AS ciudad, - d.provincia AS provincia, - d.paisCode3 AS paisCode3, - p.keyword AS paisKeyword, - d.telefono AS telefono, - d.direccionFacturacion AS direccionFacturacion, - d.razonSocial AS razonSocial, - d.tipoIdentificacionFiscal AS tipoIdentificacionFiscal, - d.identificacionFiscal AS identificacionFiscal, - u.fullName AS cliente - FROM Direccion d - JOIN d.user u - LEFT JOIN Paises p ON d.paisCode3 = p.code3 - WHERE (d.id = :id) - """) - Optional findByIdWithPaisAndUser(@Param("id") Long id); + select d from Direccion d + left join fetch d.user + left join fetch d.pais + where d.id = :id + """) + Optional findByIdWithPaisAndUser(@Param("id") Long id); diff --git a/src/main/java/com/imprimelibros/erp/direcciones/DireccionView.java b/src/main/java/com/imprimelibros/erp/direcciones/DireccionView.java index b80c28b..b9d6cfd 100644 --- a/src/main/java/com/imprimelibros/erp/direcciones/DireccionView.java +++ b/src/main/java/com/imprimelibros/erp/direcciones/DireccionView.java @@ -2,13 +2,14 @@ package com.imprimelibros.erp.direcciones; public interface DireccionView { Long getId(); + UserView getUser(); String getAlias(); String getAtt(); String getDireccion(); String getCp(); String getCiudad(); String getProvincia(); - String getPaisCode3(); + PaisView getPais(); String getPaisKeyword(); String getTelefono(); Boolean getIsFacturacion(); @@ -16,4 +17,12 @@ public interface DireccionView { String getTipoIdentificacionFiscal(); String getIdentificacionFiscal(); String getCliente(); + interface UserView { + Long getId(); + String getFullName(); + } + interface PaisView { + String getCode3(); + String getKeyword(); + } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a58a4f4..d56bf95 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -85,7 +85,6 @@ geoip.http.enabled=true # Hibernate Timezone # spring.jpa.properties.hibernate.jdbc.time_zone=UTC - # # PDF Templates # diff --git a/src/main/resources/i18n/direcciones_es.properties b/src/main/resources/i18n/direcciones_es.properties index 8f90a0b..a88de3c 100644 --- a/src/main/resources/i18n/direcciones_es.properties +++ b/src/main/resources/i18n/direcciones_es.properties @@ -4,6 +4,8 @@ direcciones.editar=Editar dirección direcciones.breadcrumb=Direcciones direcciones.add=Añadir dirección direcciones.edit=Editar dirección +direcciones.save=Guardar dirección +direcciones.user=Cliente direcciones.alias=Alias direcciones.alias-descripcion=Nombre descriptivo para identificar la dirección. direcciones.nombre=Nombre y Apellidos @@ -21,6 +23,7 @@ direcciones.tipo_identificacion_fiscal=Tipo de identificación fiscal direcciones.identificacion_fiscal=Número de identificación fiscal direcciones.tabla.id=ID +direcciones.tabla.att=Att. direcciones.tabla.cliente=Cliente direcciones.tabla.acciones=Acciones @@ -30,5 +33,15 @@ direcciones.pasaporte=Pasaporte direcciones.cif=C.I.F. direcciones.vat_id=VAT ID -direcciones.error.noEncontrado=Dirección no encontrada. +direcciones.delete.title=Eliminar dirección +direcciones.delete.button=Si, ELIMINAR +direcciones.delete.text=¿Está seguro de que desea eliminar esta dirección?
Esta acción no se puede deshacer. +direcciones.delete.ok.title=Dirección eliminada +direcciones.delete.ok.text=La dirección ha sido eliminada con éxito. + + +direcciones.error.noEncontrado=Dirección no encontrada. +direcciones.error.sinPermiso=No tienes permiso para realizar esta acción. + +direcciones.form.error.required=Campo obligatorio. diff --git a/src/main/resources/static/assets/js/pages/imprimelibros/direcciones/list.js b/src/main/resources/static/assets/js/pages/imprimelibros/direcciones/list.js index 288cc92..5fce408 100644 --- a/src/main/resources/static/assets/js/pages/imprimelibros/direcciones/list.js +++ b/src/main/resources/static/assets/js/pages/imprimelibros/direcciones/list.js @@ -27,7 +27,7 @@ pageLength: 50, language: { url: '/assets/libs/datatables/i18n/' + language + '.json' }, responsive: true, - dom: 'lrBtip', + dom: $('#isUser').val() == 1 ? 'lrtip' : 'lrBtip', buttons: { dom: { button: { @@ -49,10 +49,10 @@ }, order: [[0, 'asc']], columns: [ - { data: 'id', name: 'id', orderable: true, visible: $('#isUser').val() }, - { data: 'cliente', name: 'cliente', orderable: true }, + { data: 'id', name: 'id', orderable: true, visible: $('#isUser').val() == 1 ? false : true }, + { data: 'cliente', name: 'cliente', orderable: true, visible: $('#isUser').val() == 1 ? false : true }, { data: 'alias', name: 'alias', orderable: true }, - { data: 'nombre', name: 'nombre', orderable: true }, + { data: 'att', name: 'att', orderable: true }, { data: 'direccion', name: 'direccion', orderable: true }, { data: 'cp', name: 'cp', orderable: true }, { data: 'ciudad', name: 'ciudad', orderable: true }, @@ -77,7 +77,7 @@ $(document).on("change", ".direccionFacturacion", function () { const isChecked = $(this).is(':checked'); - if(isChecked) { + if (isChecked) { $('.direccionFacturacionItems').removeClass('d-none'); } else { $('.direccionFacturacionItems').addClass('d-none'); @@ -95,24 +95,50 @@ const title = $('#direccionFormModalBody #direccionForm').data('add'); $('#direccionFormModal .modal-title').text(title); modal.show(); + initSelect2Cliente(true); }); }); + function initSelect2Cliente(initialize = false) { + + if ($('#isUser').val() == 0) { + const $sel = $('#user_id').select2({ + dropdownParent: modalEl, + width: '100%', + ajax: { + url: 'users/api/get-users', + dataType: 'json', + delay: 250, + }, + allowClear: true + }); + if (initialize) { + const id = $sel.data('init-id'); + const text = $sel.data('init-name'); + const option = new Option(text, id, true, true); + $('#user_id').append(option).trigger('change'); + } + } + + } // Abrir "Editar" $(document).on('click', '.btn-edit-direccion', 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();*/ + e.preventDefault(); + $.get('/direcciones/form', { id }, function (html) { + $('#direccionFormModalBody').html(html); + const title = $('#direccionFormModalBody #direccionForm').data('edit'); + $('#direccionFormModal .modal-title').text(title); + modal.show(); + initSelect2Cliente(true); + }); }); // Botón "Eliminar" - $(document).on('click', '.btn-delete-margen', function (e) { + $(document).on('click', '.btn-delete-direccion', function (e) { e.preventDefault(); const id = $(this).data('id'); @@ -132,12 +158,12 @@ if (!result.isConfirmed) return; $.ajax({ - url: '/configuracion/margenes-presupuesto/' + id, + url: '/direcciones/' + 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.', + icon: 'success', title: window.languageBundle.get(['direcciones.delete.ok.title']) || 'Eliminado', + text: window.languageBundle.get(['direcciones.delete.ok.text']) || 'La dirección ha sido eliminada con éxito.', showConfirmButton: true, customClass: { confirmButton: 'btn btn-secondary w-xs mt-2', @@ -148,7 +174,7 @@ 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.'; + || 'Error al eliminar la direccion.'; Swal.fire({ icon: 'error', title: 'No se pudo eliminar', text: msg }); } }); @@ -156,7 +182,7 @@ }); // Submit del form en el modal - $(document).on('submit', '#margenesPresupuestoForm', function (e) { + $(document).on('submit', '#direccionForm', function (e) { e.preventDefault(); const $form = $(this); @@ -167,11 +193,11 @@ 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(' 0; - const title = $('#margenesPresupuestoModalBody #margenesPresupuestoForm').data(isEdit ? 'edit' : 'add'); - $('#margenesPresupuestoModal .modal-title').text(title); + if (typeof html === 'string' && html.indexOf('id="direccionForm"') !== -1 && html.indexOf(' 0; + const title = $('#direccionFormModalBody #direccionForm').data(isEdit ? 'edit' : 'add'); + $('#direccionModal .modal-title').text(title); return; } // Éxito real: cerrar y recargar tabla @@ -181,14 +207,15 @@ 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); + $('#direccionFormModalBody').html(xhr.responseText); + const isEdit = $('#direccionFormModalBody #direccionForm input[name="_method"][value="PUT"]').length > 0; + const title = $('#direccionFormModalBody #direccionForm').data(isEdit ? 'edit' : 'add'); + $('#direccionModal .modal-title').text(title); + initSelect2Cliente(true); return; } // Fallback - $('#margenesPresupuestoModalBody').html('
Error inesperado.
'); + $('#direccionFormModalBody').html('
Error inesperado.
'); } }); }); diff --git a/src/main/resources/templates/imprimelibros/direcciones/direccion-form.html b/src/main/resources/templates/imprimelibros/direcciones/direccion-form.html index 1087e07..ddfe02a 100644 --- a/src/main/resources/templates/imprimelibros/direcciones/direccion-form.html +++ b/src/main/resources/templates/imprimelibros/direcciones/direccion-form.html @@ -1,44 +1,66 @@
-
-
+ +
+ Error +
+ +
+ + +
+
+
+ + + +
- -
+ +
-
+
- -
+ +
-
+
-
+ required style="max-height: 125px;" + th:classappend="${#fields.hasErrors('direccion')} ? ' is-invalid'"> +
-
+
-
+ required th:classappend="${#fields.hasErrors('cp')} ? ' is-invalid'"> +
@@ -46,21 +68,21 @@ Ciudad * - -
+ +
-
-
+
-
+ required th:classappend="${#fields.hasErrors('provincia')} ? ' is-invalid'"> +
@@ -68,67 +90,74 @@ País * - -
+
-
+
- -
+ +
-
+
-
+ style="max-height: 125px;" + th:classappend="${#fields.hasErrors('instrucciones')} ? ' is-invalid'"> +
+
- + id="direccionFacturacion" th:field="*{direccionFacturacion}"> +
-
+ th:class="'row mt-2 direccionFacturacionItems' + (${direccion != null and direccion.direccionFacturacion} ? '' : ' d-none')">
-
+
@@ -137,13 +166,14 @@ * -
+ maxlength="50" th:classappend="${#fields.hasErrors('identificacionFiscal')} ? ' is-invalid'"> +
- +
diff --git a/src/main/resources/templates/imprimelibros/direcciones/direccion-list.html b/src/main/resources/templates/imprimelibros/direcciones/direccion-list.html index 8dfa287..e2560b5 100644 --- a/src/main/resources/templates/imprimelibros/direcciones/direccion-list.html +++ b/src/main/resources/templates/imprimelibros/direcciones/direccion-list.html @@ -49,7 +49,7 @@ ID Cliente Alias - Nombre y Apellidos + Att. Dirección Código Postal Ciudad