package com.imprimelibros.erp.facturacion.controller; import com.imprimelibros.erp.configurationERP.VariableService; 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.direcciones.DireccionService; import com.imprimelibros.erp.facturacion.EstadoFactura; import com.imprimelibros.erp.facturacion.Factura; import com.imprimelibros.erp.facturacion.FacturaDireccion; import com.imprimelibros.erp.facturacion.dto.FacturaAddRequestDto; import com.imprimelibros.erp.facturacion.dto.FacturaGuardarDto; import com.imprimelibros.erp.facturacion.dto.FacturaLineaUpsertDto; import com.imprimelibros.erp.facturacion.dto.FacturaPagoUpsertDto; import com.imprimelibros.erp.facturacion.repo.FacturaRepository; import com.imprimelibros.erp.facturacion.service.FacturacionService; import com.imprimelibros.erp.i18n.TranslationService; import com.imprimelibros.erp.pedidos.PedidoDireccion; import com.imprimelibros.erp.pedidos.PedidoService; import jakarta.persistence.EntityNotFoundException; import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; 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.security.core.Authentication; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; import java.security.Principal; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; @Controller @RequestMapping("/facturas") @PreAuthorize("hasRole('SUPERADMIN') || hasRole('ADMIN')") public class FacturasController { private final FacturacionService facturacionService; private final FacturaRepository repo; private final TranslationService translationService; private final MessageSource messageSource; private final PedidoService pedidoService; private final VariableService variableService; private final DireccionService direccionService; public FacturasController( FacturaRepository repo, TranslationService translationService, MessageSource messageSource, PedidoService pedidoService, FacturacionService facturacionService, VariableService variableService, DireccionService direccionService) { this.repo = repo; this.translationService = translationService; this.messageSource = messageSource; this.pedidoService = pedidoService; this.facturacionService = facturacionService; this.direccionService = direccionService; this.variableService = variableService; } @GetMapping public String facturasList(Model model, Locale locale) { List keys = List.of( "app.eliminar", "app.cancelar", "facturas.delete.title", "facturas.delete.text", "facturas.delete.ok.title", "facturas.delete.ok.text"); Map translations = translationService.getTranslations(locale, keys); model.addAttribute("languageBundle", translations); return "imprimelibros/facturas/facturas-list"; } @GetMapping("/add") public String facturaAdd(Model model, Locale locale) { List keys = List.of( "facturas.form.cliente.placeholder", "facturas.add.form.validation.title", "facturas.add.form.validation", "facturas.error.create" ); Map translations = translationService.getTranslations(locale, keys); model.addAttribute("languageBundle", translations); model.addAttribute("defaultSerieRectificativa", variableService.getValorEntero("serie_facturacion_rect_default")); return "imprimelibros/facturas/facturas-add-form"; } @PostMapping("/add") @ResponseBody public Map facturaAddPost( Model model, @RequestBody FacturaAddRequestDto request, Locale locale) { Factura nuevaFactura = facturacionService.crearNuevaFactura( request.getUser(), request.getSerie(), request.getDireccion(), request.getFactura_rectificada() ); Map result = new HashMap<>(); if(nuevaFactura == null){ result.put("success", false); result.put("message", messageSource.getMessage("facturas.error.create", null, "No se ha podido crear la factura. Revise los datos e inténtelo de nuevo.", locale)); return result; } else{ result.put("success", true); result.put("facturaId", nuevaFactura.getId()); return result; } } @GetMapping("/{id}") public String facturaDetail(@PathVariable Long id, Model model, Locale locale) { Factura factura = repo.findById(id) .orElseThrow(() -> new EntityNotFoundException("Factura no encontrada con ID: " + id)); List keys = List.of( "facturas.lineas.error.base", "facturas.lineas.delete.title", "facturas.lineas.delete.text", "facturas.pagos.delete.title", "facturas.pagos.delete.text", "facturas.pagos.error.cantidad", "facturas.pagos.error.fecha", "app.eliminar", "app.cancelar"); Map translations = translationService.getTranslations(locale, keys); model.addAttribute("languageBundle", translations); FacturaDireccion direccionFacturacion = factura.getDireccionFacturacion(); model.addAttribute("direccionFacturacion", direccionFacturacion); model.addAttribute("factura", factura); return "imprimelibros/facturas/facturas-form"; } @PostMapping("/{id}/guardar") public ResponseEntity guardarFacturaCabeceraYDireccion( @PathVariable Long id, @RequestBody @Valid FacturaGuardarDto payload) { facturacionService.guardarCabeceraYDireccionFacturacion(id, payload); return ResponseEntity.ok(Map.of("ok", true)); } @GetMapping("/{id}/container") public String facturaContainer(@PathVariable Long id, Model model, Locale locale) { Factura factura = repo.findById(id) .orElseThrow(() -> new EntityNotFoundException("Factura no encontrada con ID: " + id)); FacturaDireccion direccionFacturacion = factura.getDireccionFacturacion(); model.addAttribute("direccionFacturacion", direccionFacturacion); model.addAttribute("factura", factura); return "imprimelibros/facturas/partials/factura-container :: factura-container"; } @PostMapping("/{id}/validar") public ResponseEntity validarFactura(@PathVariable Long id) { Factura factura = repo.findById(id) .orElseThrow(() -> new EntityNotFoundException("Factura no encontrada con ID: " + id)); if (factura.getEstado() != EstadoFactura.borrador) { return ResponseEntity.badRequest().body("Solo se pueden validar facturas en estado 'borrador'."); } facturacionService.validarFactura(factura.getId()); repo.save(factura); return ResponseEntity.ok().build(); } @PostMapping("/{id}/borrador") public ResponseEntity marcarBorrador(@PathVariable Long id) { Factura factura = repo.findById(id) .orElseThrow(() -> new EntityNotFoundException("Factura no encontrada con ID: " + id)); if (factura.getEstado() != EstadoFactura.validada) { return ResponseEntity.badRequest() .body("Solo se pueden marcar como borrador facturas en estado 'validada'."); } factura.setEstado(EstadoFactura.borrador); repo.save(factura); return ResponseEntity.ok().build(); } @PostMapping("/{facturaId}/lineas") public ResponseEntity createLinea(@PathVariable Long facturaId, @Valid @RequestBody FacturaLineaUpsertDto req) { facturacionService.createLinea(facturaId, req); return ResponseEntity.ok(Map.of("ok", true)); } @PostMapping("/{facturaId}/lineas/{lineaId}") public ResponseEntity updateLinea(@PathVariable Long facturaId, @PathVariable Long lineaId, @Valid @RequestBody FacturaLineaUpsertDto req) { facturacionService.upsertLinea(facturaId, req); return ResponseEntity.ok(Map.of("ok", true)); } @PostMapping("/{facturaId}/lineas/{lineaId}/delete") public ResponseEntity deleteLinea(@PathVariable Long facturaId, @PathVariable Long lineaId) { facturacionService.borrarLinea(facturaId, lineaId); return ResponseEntity.ok(Map.of("ok", true)); } /* * ----------------------------- * Pagos * -------------------------------- */ @PostMapping("/{facturaId}/pagos") public ResponseEntity createPago( @PathVariable Long facturaId, @Valid @RequestBody FacturaPagoUpsertDto req, Principal principal) { facturacionService.upsertPago(facturaId, req, principal); return ResponseEntity.ok(Map.of("ok", true)); } @PostMapping("/{facturaId}/pagos/{pagoId}") public ResponseEntity updatePago( @PathVariable Long facturaId, @PathVariable Long pagoId, @Valid @RequestBody FacturaPagoUpsertDto req, Principal principal) { // opcional: fuerza consistencia req.setId(pagoId); facturacionService.upsertPago(facturaId, req, principal); return ResponseEntity.ok(Map.of("ok", true)); } @PostMapping("/{facturaId}/pagos/{pagoId}/delete") public ResponseEntity deletePago( @PathVariable Long facturaId, @PathVariable Long pagoId, Principal principal) { facturacionService.borrarPago(facturaId, pagoId, principal); return ResponseEntity.ok(Map.of("ok", true)); } @PostMapping("/{id}/notas") public ResponseEntity setNotas( @PathVariable Long id, @RequestBody Map payload, Model model, Locale locale) { Factura factura = repo.findById(id) .orElseThrow(() -> new EntityNotFoundException("Factura no encontrada con ID: " + id)); String notas = payload.get("notas"); factura.setNotas(notas); repo.save(factura); return ResponseEntity.ok().build(); } // ----------------------------- // 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, Factura.class, dt, List.of("clienteNombre", "numeroFactura", "estado", "estadoPago")) .where(notDeleted) .orderable(List.of("id", "clienteNombre", "numeroFactura", "estado", "estadoPago")) .onlyAddedColumns() .add("id", Factura::getId) .add("cliente", f -> { var c = f.getCliente(); return c == null ? null : c.getFullName(); // o getNombre(), etc. }) .add("numero_factura", Factura::getNumeroFactura) .add("estado", Factura::getEstado) .add("estado_label", f -> { String key = "facturas.estado." + f.getEstado().name().toLowerCase(); return messageSource.getMessage(key, null, f.getEstado().name(), locale); }) .add("estado_pago", Factura::getEstadoPago) .add("estado_pago_label", f -> { String key = "facturas.estado-pago." + f.getEstadoPago().name().toLowerCase(); return messageSource.getMessage(key, null, f.getEstadoPago().name(), locale); }) .add("total", Factura::getTotalFactura) .add("fecha_emision", f -> { LocalDateTime fecha = f.getFechaEmision(); return fecha == null ? null : fecha.format(DateTimeFormatter.ofPattern("dd/MM/yyyy")); }) .add("actions", f -> { if (f.getEstado() == EstadoFactura.borrador) { return """
""".formatted(f.getId(), f.getId()); } else { return """
""".formatted(f.getId()); } }) .toJson(total); } // ----------------------------- // API: select2 Direcciones // ----------------------------- @GetMapping("/api/get-direcciones") @ResponseBody public Map getSelect2Facturacion( @RequestParam(value = "q", required = false) String q1, @RequestParam(value = "term", required = false) String q2, @RequestParam(value = "user_id", required = true) Long userId, Authentication auth) { return direccionService.getForSelectFacturacion(q1, q2, userId); } // ----------------------------- // API: select2 facturas rectificables // ----------------------------- @GetMapping("/api/get-facturas-rectificables") @ResponseBody public Map getSelect2FacturasRectificables( @RequestParam(value = "q", required = false) String q1, @RequestParam(value = "term", required = false) String q2, @RequestParam(value = "user_id", required = true) Long userId, Authentication auth) { try { } catch (Exception e) { e.printStackTrace(); return Map.of("results", List.of()); } return facturacionService.getForSelectFacturasRectificables(q1, q2, userId); } }