package com.imprimelibros.erp.facturacion.controller; 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.facturacion.SerieFactura; import com.imprimelibros.erp.facturacion.TipoSerieFactura; import com.imprimelibros.erp.facturacion.repo.SerieFacturaRepository; import com.imprimelibros.erp.i18n.TranslationService; import jakarta.persistence.EntityNotFoundException; import jakarta.servlet.http.HttpServletRequest; import org.springframework.context.MessageSource; import org.springframework.data.jpa.domain.Specification; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; import java.time.Instant; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; @Controller @RequestMapping("/configuracion/series-facturacion") @PreAuthorize("hasRole('SUPERADMIN')") public class SeriesFacturacionController { private final SerieFacturaRepository repo; private final TranslationService translationService; private final MessageSource messageSource; public SeriesFacturacionController(SerieFacturaRepository repo, TranslationService translationService, MessageSource messageSource) { this.repo = repo; this.translationService = translationService; this.messageSource = messageSource; } // ----------------------------- // VISTA // ----------------------------- @GetMapping public String listView(Model model, Locale locale) { List keys = List.of( "series-facturacion.modal.title.add", "series-facturacion.modal.title.edit", "app.guardar", "app.cancelar", "app.eliminar", "series-facturacion.delete.title", "series-facturacion.delete.text", "series-facturacion.delete.ok.title", "series-facturacion.delete.ok.text"); Map translations = translationService.getTranslations(locale, keys); model.addAttribute("languageBundle", translations); return "imprimelibros/configuracion/series-facturas/series-facturas-list"; } // ----------------------------- // API: DataTables (server-side) // ----------------------------- @GetMapping("/api/datatables") @ResponseBody public DataTablesResponse> datatables(HttpServletRequest request, Locale locale) { DataTablesRequest dt = DataTablesParser.from(request); Specification notDeleted = (root, q, cb) -> cb.isNull(root.get("deletedAt")); long total = repo.count(notDeleted); return DataTable .of(repo, SerieFactura.class, dt, List.of("nombreSerie", "prefijo")) .where(notDeleted) .orderable(List.of("id", "nombreSerie", "prefijo", "tipo", "numeroActual")) .onlyAddedColumns() .add("id", SerieFactura::getId) .add("nombre_serie", SerieFactura::getNombreSerie) .add("prefijo", SerieFactura::getPrefijo) .add("tipo", s -> s.getTipo() != null ? s.getTipo().name() : null) .add("tipo_label", s -> { if (s.getTipo() == null) return null; return messageSource.getMessage( "series-facturacion.tipo." + s.getTipo().name(), null, s.getTipo().name(), locale); }) .add("numero_actual", SerieFactura::getNumeroActual) .add("actions", s -> """
""".formatted(s.getId(), s.getId())) .toJson(total); } // ----------------------------- // API: CREATE // ----------------------------- @PostMapping(value = "/api", consumes = "application/json") @ResponseBody public Map create(@RequestBody SerieFacturaPayload payload) { validate(payload); SerieFactura s = new SerieFactura(); s.setNombreSerie(payload.nombre_serie.trim()); s.setPrefijo(payload.prefijo.trim()); s.setTipo(TipoSerieFactura.facturacion); // fijo s.setNumeroActual(payload.numero_actual); repo.save(s); return Map.of("ok", true, "id", s.getId()); } // ----------------------------- // API: UPDATE // ----------------------------- @PutMapping(value = "/api/{id}", consumes = "application/json") @ResponseBody public Map update(@PathVariable Long id, @RequestBody SerieFacturaPayload payload) { validate(payload); SerieFactura s = repo.findById(id) .orElseThrow(() -> new EntityNotFoundException("Serie no encontrada: " + id)); if (s.getDeletedAt() != null) { throw new IllegalStateException("No se puede editar una serie eliminada."); } s.setNombreSerie(payload.nombre_serie.trim()); s.setPrefijo(payload.prefijo.trim()); s.setTipo(TipoSerieFactura.facturacion); s.setNumeroActual(payload.numero_actual); repo.save(s); return Map.of("ok", true); } // ----------------------------- // API: DELETE (soft) // ----------------------------- @DeleteMapping("/api/{id}") @ResponseBody public ResponseEntity delete(@PathVariable Long id) { SerieFactura s = repo.findById(id) .orElseThrow(() -> new EntityNotFoundException("Serie no encontrada: " + id)); if (s.getDeletedAt() == null) { s.setDeletedAt(Instant.now()); s.setDeletedBy(null); // luego lo conectamos al usuario actual repo.save(s); } return ResponseEntity.ok(Map.of("ok", true)); } // ----------------------------- // API: GET for select2 // ----------------------------- @GetMapping("/api/get-series") @ResponseBody public Map getSeriesForSelect( @RequestParam(value = "q", required = false) String q1, @RequestParam(value = "term", required = false) String q2, Locale locale) { String query = (q1 != null && !q1.isBlank()) ? q1 : (q2 != null && !q2.isBlank()) ? q2 : ""; List> results = repo.searchForSelectSeriesFacturacion(query).stream() .map(s -> { Map m = new HashMap<>(); m.put("id", s.getId()); m.put("text", s.getNombreSerie()); return m; }) .toList(); return Map.of("results", results); } // ----------------------------- // Payload + validación // ----------------------------- public static class SerieFacturaPayload { public String nombre_serie; public String prefijo; public String tipo; // lo manda UI, pero en backend lo fijamos public Long numero_actual; } private void validate(SerieFacturaPayload p) { if (p == null) throw new IllegalArgumentException("Body requerido."); if (p.nombre_serie == null || p.nombre_serie.trim().isBlank()) { throw new IllegalArgumentException("nombre_serie es obligatorio."); } if (p.prefijo == null || p.prefijo.trim().isBlank()) { throw new IllegalArgumentException("prefijo es obligatorio."); } if (p.prefijo.trim().length() > 10) { throw new IllegalArgumentException("prefijo máximo 10 caracteres."); } if (p.numero_actual == null || p.numero_actual < 1) { throw new IllegalArgumentException("numero_actual debe ser >= 1."); } } }