Files
erp-imprimelibros/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoController.java

758 lines
33 KiB
Java

package com.imprimelibros.erp.presupuesto;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import java.time.Instant;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.List;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.bind.annotation.DeleteMapping;
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.RequestBody;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import jakarta.validation.Validator;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.type.TypeReference;
import com.imprimelibros.erp.configurationERP.VariableService;
import com.imprimelibros.erp.datatables.*;
import com.imprimelibros.erp.externalApi.skApiClient;
import com.imprimelibros.erp.i18n.TranslationService;
import com.imprimelibros.erp.presupuesto.classes.ImagenPresupuesto;
import com.imprimelibros.erp.presupuesto.classes.PresupuestoMaquetacion;
import com.imprimelibros.erp.presupuesto.classes.PresupuestoMarcapaginas;
import com.imprimelibros.erp.presupuesto.dto.Presupuesto;
import com.imprimelibros.erp.presupuesto.service.PresupuestoService;
import com.imprimelibros.erp.presupuesto.validation.PresupuestoValidationGroups;
import com.imprimelibros.erp.users.UserDao;
import com.imprimelibros.erp.users.UserDetailsImpl;
import com.imprimelibros.erp.presupuesto.service.PresupuestoFormDataMapper;
import com.imprimelibros.erp.presupuesto.service.PresupuestoFormDataMapper.PresupuestoFormDataDto;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Valid;
@Controller
@RequestMapping("/presupuesto")
public class PresupuestoController {
private final PresupuestoRepository presupuestoRepository;
@Autowired
protected PresupuestoService presupuestoService;
@Autowired
protected skApiClient apiClient;
@Autowired
protected MessageSource messageSource;
@Autowired
private Validator validator;
private final ObjectMapper objectMapper;
private final TranslationService translationService;
private final PresupuestoDatatableService dtService;
private final VariableService variableService;
private final PresupuestoFormDataMapper formDataMapper;
private final UserDao userRepo;
public PresupuestoController(ObjectMapper objectMapper, TranslationService translationService,
PresupuestoDatatableService dtService, PresupuestoRepository presupuestoRepository,
VariableService variableService, PresupuestoFormDataMapper formDataMapper, UserDao userRepo) {
this.objectMapper = objectMapper;
this.translationService = translationService;
this.dtService = dtService;
this.presupuestoRepository = presupuestoRepository;
this.variableService = variableService;
this.formDataMapper = formDataMapper;
this.userRepo = userRepo;
}
@PostMapping("/public/validar/datos-generales")
public ResponseEntity<?> validarDatosGenerales(
@Validated(PresupuestoValidationGroups.DatosGenerales.class) Presupuesto presupuesto,
BindingResult result, Locale locale) {
Map<String, String> errores = new HashMap<>();
result.getFieldErrors().forEach(error -> {
String code = Objects.requireNonNullElse(error.getDefaultMessage(), "").replace("{", "").replace("}", "");
String msg = messageSource.getMessage(code, null, locale);
errores.put(error.getField(), msg);
});
result.getGlobalErrors().forEach(error -> {
errores.put("global", error.getDefaultMessage());
});
if (!errores.isEmpty()) {
return ResponseEntity.badRequest().body(errores);
}
// opciones color
Map<String, Object> resultado = presupuestoService.obtenerOpcionesColor(presupuesto, locale);
// opciones papel interior
resultado.putAll(presupuestoService.obtenerOpcionesPapelInterior(presupuesto, locale));
// opciones gramaje interior
resultado.putAll(presupuestoService.obtenerOpcionesGramajeInterior(presupuesto));
return ResponseEntity.ok(resultado);
}
@PostMapping("/public/validar/interior")
public ResponseEntity<?> validarInterior(
@Validated(PresupuestoValidationGroups.Interior.class) Presupuesto presupuesto,
BindingResult result, Locale locale) {
Map<String, String> errores = new HashMap<>();
// errores de campos individuales
result.getFieldErrors().forEach(error -> {
String code = Objects.requireNonNullElse(error.getDefaultMessage(), "").replace("{", "").replace("}", "");
String msg = messageSource.getMessage(code, null, locale);
errores.put(error.getField(), msg);
});
// errores globales (@ConsistentTiradas...)
result.getGlobalErrors().forEach(error -> errores.put("global", error.getDefaultMessage()));
if (!errores.isEmpty()) {
return ResponseEntity.badRequest().body(errores);
}
Map<String, Object> resultado = new HashMap<>();
resultado.put("solapas", apiClient.getMaxSolapas(presupuestoService.toSkApiRequest(presupuesto)));
resultado.putAll(presupuestoService.obtenerOpcionesAcabadosCubierta(presupuesto, locale));
return ResponseEntity.ok(resultado);
}
@PostMapping("/public/validar/cubierta")
public ResponseEntity<?> validarCubierta(
@Validated(PresupuestoValidationGroups.Cubierta.class) Presupuesto presupuesto,
BindingResult result,
@RequestParam(name = "calcular", defaultValue = "true") boolean calcular,
Locale locale) {
Map<String, String> errores = new HashMap<>();
// errores de campos individuales
result.getFieldErrors().forEach(error -> {
String code = Objects.requireNonNullElse(error.getDefaultMessage(), "").replace("{", "").replace("}", "");
String msg = messageSource.getMessage(code, null, locale);
errores.put(error.getField(), msg);
});
// errores globales (@ConsistentTiradas...)
result.getGlobalErrors().forEach(error -> errores.put("global", error.getDefaultMessage()));
if (!errores.isEmpty()) {
return ResponseEntity.badRequest().body(errores);
}
if (calcular) {
HashMap<String, Object> price = presupuestoService.calcularPresupuesto(presupuesto, locale);
if (!price.containsKey("data")) {
return ResponseEntity.badRequest()
.body(messageSource.getMessage("presupuesto.error-obtener-precio", null, locale));
}
return ResponseEntity.ok(price.get("data"));
}
return ResponseEntity.ok().build();
}
@PostMapping("/public/validar/seleccion-tirada")
public ResponseEntity<?> validarSeleccionTirada(
Presupuesto presupuesto,
BindingResult result, Locale locale) {
Map<String, String> errores = new HashMap<>();
// errores de campos individuales
result.getFieldErrors().forEach(error -> {
String code = Objects.requireNonNullElse(error.getDefaultMessage(), "").replace("{", "").replace("}", "");
String msg = messageSource.getMessage(code, null, locale);
errores.put(error.getField(), msg);
});
// errores globales (@ConsistentTiradas...)
result.getGlobalErrors().forEach(error -> errores.put("global", error.getDefaultMessage()));
if (!errores.isEmpty()) {
return ResponseEntity.badRequest().body(errores);
}
Map<String, Object> resultado = new HashMap<>();
// servicios extra
resultado.putAll(presupuestoService.obtenerServiciosExtras(presupuesto, locale));
Map<String, String> language = new HashMap<>();
language.put("calcular", messageSource.getMessage("presupuesto.calcular", null, locale));
resultado.put("language", language);
return ResponseEntity.ok(resultado);
}
@PostMapping("/public/get-papel-interior")
public ResponseEntity<?> getPapelInterior(
@Validated(PresupuestoValidationGroups.Interior.class) Presupuesto presupuesto,
BindingResult result, Locale locale) {
Map<String, String> errores = new HashMap<>();
// errores de campos individuales
result.getFieldErrors().forEach(error -> {
String code = Objects.requireNonNullElse(error.getDefaultMessage(), "").replace("{", "").replace("}", "");
String msg = messageSource.getMessage(code, null, locale);
errores.put(error.getField(), msg);
});
if (!errores.isEmpty()) {
return ResponseEntity.badRequest().body(errores);
}
// opciones color
Map<String, Object> resultado = presupuestoService.obtenerOpcionesPapelInterior(presupuesto, locale);
// opciones gramaje interior
resultado.putAll(presupuestoService.obtenerOpcionesGramajeInterior(presupuesto));
List<ImagenPresupuesto> opciones_papel = new ObjectMapper().convertValue(
presupuestoService
.obtenerOpcionesPapelInterior(presupuesto, locale)
.get("opciones_papel_interior"),
new TypeReference<List<ImagenPresupuesto>>() {
});
if (opciones_papel != null && opciones_papel.stream().noneMatch(
o -> o.getExtra_data().get("sk-id").equals(String.valueOf(presupuesto.getPapelInteriorId())))) {
presupuesto.setPapelInteriorId(Integer.valueOf(opciones_papel.get(0).getExtra_data().get("sk-id")));
}
List<String> opciones = new ObjectMapper().convertValue(resultado.get("opciones_gramaje_interior"),
new TypeReference<List<String>>() {
});
if (opciones != null && !opciones.isEmpty()) {
String gramajeActual = presupuesto.getGramajeInterior().toString();
if (!opciones.contains(gramajeActual)) {
presupuesto.setGramajeInterior(Integer.parseInt(opciones.get(0))); // Asignar primera opción
}
}
resultado.put("solapas", apiClient.getMaxSolapas(presupuestoService.toSkApiRequest(presupuesto)));
return ResponseEntity.ok(resultado);
}
@PostMapping("/public/get-gramaje-interior")
public ResponseEntity<?> getGramajeInterior(
@Validated(PresupuestoValidationGroups.Interior.class) Presupuesto presupuesto,
BindingResult result, Locale locale) {
Map<String, String> errores = new HashMap<>();
// errores de campos individuales
result.getFieldErrors().forEach(error -> {
String code = Objects.requireNonNullElse(error.getDefaultMessage(), "").replace("{", "").replace("}", "");
String msg = messageSource.getMessage(code, null, locale);
errores.put(error.getField(), msg);
});
if (!errores.isEmpty()) {
return ResponseEntity.badRequest().body(errores);
}
Map<String, Object> resultado = presupuestoService.obtenerOpcionesGramajeInterior(presupuesto);
List<String> opciones = new ObjectMapper().convertValue(resultado.get("opciones_gramaje_interior"),
new TypeReference<List<String>>() {
});
if (opciones != null && !opciones.isEmpty()) {
String gramajeActual = presupuesto.getGramajeInterior().toString();
if (!opciones.contains(gramajeActual)) {
presupuesto.setGramajeInterior(Integer.parseInt(opciones.get(0))); // Asignar primera opción
}
}
resultado.put("solapas", apiClient.getMaxSolapas(presupuestoService.toSkApiRequest(presupuesto)));
return ResponseEntity.ok(resultado);
}
@PostMapping("/public/get-max-solapas")
public ResponseEntity<?> getMaxSolapas(
@Validated(PresupuestoValidationGroups.Interior.class) Presupuesto presupuesto,
BindingResult result, Locale locale) {
Map<String, String> errores = new HashMap<>();
// errores de campos individuales
result.getFieldErrors().forEach(error -> {
String code = Objects.requireNonNullElse(error.getDefaultMessage(), "").replace("{", "").replace("}", "");
String msg = messageSource.getMessage(code, null, locale);
errores.put(error.getField(), msg);
});
if (!errores.isEmpty()) {
return ResponseEntity.badRequest().body(errores);
}
Map<String, Object> resultado = new HashMap<>();
resultado.put("solapas", apiClient.getMaxSolapas(presupuestoService.toSkApiRequest(presupuesto)));
return ResponseEntity.ok(resultado);
}
@PostMapping("/public/get-papel-cubierta")
public ResponseEntity<?> getPapelCubierta(
Presupuesto presupuesto,
BindingResult result, Locale locale) {
Map<String, Object> resultado = new HashMap<>();
Map<String, Object> papelesCubierta = presupuestoService.obtenerOpcionesPapelCubierta(presupuesto, locale);
List<ImagenPresupuesto> opciones = new ObjectMapper().convertValue(
presupuestoService
.obtenerOpcionesPapelCubierta(presupuesto, locale)
.get("opciones_papel_cubierta"),
new TypeReference<List<ImagenPresupuesto>>() {
});
if (opciones != null && opciones.stream().noneMatch(
o -> o.getExtra_data().get("sk-id").equals(String.valueOf(presupuesto.getPapelCubiertaId())))) {
presupuesto.setPapelCubiertaId(Integer.valueOf(opciones.get(0).getExtra_data().get("sk-id")));
}
resultado.putAll(papelesCubierta);
resultado.putAll(presupuestoService.obtenerOpcionesGramajeCubierta(presupuesto));
List<String> gramajesCubierta = new ObjectMapper().convertValue(
resultado.get("opciones_gramaje_cubierta"),
new TypeReference<List<String>>() {
});
if (gramajesCubierta != null && !gramajesCubierta.isEmpty()) {
String gramajeActual = presupuesto.getGramajeCubierta().toString();
if (!gramajesCubierta.contains(gramajeActual)) {
presupuesto.setGramajeCubierta(Integer.parseInt(gramajesCubierta.get(0))); // Asignar primera opción
}
}
return ResponseEntity.ok(resultado);
}
@PostMapping("/public/get-gramaje-cubierta")
public ResponseEntity<?> getGramajeCubierta(
Presupuesto presupuesto,
BindingResult result) {
Map<String, Object> resultado = presupuestoService.obtenerOpcionesGramajeCubierta(presupuesto);
List<String> gramajesCubierta = (List<String>) resultado.get("opciones_gramaje_cubierta");
if (gramajesCubierta != null && !gramajesCubierta.isEmpty()) {
String gramajeActual = presupuesto.getGramajeCubierta().toString();
if (!gramajesCubierta.contains(gramajeActual)) {
presupuesto.setGramajeCubierta(Integer.parseInt(gramajesCubierta.get(0))); // Asignar primera opción
}
}
return ResponseEntity.ok(resultado);
}
@PostMapping("/public/get-acabados-cubierta")
public ResponseEntity<?> getAcabadosCubierta(
Presupuesto presupuesto,
BindingResult result, Locale locale) {
Map<String, Object> resultado = presupuestoService.obtenerOpcionesAcabadosCubierta(presupuesto, locale);
return ResponseEntity.ok(resultado);
}
@PostMapping("/public/get-price")
public ResponseEntity<?> getPrice(
Presupuesto presupuesto,
BindingResult result, Locale locale) {
Map<String, String> errores = new HashMap<>();
// errores de campos individuales
result.getFieldErrors().forEach(error -> errores.put(error.getField(), error.getDefaultMessage()));
// errores globales (@ConsistentTiradas...)
result.getGlobalErrors().forEach(error -> errores.put("global", error.getDefaultMessage()));
if (!errores.isEmpty()) {
return ResponseEntity.badRequest().body(errores);
}
String price = apiClient.getPrice(presupuestoService.toSkApiRequest(presupuesto),
presupuesto.getTipoEncuadernacion(), presupuesto.getTipoCubierta());
if (price == null || price.isEmpty()) {
return ResponseEntity.badRequest().body("No se pudo obtener el precio. Intente nuevamente.");
}
return ResponseEntity.ok(price);
}
@GetMapping(value = "/public/maquetacion/form", produces = MediaType.TEXT_HTML_VALUE)
public String getMaquetacionForm(Model model) {
model.addAttribute("presupuestoMaquetacion", new PresupuestoMaquetacion());
return "imprimelibros/presupuestos/presupuesto-maquetacion-form :: maquetacionForm";
}
@GetMapping("/public/maquetacion")
public ResponseEntity<?> getPresupuestoMaquetacion(
@Valid @ModelAttribute PresupuestoMaquetacion presupuestoMaquetacion,
BindingResult result,
Locale locale) {
if (result.hasErrors()) {
// Construimos un mapa field -> mensaje para tu AJAX
Map<String, String> errores = result.getFieldErrors().stream()
.collect(java.util.stream.Collectors.toMap(
fe -> fe.getField(),
fe -> fe.getDefaultMessage(),
(a, b) -> a));
return ResponseEntity.badRequest().body(errores);
}
Map<String, Object> resultado = presupuestoService.getPrecioMaquetacion(presupuestoMaquetacion, locale);
if ((Double) resultado.get("precio") == 0.0 && (Integer) resultado.get("numPaginasEstimadas") == 0
&& (Double) resultado.get("precioPaginaEstimado") == 0.0) {
return ResponseEntity.badRequest()
.body(messageSource.getMessage("presupuesto.errores.presupuesto-maquetacion", null, locale));
}
return ResponseEntity.ok(resultado);
}
@GetMapping(value = "/public/marcapaginas/form", produces = MediaType.TEXT_HTML_VALUE)
public String getMarcapaginasForm(Model model) {
model.addAttribute("presupuestoMarcapaginas", new PresupuestoMarcapaginas());
return "imprimelibros/presupuestos/presupuesto-marcapaginas-form :: marcapaginasForm";
}
@GetMapping("/public/marcapaginas")
public ResponseEntity<?> getPresupuestoMarcapaginas(
@Valid @ModelAttribute PresupuestoMarcapaginas presupuestoMarcapaginas,
BindingResult result,
Locale locale) {
if (result.hasErrors()) {
// Construimos un mapa field -> mensaje para tu AJAX
Map<String, String> errores = result.getFieldErrors().stream()
.collect(java.util.stream.Collectors.toMap(
fe -> fe.getField(),
fe -> fe.getDefaultMessage(),
(a, b) -> a));
return ResponseEntity.badRequest().body(errores);
}
Map<String, Object> resultado = presupuestoService.getPrecioMarcapaginas(presupuestoMarcapaginas, locale);
if ((Double) resultado.get("precio_total") == 0.0 && (Double) resultado.get("precio_unitario") == 0.0) {
return ResponseEntity.badRequest()
.body(messageSource.getMessage("presupuesto.errores.presupuesto-marcapaginas", null, locale));
}
return ResponseEntity.ok(resultado);
}
// Se hace un post para no tener problemas con la longitud de la URL
@PostMapping("/public/resumen")
public ResponseEntity<?> getResumen(
@RequestBody Map<String, Object> body,
Locale locale,
HttpServletRequest request) {
Presupuesto p = objectMapper.convertValue(body.get("presupuesto"), Presupuesto.class);
Boolean save = objectMapper.convertValue(body.get("save"), Boolean.class);
String mode = objectMapper.convertValue(body.get("mode"), String.class);
@SuppressWarnings("unchecked")
List<Map<String, Object>> serviciosList = (List<Map<String, Object>>) body.getOrDefault("servicios", List.of());
String sessionId = request.getSession(true).getId();
String ip = request.getRemoteAddr();
var resumen = presupuestoService.getResumen(p, serviciosList, save, mode, locale, sessionId, ip);
return ResponseEntity.ok(resumen);
}
// =============================================
// MÉTODOS PARA USUARIOS AUTENTICADOS
// =============================================
@GetMapping
public String getPresupuestoList(Model model, Authentication authentication, Locale locale) {
List<String> keys = List.of(
"presupuesto.delete.title",
"presupuesto.delete.text",
"presupuesto.eliminar",
"presupuesto.delete.button",
"app.yes",
"app.cancelar",
"presupuesto.delete.ok.title",
"presupuesto.delete.ok.text",
"presupuesto.add.tipo",
"presupuesto.add.anonimo",
"presupuesto.add.cliente",
"presupuesto.add.next",
"presupuesto.add.cancel",
"presupuesto.add.select-client",
"presupuesto.add.error.options",
"presupuesto.add.error.options-client");
Map<String, String> translations = translationService.getTranslations(locale, keys);
model.addAttribute("languageBundle", translations);
return "imprimelibros/presupuestos/presupuesto-list";
}
@GetMapping(value = { "/edit/{id}", "/view/{id}" })
public String getPresupuestoEditForm(
@PathVariable(name = "id", required = true) Long id,
RedirectAttributes redirectAttributes,
Model model,
Authentication authentication,
Locale locale) {
List<String> keys = List.of(
"presupuesto.plantilla-cubierta",
"presupuesto.plantilla-cubierta-text",
"presupuesto.impresion-cubierta",
"presupuesto.impresion-cubierta-help");
Map<String, String> translations = translationService.getTranslations(locale, keys);
model.addAttribute("languageBundle", translations);
model.addAttribute("pod", variableService.getValorEntero("POD"));
model.addAttribute("ancho_alto_min", variableService.getValorEntero("ancho_alto_min"));
model.addAttribute("ancho_alto_max", variableService.getValorEntero("ancho_alto_max"));
// Buscar el presupuesto
Optional<Presupuesto> presupuestoOpt = presupuestoRepository.findById(id);
if (presupuestoOpt.isEmpty()) {
// Añadir mensaje flash para mostrar alerta
redirectAttributes.addFlashAttribute("errorMessage",
messageSource.getMessage("presupuesto.errores.presupuesto-no-existe", new Object[] { id }, locale));
// Redirigir a la vista de lista
return "redirect:/presupuesto";
}
if (!presupuestoService.canAccessPresupuesto(presupuestoOpt.get(), authentication)) {
// Añadir mensaje flash para mostrar alerta
redirectAttributes.addFlashAttribute("errorMessage",
messageSource.getMessage("app.errors.403", null, locale));
// Redirigir a la vista de lista
return "redirect:/presupuesto";
}
String path = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
.getRequest().getRequestURI();
String mode = path.contains("/view/") ? "view" : "edit";
if (mode.equals("view")) {
model.addAttribute("appMode", "view");
} else {
model.addAttribute("appMode", "edit");
}
model.addAttribute("id", presupuestoOpt.get().getId());
return "imprimelibros/presupuestos/presupuesto-form";
}
@GetMapping(value = { "/add/{mode}", "/add/{mode}/{cliente_id}", "/add2/{cliente_id}" })
public String getPresupuestoEditForm(
@PathVariable(name = "mode", required = false) String mode,
@PathVariable(name = "cliente_id", required = false) Long clienteId,
RedirectAttributes redirectAttributes,
Model model,
Authentication authentication,
Locale locale) {
List<String> keys = List.of(
"presupuesto.plantilla-cubierta",
"presupuesto.plantilla-cubierta-text",
"presupuesto.impresion-cubierta",
"presupuesto.impresion-cubierta-help",
"presupuesto.exito.guardado",
"presupuesto.add.error.save.title");
Map<String, String> translations = translationService.getTranslations(locale, keys);
model.addAttribute("languageBundle", translations);
model.addAttribute("pod", variableService.getValorEntero("POD"));
model.addAttribute("ancho_alto_min", variableService.getValorEntero("ancho_alto_min"));
model.addAttribute("ancho_alto_max", variableService.getValorEntero("ancho_alto_max"));
model.addAttribute("appMode", "add");
if (!mode.equals("public")) {
model.addAttribute("cliente_id", clienteId);
}
model.addAttribute("mode", mode);
return "imprimelibros/presupuestos/presupuesto-form";
}
@GetMapping(value = "/api/get", produces = "application/json")
public ResponseEntity<PresupuestoFormDataDto> getPresupuesto(
@RequestParam("id") Long id, Authentication authentication) {
Optional<Presupuesto> presupuestoOpt = presupuestoRepository.findById(id);
if (!presupuestoService.canAccessPresupuesto(presupuestoOpt.get(), authentication)) {
return ResponseEntity.status(403).build();
}
if (presupuestoOpt.isPresent()) {
PresupuestoFormDataDto vm = formDataMapper.toFormData(presupuestoOpt.get());
return ResponseEntity.ok(vm);
} else {
return ResponseEntity.notFound().build();
}
}
@GetMapping(value = "/datatable/{tipo}", produces = "application/json")
@ResponseBody
public DataTablesResponse<Map<String, Object>> datatable(
HttpServletRequest request, Authentication auth, Locale locale,
@PathVariable("tipo") String tipo) {
DataTablesRequest dt = DataTablesParser.from(request);
if ("anonimos".equals(tipo)) {
return dtService.datatablePublicos(dt, locale);
} else if ("clientes".equals(tipo)) {
return dtService.datatablePrivados(dt, locale);
} else {
throw new IllegalArgumentException("Tipo de datatable no válido");
}
}
@DeleteMapping("/{id}")
@Transactional
public ResponseEntity<?> delete(@PathVariable Long id, Authentication auth, Locale locale) {
Presupuesto p = presupuestoRepository.findById(id).orElse(null);
if (p == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(Map.of("message", messageSource.getMessage("presupuesto.error.not-found", null, locale)));
}
boolean isUser = auth != null && auth.getAuthorities().stream()
.anyMatch(a -> a.getAuthority().equals("ROLE_USER"));
Long ownerId = p.getUser() != null ? p.getUser().getId() : null;
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);
}
boolean isOwner = ownerId != null && ownerId.equals(currentUserId);
if (isUser && !isOwner) {
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(Map.of("message",
messageSource.getMessage("presupuesto.error.delete-permission-denied", null, locale)));
}
if (p.getEstado() != null && !p.getEstado().equals(Presupuesto.Estado.borrador)) {
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(Map.of("message",
messageSource.getMessage("presupuesto.error.delete-not-draft", null, locale)));
}
try {
p.setDeleted(true);
p.setDeletedAt(Instant.now());
if (auth != null && auth.getPrincipal() instanceof UserDetailsImpl udi) {
p.setDeletedBy(userRepo.getReferenceById(udi.getId()));
} else if (auth != null) {
userRepo.findByUserNameIgnoreCase(auth.getName()).ifPresent(p::setDeletedBy);
}
presupuestoRepository.saveAndFlush(p);
return ResponseEntity.ok(Map.of("message",
messageSource.getMessage("presupuesto.exito.eliminado", null, locale)));
} catch (Exception ex) {
// Devuelve SIEMPRE algo en el catch
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Map.of("message",
messageSource.getMessage("presupuesto.error.delete-internal-error", null, locale),
"detail",
ex.getClass().getSimpleName() + ": " + (ex.getMessage() != null ? ex.getMessage() : "")));
}
}
@PostMapping(path="/save")
public ResponseEntity<?> save(
@RequestBody Map<String, Object> body,
Locale locale, HttpServletRequest request) {
Presupuesto presupuesto = objectMapper.convertValue(body.get("presupuesto"), Presupuesto.class);
Long id = objectMapper.convertValue(body.get("id"), Long.class);
String mode = objectMapper.convertValue(body.get("mode"), String.class);
@SuppressWarnings("unchecked")
List<Map<String, Object>> serviciosList = (List<Map<String, Object>>) body.getOrDefault("servicios", List.of());
Set<ConstraintViolation<Presupuesto>> violations = validator.validate(presupuesto,
PresupuestoValidationGroups.All.class);
if (!violations.isEmpty()) {
Map<String, String> errores = new HashMap<>();
for (ConstraintViolation<Presupuesto> v : violations) {
String campo = v.getPropertyPath().toString();
String mensaje = messageSource.getMessage(v.getMessage().replace("{", "").replace("}", ""), null, locale);
errores.put(campo, mensaje);
}
return ResponseEntity.badRequest().body(errores);
}
try {
var resumen = presupuestoService.getTextosResumen(presupuesto, serviciosList, locale);
Long cliente_id = objectMapper.convertValue(body.get("cliente_id"), Long.class);
if(id == null && cliente_id != null && !mode.equals("public")) {
presupuesto.setUser(userRepo.findById(cliente_id).orElse(null));
presupuesto.setOrigen(Presupuesto.Origen.privado);
}
if (mode.equals("public")) {
presupuesto.setOrigen(Presupuesto.Origen.publico);
String sessionId = request.getSession(true).getId();
String ip = request.getRemoteAddr();
presupuesto = presupuestoService.getDatosLocalizacion(presupuesto, sessionId, ip);
if (id != null) {
presupuesto.setId(id); // para que actualice, no cree uno nuevo
}
}
presupuesto = presupuestoService.generateTotalizadores(presupuesto, serviciosList, resumen, locale);
Map<String, Object> saveResult = presupuestoService.guardarPresupuesto(presupuesto);
return ResponseEntity.ok(Map.of("id", saveResult.get("presupuesto_id"),
"message", messageSource.getMessage("presupuesto.exito.guardado", null, locale)));
} catch (Exception ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Map.of("message",
messageSource.getMessage("presupuesto.error.save-internal-error", null, locale),
"detail",
ex.getClass().getSimpleName() + ": " + (ex.getMessage() != null ? ex.getMessage() : "")));
}
}
}