mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-13 00:48:49 +00:00
falta borrar direcciones e implementar una vista de cliente
This commit is contained in:
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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<String> 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<String, String> 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);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
@ -13,54 +13,22 @@ public interface DireccionRepository
|
||||
extends JpaRepository<Direccion, Long>,
|
||||
JpaSpecificationExecutor<Direccion> {
|
||||
|
||||
|
||||
@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<DireccionView> 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<DireccionView> 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<Direccion> findByIdWithPaisAndUser(@Param("id") Long id);
|
||||
|
||||
|
||||
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user