mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-29 23:28:51 +00:00
trabajando en el formulario de la factura
This commit is contained in:
16734
logs/erp.log
16734
logs/erp.log
File diff suppressed because one or more lines are too long
@ -9,13 +9,12 @@ import java.time.LocalDateTime;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.Formula;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(
|
@Table(name = "facturas", uniqueConstraints = {
|
||||||
name = "facturas",
|
|
||||||
uniqueConstraints = {
|
|
||||||
@UniqueConstraint(name = "uq_facturas_numero_factura", columnNames = "numero_factura")
|
@UniqueConstraint(name = "uq_facturas_numero_factura", columnNames = "numero_factura")
|
||||||
}
|
})
|
||||||
)
|
|
||||||
public class Factura extends AbstractAuditedEntitySoftTs {
|
public class Factura extends AbstractAuditedEntitySoftTs {
|
||||||
|
|
||||||
@Column(name = "pedido_id")
|
@Column(name = "pedido_id")
|
||||||
@ -80,11 +79,15 @@ public class Factura extends AbstractAuditedEntitySoftTs {
|
|||||||
@OneToMany(mappedBy = "factura", cascade = CascadeType.ALL, orphanRemoval = true)
|
@OneToMany(mappedBy = "factura", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||||
private List<FacturaPago> pagos = new ArrayList<>();
|
private List<FacturaPago> pagos = new ArrayList<>();
|
||||||
|
|
||||||
|
@Formula("(select u.fullname from users u where u.id = cliente_id)")
|
||||||
|
private String clienteNombre;
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
public void addLinea(FacturaLinea linea) {
|
public void addLinea(FacturaLinea linea) {
|
||||||
linea.setFactura(this);
|
linea.setFactura(this);
|
||||||
this.lineas.add(linea);
|
this.lineas.add(linea);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeLinea(FacturaLinea linea) {
|
public void removeLinea(FacturaLinea linea) {
|
||||||
this.lineas.remove(linea);
|
this.lineas.remove(linea);
|
||||||
linea.setFactura(null);
|
linea.setFactura(null);
|
||||||
@ -94,63 +97,154 @@ public class Factura extends AbstractAuditedEntitySoftTs {
|
|||||||
pago.setFactura(this);
|
pago.setFactura(this);
|
||||||
this.pagos.add(pago);
|
this.pagos.add(pago);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removePago(FacturaPago pago) {
|
public void removePago(FacturaPago pago) {
|
||||||
this.pagos.remove(pago);
|
this.pagos.remove(pago);
|
||||||
pago.setFactura(null);
|
pago.setFactura(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters/Setters
|
// Getters/Setters
|
||||||
public Long getPedidoId() { return pedidoId; }
|
public Long getPedidoId() {
|
||||||
public void setPedidoId(Long pedidoId) { this.pedidoId = pedidoId; }
|
return pedidoId;
|
||||||
|
}
|
||||||
|
|
||||||
public Factura getFacturaRectificada() { return facturaRectificada; }
|
public void setPedidoId(Long pedidoId) {
|
||||||
public void setFacturaRectificada(Factura facturaRectificada) { this.facturaRectificada = facturaRectificada; }
|
this.pedidoId = pedidoId;
|
||||||
|
}
|
||||||
|
|
||||||
public Factura getFacturaRectificativa() { return facturaRectificativa; }
|
public Factura getFacturaRectificada() {
|
||||||
public void setFacturaRectificativa(Factura facturaRectificativa) { this.facturaRectificativa = facturaRectificativa; }
|
return facturaRectificada;
|
||||||
|
}
|
||||||
|
|
||||||
public User getCliente() { return cliente; }
|
public void setFacturaRectificada(Factura facturaRectificada) {
|
||||||
public void setCliente(User cliente) { this.cliente = cliente; }
|
this.facturaRectificada = facturaRectificada;
|
||||||
|
}
|
||||||
|
|
||||||
public SerieFactura getSerie() { return serie; }
|
public Factura getFacturaRectificativa() {
|
||||||
public void setSerie(SerieFactura serie) { this.serie = serie; }
|
return facturaRectificativa;
|
||||||
|
}
|
||||||
|
|
||||||
public String getNumeroFactura() { return numeroFactura; }
|
public void setFacturaRectificativa(Factura facturaRectificativa) {
|
||||||
public void setNumeroFactura(String numeroFactura) { this.numeroFactura = numeroFactura; }
|
this.facturaRectificativa = facturaRectificativa;
|
||||||
|
}
|
||||||
|
|
||||||
public EstadoFactura getEstado() { return estado; }
|
public User getCliente() {
|
||||||
public void setEstado(EstadoFactura estado) { this.estado = estado; }
|
return cliente;
|
||||||
|
}
|
||||||
|
|
||||||
public EstadoPagoFactura getEstadoPago() { return estadoPago; }
|
public void setCliente(User cliente) {
|
||||||
public void setEstadoPago(EstadoPagoFactura estadoPago) { this.estadoPago = estadoPago; }
|
this.cliente = cliente;
|
||||||
|
}
|
||||||
|
|
||||||
public TipoPago getTipoPago() { return tipoPago; }
|
public SerieFactura getSerie() {
|
||||||
public void setTipoPago(TipoPago tipoPago) { this.tipoPago = tipoPago; }
|
return serie;
|
||||||
|
}
|
||||||
|
|
||||||
public LocalDateTime getFechaEmision() { return fechaEmision; }
|
public void setSerie(SerieFactura serie) {
|
||||||
public void setFechaEmision(LocalDateTime fechaEmision) { this.fechaEmision = fechaEmision; }
|
this.serie = serie;
|
||||||
|
}
|
||||||
|
|
||||||
public BigDecimal getBaseImponible() { return baseImponible; }
|
public String getNumeroFactura() {
|
||||||
public void setBaseImponible(BigDecimal baseImponible) { this.baseImponible = baseImponible; }
|
return numeroFactura;
|
||||||
|
}
|
||||||
|
|
||||||
public BigDecimal getIva4() { return iva4; }
|
public void setNumeroFactura(String numeroFactura) {
|
||||||
public void setIva4(BigDecimal iva4) { this.iva4 = iva4; }
|
this.numeroFactura = numeroFactura;
|
||||||
|
}
|
||||||
|
|
||||||
public BigDecimal getIva21() { return iva21; }
|
public EstadoFactura getEstado() {
|
||||||
public void setIva21(BigDecimal iva21) { this.iva21 = iva21; }
|
return estado;
|
||||||
|
}
|
||||||
|
|
||||||
public BigDecimal getTotalFactura() { return totalFactura; }
|
public void setEstado(EstadoFactura estado) {
|
||||||
public void setTotalFactura(BigDecimal totalFactura) { this.totalFactura = totalFactura; }
|
this.estado = estado;
|
||||||
|
}
|
||||||
|
|
||||||
public BigDecimal getTotalPagado() { return totalPagado; }
|
public EstadoPagoFactura getEstadoPago() {
|
||||||
public void setTotalPagado(BigDecimal totalPagado) { this.totalPagado = totalPagado; }
|
return estadoPago;
|
||||||
|
}
|
||||||
|
|
||||||
public String getNotas() { return notas; }
|
public void setEstadoPago(EstadoPagoFactura estadoPago) {
|
||||||
public void setNotas(String notas) { this.notas = notas; }
|
this.estadoPago = estadoPago;
|
||||||
|
}
|
||||||
|
|
||||||
public List<FacturaLinea> getLineas() { return lineas; }
|
public TipoPago getTipoPago() {
|
||||||
public void setLineas(List<FacturaLinea> lineas) { this.lineas = lineas; }
|
return tipoPago;
|
||||||
|
}
|
||||||
|
|
||||||
public List<FacturaPago> getPagos() { return pagos; }
|
public void setTipoPago(TipoPago tipoPago) {
|
||||||
public void setPagos(List<FacturaPago> pagos) { this.pagos = pagos; }
|
this.tipoPago = tipoPago;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getFechaEmision() {
|
||||||
|
return fechaEmision;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFechaEmision(LocalDateTime fechaEmision) {
|
||||||
|
this.fechaEmision = fechaEmision;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getBaseImponible() {
|
||||||
|
return baseImponible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBaseImponible(BigDecimal baseImponible) {
|
||||||
|
this.baseImponible = baseImponible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getIva4() {
|
||||||
|
return iva4;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIva4(BigDecimal iva4) {
|
||||||
|
this.iva4 = iva4;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getIva21() {
|
||||||
|
return iva21;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIva21(BigDecimal iva21) {
|
||||||
|
this.iva21 = iva21;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getTotalFactura() {
|
||||||
|
return totalFactura;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTotalFactura(BigDecimal totalFactura) {
|
||||||
|
this.totalFactura = totalFactura;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getTotalPagado() {
|
||||||
|
return totalPagado;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTotalPagado(BigDecimal totalPagado) {
|
||||||
|
this.totalPagado = totalPagado;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNotas() {
|
||||||
|
return notas;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNotas(String notas) {
|
||||||
|
this.notas = notas;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<FacturaLinea> getLineas() {
|
||||||
|
return lineas;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLineas(List<FacturaLinea> lineas) {
|
||||||
|
this.lineas = lineas;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<FacturaPago> getPagos() {
|
||||||
|
return pagos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPagos(List<FacturaPago> pagos) {
|
||||||
|
this.pagos = pagos;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,146 @@
|
|||||||
|
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.EstadoFactura;
|
||||||
|
import com.imprimelibros.erp.facturacion.Factura;
|
||||||
|
import com.imprimelibros.erp.facturacion.SerieFactura;
|
||||||
|
import com.imprimelibros.erp.facturacion.TipoSerieFactura;
|
||||||
|
import com.imprimelibros.erp.facturacion.repo.FacturaRepository;
|
||||||
|
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.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
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 FacturaRepository repo;
|
||||||
|
private final TranslationService translationService;
|
||||||
|
private final MessageSource messageSource;
|
||||||
|
|
||||||
|
public FacturasController(
|
||||||
|
FacturaRepository repo,
|
||||||
|
TranslationService translationService,
|
||||||
|
MessageSource messageSource) {
|
||||||
|
this.repo = repo;
|
||||||
|
this.translationService = translationService;
|
||||||
|
this.messageSource = messageSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public String facturasList(Model model, Locale locale) {
|
||||||
|
|
||||||
|
List<String> keys = List.of(
|
||||||
|
"app.eliminar",
|
||||||
|
"app.cancelar",
|
||||||
|
"facturas.delete.title",
|
||||||
|
"facturas.delete.text",
|
||||||
|
"facturas.delete.ok.title",
|
||||||
|
"facturas.delete.ok.text");
|
||||||
|
Map<String, String> translations = translationService.getTranslations(locale, keys);
|
||||||
|
model.addAttribute("languageBundle", translations);
|
||||||
|
return "imprimelibros/facturas/facturas-list";
|
||||||
|
}
|
||||||
|
|
||||||
|
@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));
|
||||||
|
|
||||||
|
model.addAttribute("factura", factura);
|
||||||
|
|
||||||
|
return "imprimelibros/facturas/facturas-form";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -----------------------------
|
||||||
|
// API: DataTables (server-side)
|
||||||
|
// -----------------------------
|
||||||
|
@GetMapping("/api/datatables")
|
||||||
|
@ResponseBody
|
||||||
|
public DataTablesResponse<Map<String, Object>> datatables(HttpServletRequest request, Locale locale) {
|
||||||
|
|
||||||
|
DataTablesRequest dt = DataTablesParser.from(request);
|
||||||
|
|
||||||
|
Specification<Factura> 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 """
|
||||||
|
<div class="hstack gap-3 flex-wrap">
|
||||||
|
<button type="button"
|
||||||
|
class="btn p-0 link-success btn-view-factura fs-15"
|
||||||
|
data-id="%d">
|
||||||
|
<i class="ri-eye-line"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button"
|
||||||
|
class="btn p-0 link-danger btn-delete-factura fs-15"
|
||||||
|
data-id="%d">
|
||||||
|
<i class="ri-delete-bin-5-line"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
""".formatted(f.getId(), f.getId());
|
||||||
|
} else {
|
||||||
|
return """
|
||||||
|
<div class="hstack gap-3 flex-wrap">
|
||||||
|
<button type="button"
|
||||||
|
class="btn p-0 link-success btn-view-factura fs-15"
|
||||||
|
data-id="%d">
|
||||||
|
<i class="ri-eye-line"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
""".formatted(f.getId());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toJson(total);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -98,12 +98,12 @@ public class SeriesFacturacionController {
|
|||||||
.add("actions", s -> """
|
.add("actions", s -> """
|
||||||
<div class="hstack gap-3 flex-wrap">
|
<div class="hstack gap-3 flex-wrap">
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="btn btn-link p-0 link-success btn-edit-serie fs-15"
|
class="btn p-0 link-success btn-edit-serie fs-15"
|
||||||
data-id="%d">
|
data-id="%d">
|
||||||
<i class="ri-edit-2-line"></i>
|
<i class="ri-edit-2-line"></i>
|
||||||
</button>
|
</button>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="btn btn-link p-0 link-danger btn-delete-serie fs-15"
|
class="btn p-0 link-danger btn-delete-serie fs-15"
|
||||||
data-id="%d">
|
data-id="%d">
|
||||||
<i class="ri-delete-bin-5-line"></i>
|
<i class="ri-delete-bin-5-line"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -2,9 +2,10 @@ package com.imprimelibros.erp.facturacion.repo;
|
|||||||
|
|
||||||
import com.imprimelibros.erp.facturacion.Factura;
|
import com.imprimelibros.erp.facturacion.Factura;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public interface FacturaRepository extends JpaRepository<Factura, Long> {
|
public interface FacturaRepository extends JpaRepository<Factura, Long>, JpaSpecificationExecutor<Factura> {
|
||||||
Optional<Factura> findByNumeroFactura(String numeroFactura);
|
Optional<Factura> findByNumeroFactura(String numeroFactura);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,31 @@
|
|||||||
package com.imprimelibros.erp.facturacion.service;
|
package com.imprimelibros.erp.facturacion.service;
|
||||||
|
|
||||||
|
import com.imprimelibros.erp.common.Utils;
|
||||||
import com.imprimelibros.erp.facturacion.*;
|
import com.imprimelibros.erp.facturacion.*;
|
||||||
import com.imprimelibros.erp.facturacion.dto.FacturaLineaUpsertDto;
|
import com.imprimelibros.erp.facturacion.dto.FacturaLineaUpsertDto;
|
||||||
import com.imprimelibros.erp.facturacion.dto.FacturaPagoUpsertDto;
|
import com.imprimelibros.erp.facturacion.dto.FacturaPagoUpsertDto;
|
||||||
import com.imprimelibros.erp.facturacion.repo.FacturaPagoRepository;
|
import com.imprimelibros.erp.facturacion.repo.FacturaPagoRepository;
|
||||||
import com.imprimelibros.erp.facturacion.repo.FacturaRepository;
|
import com.imprimelibros.erp.facturacion.repo.FacturaRepository;
|
||||||
import com.imprimelibros.erp.facturacion.repo.SerieFacturaRepository;
|
import com.imprimelibros.erp.facturacion.repo.SerieFacturaRepository;
|
||||||
|
import com.imprimelibros.erp.pedidos.Pedido;
|
||||||
|
import com.imprimelibros.erp.pedidos.PedidoLinea;
|
||||||
|
import com.imprimelibros.erp.pedidos.PedidoLineaRepository;
|
||||||
|
import com.imprimelibros.erp.presupuesto.dto.Presupuesto;
|
||||||
|
|
||||||
import jakarta.persistence.EntityNotFoundException;
|
import jakarta.persistence.EntityNotFoundException;
|
||||||
|
|
||||||
|
import org.springframework.context.MessageSource;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Locale;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@ -20,21 +34,121 @@ public class FacturacionService {
|
|||||||
private final FacturaRepository facturaRepo;
|
private final FacturaRepository facturaRepo;
|
||||||
private final SerieFacturaRepository serieRepo;
|
private final SerieFacturaRepository serieRepo;
|
||||||
private final FacturaPagoRepository pagoRepo;
|
private final FacturaPagoRepository pagoRepo;
|
||||||
|
private final PedidoLineaRepository pedidoLineaRepo;
|
||||||
|
private final Utils utils;
|
||||||
|
private final MessageSource messageSource;
|
||||||
|
|
||||||
public FacturacionService(
|
public FacturacionService(
|
||||||
FacturaRepository facturaRepo,
|
FacturaRepository facturaRepo,
|
||||||
SerieFacturaRepository serieRepo,
|
SerieFacturaRepository serieRepo,
|
||||||
FacturaPagoRepository pagoRepo
|
FacturaPagoRepository pagoRepo,
|
||||||
) {
|
PedidoLineaRepository pedidoLineaRepo,
|
||||||
|
Utils utils,
|
||||||
|
MessageSource messageSource) {
|
||||||
this.facturaRepo = facturaRepo;
|
this.facturaRepo = facturaRepo;
|
||||||
this.serieRepo = serieRepo;
|
this.serieRepo = serieRepo;
|
||||||
this.pagoRepo = pagoRepo;
|
this.pagoRepo = pagoRepo;
|
||||||
|
this.pedidoLineaRepo = pedidoLineaRepo;
|
||||||
|
this.utils = utils;
|
||||||
|
this.messageSource = messageSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public SerieFactura getDefaultSerieFactura() {
|
||||||
|
List<SerieFactura> series = serieRepo.findAll();
|
||||||
|
if (series.isEmpty()) {
|
||||||
|
throw new IllegalStateException("No hay ninguna serie de facturación configurada.");
|
||||||
|
}
|
||||||
|
// Aquí simplemente devolvemos la primera. Puedes implementar lógica más
|
||||||
|
// compleja si es necesario.
|
||||||
|
return series.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------
|
||||||
|
// Nueva factura
|
||||||
|
// -----------------------
|
||||||
|
@Transactional
|
||||||
|
public Factura crearNuevaFacturaAuto(Pedido pedido, SerieFactura serie, TipoPago tipoPago, Locale locale) {
|
||||||
|
|
||||||
|
Factura factura = new Factura();
|
||||||
|
factura.setCliente(pedido.getCreatedBy());
|
||||||
|
factura.setCreatedAt(Instant.now());
|
||||||
|
factura.setUpdatedAt(Instant.now());
|
||||||
|
Boolean pedidoPendientePago = false;
|
||||||
|
List<PedidoLinea> lineasPedido = pedidoLineaRepo.findByPedidoId(pedido.getId());
|
||||||
|
for (PedidoLinea lineaPedido : lineasPedido) {
|
||||||
|
if (lineaPedido.getEstado() == PedidoLinea.Estado.pendiente_pago) {
|
||||||
|
pedidoPendientePago = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
factura.setEstado(pedidoPendientePago ? EstadoFactura.borrador : EstadoFactura.validada);
|
||||||
|
factura.setEstadoPago(pedidoPendientePago ? EstadoPagoFactura.pendiente : EstadoPagoFactura.pagada);
|
||||||
|
factura.setTipoPago(pedidoPendientePago ? TipoPago.otros : tipoPago);
|
||||||
|
factura.setPedidoId(pedido.getId());
|
||||||
|
factura.setSerie(serie);
|
||||||
|
factura.setNumeroFactura(this.getNumberFactura(serie));
|
||||||
|
factura.setFechaEmision(LocalDateTime.now());
|
||||||
|
factura.setBaseImponible(BigDecimal.valueOf(pedido.getBase()).setScale(2, RoundingMode.HALF_UP));
|
||||||
|
factura.setIva4(BigDecimal.valueOf(pedido.getIva4()).setScale(2, RoundingMode.HALF_UP));
|
||||||
|
factura.setIva21(BigDecimal.valueOf(pedido.getIva21()).setScale(2, RoundingMode.HALF_UP));
|
||||||
|
factura.setTotalFactura(BigDecimal.valueOf(pedido.getTotal()).setScale(2, RoundingMode.HALF_UP));
|
||||||
|
factura.setTotalPagado(BigDecimal.valueOf(pedido.getTotal()).setScale(2, RoundingMode.HALF_UP));
|
||||||
|
// rellenar lineas
|
||||||
|
List<FacturaLinea> lineasFactura = new ArrayList<>();
|
||||||
|
for (PedidoLinea lineaPedido : lineasPedido) {
|
||||||
|
Presupuesto p = lineaPedido.getPresupuesto();
|
||||||
|
FacturaLinea lineaFactura = new FacturaLinea();
|
||||||
|
lineaFactura.setDescripcion(this.obtenerLineaFactura(lineaPedido, locale));
|
||||||
|
lineaFactura.setCantidad(p.getSelectedTirada());
|
||||||
|
lineaFactura.setBaseLinea(p.getBaseImponible());
|
||||||
|
lineaFactura.setIva4Linea(p.getIvaImporte4());
|
||||||
|
lineaFactura.setIva21Linea(p.getIvaImporte21());
|
||||||
|
lineaFactura.setTotalLinea(p.getTotalConIva());
|
||||||
|
lineaFactura.setCreatedBy(p.getUser());
|
||||||
|
lineaFactura.setFactura(factura);
|
||||||
|
lineasFactura.add(lineaFactura);
|
||||||
|
}
|
||||||
|
factura.setLineas(lineasFactura);
|
||||||
|
|
||||||
|
factura = facturaRepo.save(factura);
|
||||||
|
|
||||||
|
if(pedidoPendientePago) {
|
||||||
|
return factura;
|
||||||
|
}
|
||||||
|
FacturaPago pago = new FacturaPago();
|
||||||
|
pago.setMetodoPago(tipoPago);
|
||||||
|
pago.setCantidadPagada(factura.getTotalFactura());
|
||||||
|
pago.setFechaPago(LocalDateTime.now());
|
||||||
|
pago.setFactura(factura);
|
||||||
|
pago.setCreatedBy(pedido.getCreatedBy());
|
||||||
|
pago.setCreatedAt(Instant.now());
|
||||||
|
pagoRepo.save(pago);
|
||||||
|
|
||||||
|
return factura;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------
|
// -----------------------
|
||||||
// Estado / Numeración
|
// Estado / Numeración
|
||||||
// -----------------------
|
// -----------------------
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public String getNumberFactura(SerieFactura serie) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
long next = (serie.getNumeroActual() == null) ? 1L : serie.getNumeroActual();
|
||||||
|
String numeroFactura = buildNumeroFactura(serie.getPrefijo(), next);
|
||||||
|
|
||||||
|
// Incrementar contador para la siguiente
|
||||||
|
serie.setNumeroActual(next + 1);
|
||||||
|
serieRepo.save(serie);
|
||||||
|
|
||||||
|
return numeroFactura;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public Factura validarFactura(Long facturaId) {
|
public Factura validarFactura(Long facturaId) {
|
||||||
Factura factura = facturaRepo.findById(facturaId)
|
Factura factura = facturaRepo.findById(facturaId)
|
||||||
@ -56,7 +170,8 @@ public class FacturacionService {
|
|||||||
// Si ya tiene numero_factura, no reservamos otro
|
// Si ya tiene numero_factura, no reservamos otro
|
||||||
if (factura.getNumeroFactura() == null || factura.getNumeroFactura().isBlank()) {
|
if (factura.getNumeroFactura() == null || factura.getNumeroFactura().isBlank()) {
|
||||||
SerieFactura serieLocked = serieRepo.findByIdForUpdate(factura.getSerie().getId())
|
SerieFactura serieLocked = serieRepo.findByIdForUpdate(factura.getSerie().getId())
|
||||||
.orElseThrow(() -> new EntityNotFoundException("Serie no encontrada: " + factura.getSerie().getId()));
|
.orElseThrow(
|
||||||
|
() -> new EntityNotFoundException("Serie no encontrada: " + factura.getSerie().getId()));
|
||||||
|
|
||||||
long next = (serieLocked.getNumeroActual() == null) ? 1L : serieLocked.getNumeroActual();
|
long next = (serieLocked.getNumeroActual() == null) ? 1L : serieLocked.getNumeroActual();
|
||||||
String numeroFactura = buildNumeroFactura(serieLocked.getPrefijo(), next);
|
String numeroFactura = buildNumeroFactura(serieLocked.getPrefijo(), next);
|
||||||
@ -89,7 +204,7 @@ public class FacturacionService {
|
|||||||
private String buildNumeroFactura(String prefijo, long numero) {
|
private String buildNumeroFactura(String prefijo, long numero) {
|
||||||
String pref = (prefijo == null) ? "" : prefijo.trim();
|
String pref = (prefijo == null) ? "" : prefijo.trim();
|
||||||
String num = String.format("%07d", numero);
|
String num = String.format("%07d", numero);
|
||||||
return pref.isBlank() ? num : (pref + "-" + num);
|
return pref.isBlank() ? num : (pref + " " + num + "/" + LocalDate.now().getYear());
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------
|
// -----------------------
|
||||||
@ -191,7 +306,8 @@ public class FacturacionService {
|
|||||||
pago.setFechaPago(dto.getFechaPago() != null ? dto.getFechaPago() : LocalDateTime.now());
|
pago.setFechaPago(dto.getFechaPago() != null ? dto.getFechaPago() : LocalDateTime.now());
|
||||||
pago.setNotas(dto.getNotas());
|
pago.setNotas(dto.getNotas());
|
||||||
|
|
||||||
// El tipo_pago de la factura: si tiene un pago, lo reflejamos (último pago manda)
|
// El tipo_pago de la factura: si tiene un pago, lo reflejamos (último pago
|
||||||
|
// manda)
|
||||||
factura.setTipoPago(dto.getMetodoPago());
|
factura.setTipoPago(dto.getMetodoPago());
|
||||||
|
|
||||||
recalcularTotales(factura);
|
recalcularTotales(factura);
|
||||||
@ -254,7 +370,8 @@ public class FacturacionService {
|
|||||||
factura.setTotalPagado(scale2(pagado));
|
factura.setTotalPagado(scale2(pagado));
|
||||||
|
|
||||||
// estado_pago
|
// estado_pago
|
||||||
// - cancelada: si la factura está marcada como cancelada manualmente (aquí NO lo hacemos automático)
|
// - cancelada: si la factura está marcada como cancelada manualmente (aquí NO
|
||||||
|
// lo hacemos automático)
|
||||||
// - pagada: si total_pagado >= total_factura y total_factura > 0
|
// - pagada: si total_pagado >= total_factura y total_factura > 0
|
||||||
// - pendiente: resto
|
// - pendiente: resto
|
||||||
if (factura.getEstadoPago() == EstadoPagoFactura.cancelada) {
|
if (factura.getEstadoPago() == EstadoPagoFactura.cancelada) {
|
||||||
@ -277,4 +394,90 @@ public class FacturacionService {
|
|||||||
private static BigDecimal scale2(BigDecimal v) {
|
private static BigDecimal scale2(BigDecimal v) {
|
||||||
return (v == null ? BigDecimal.ZERO : v).setScale(2, RoundingMode.HALF_UP);
|
return (v == null ? BigDecimal.ZERO : v).setScale(2, RoundingMode.HALF_UP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String obtenerLineaFactura(PedidoLinea lineaPedido, Locale locale) {
|
||||||
|
|
||||||
|
Map<String, Object> specs = utils.getTextoPresupuesto(lineaPedido.getPresupuesto(), locale);
|
||||||
|
|
||||||
|
StringBuilder html = new StringBuilder();
|
||||||
|
html.append("<div class=\"specs-wrapper align-with-text \">")
|
||||||
|
.append("<div class=\"specs\">");
|
||||||
|
|
||||||
|
if (specs == null) {
|
||||||
|
return "<div></div>";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1) Líneas del presupuesto (HTML)
|
||||||
|
Object lineasObj = specs.get("lineas");
|
||||||
|
if (lineasObj instanceof List<?> lineasList) {
|
||||||
|
for (Object o : lineasList) {
|
||||||
|
if (!(o instanceof Map<?, ?> m))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Object descObj = m.get("descripcion");
|
||||||
|
String descripcionHtml = descObj != null ? descObj.toString() : "";
|
||||||
|
if (descripcionHtml.isBlank())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
html.append("<div class=\"spec-row mb-1\">")
|
||||||
|
.append("<span class=\"spec-label\">")
|
||||||
|
.append(descripcionHtml) // OJO: esto es HTML (como th:utext)
|
||||||
|
.append("</span>")
|
||||||
|
.append("</div>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Servicios adicionales (texto)
|
||||||
|
Object serviciosObj = specs.get("servicios");
|
||||||
|
String servicios = (serviciosObj != null) ? serviciosObj.toString().trim() : "";
|
||||||
|
if (!servicios.isBlank()) {
|
||||||
|
String label = messageSource.getMessage("pdf.servicios-adicionales", null, "Servicios adicionales", locale);
|
||||||
|
html.append("<div class=\"spec-row mb-1\">")
|
||||||
|
.append("<span>").append(escapeHtml(label)).append("</span>")
|
||||||
|
.append("<span class=\"spec-label\">").append(escapeHtml(servicios)).append("</span>")
|
||||||
|
.append("</div>");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Datos de maquetación (HTML)
|
||||||
|
Object datosMaqObj = specs.get("datosMaquetacion");
|
||||||
|
if (datosMaqObj != null && !datosMaqObj.toString().isBlank()) {
|
||||||
|
String label = messageSource.getMessage("pdf.datos-maquetacion", null, "Datos de maquetación:", locale);
|
||||||
|
html.append("<div class=\"spec-row mb-1\">")
|
||||||
|
.append("<span>").append(escapeHtml(label)).append("</span>")
|
||||||
|
.append("<span class=\"spec-label\">")
|
||||||
|
.append(datosMaqObj) // HTML (como th:utext)
|
||||||
|
.append("</span>")
|
||||||
|
.append("</div>");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4) Datos de marcapáginas (HTML)
|
||||||
|
Object datosMarcaObj = specs.get("datosMarcapaginas");
|
||||||
|
if (datosMarcaObj != null && !datosMarcaObj.toString().isBlank()) {
|
||||||
|
String label = messageSource.getMessage("pdf.datos-marcapaginas", null, "Datos de marcapáginas:", locale);
|
||||||
|
html.append("<div class=\"spec-row mb-1\">")
|
||||||
|
.append("<span>").append(escapeHtml(label)).append("</span>")
|
||||||
|
.append("<span class=\"spec-label\">")
|
||||||
|
.append(datosMarcaObj) // HTML (como th:utext)
|
||||||
|
.append("</span>")
|
||||||
|
.append("</div>");
|
||||||
|
}
|
||||||
|
|
||||||
|
html.append("</div></div>");
|
||||||
|
return html.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escape mínimo para texto plano (equivalente a th:text).
|
||||||
|
* No lo uses para fragmentos que ya son HTML (th:utext).
|
||||||
|
*/
|
||||||
|
private static String escapeHtml(String s) {
|
||||||
|
if (s == null)
|
||||||
|
return "";
|
||||||
|
return s.replace("&", "&")
|
||||||
|
.replace("<", "<")
|
||||||
|
.replace(">", ">")
|
||||||
|
.replace("\"", """)
|
||||||
|
.replace("'", "'");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,9 @@ package com.imprimelibros.erp.payments;
|
|||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.imprimelibros.erp.cart.Cart;
|
import com.imprimelibros.erp.cart.Cart;
|
||||||
import com.imprimelibros.erp.cart.CartService;
|
import com.imprimelibros.erp.cart.CartService;
|
||||||
|
import com.imprimelibros.erp.facturacion.SerieFactura;
|
||||||
|
import com.imprimelibros.erp.facturacion.TipoPago;
|
||||||
|
import com.imprimelibros.erp.facturacion.service.FacturacionService;
|
||||||
import com.imprimelibros.erp.payments.model.*;
|
import com.imprimelibros.erp.payments.model.*;
|
||||||
import com.imprimelibros.erp.payments.repo.PaymentRepository;
|
import com.imprimelibros.erp.payments.repo.PaymentRepository;
|
||||||
import com.imprimelibros.erp.payments.repo.PaymentTransactionRepository;
|
import com.imprimelibros.erp.payments.repo.PaymentTransactionRepository;
|
||||||
@ -33,6 +36,7 @@ public class PaymentService {
|
|||||||
private final ObjectMapper om = new ObjectMapper();
|
private final ObjectMapper om = new ObjectMapper();
|
||||||
private final CartService cartService;
|
private final CartService cartService;
|
||||||
private final PedidoService pedidoService;
|
private final PedidoService pedidoService;
|
||||||
|
private final FacturacionService facturacionService;
|
||||||
|
|
||||||
public PaymentService(PaymentRepository payRepo,
|
public PaymentService(PaymentRepository payRepo,
|
||||||
PaymentTransactionRepository txRepo,
|
PaymentTransactionRepository txRepo,
|
||||||
@ -40,7 +44,8 @@ public class PaymentService {
|
|||||||
RedsysService redsysService,
|
RedsysService redsysService,
|
||||||
WebhookEventRepository webhookEventRepo,
|
WebhookEventRepository webhookEventRepo,
|
||||||
CartService cartService,
|
CartService cartService,
|
||||||
PedidoService pedidoService) {
|
PedidoService pedidoService,
|
||||||
|
FacturacionService facturacionService) {
|
||||||
this.payRepo = payRepo;
|
this.payRepo = payRepo;
|
||||||
this.txRepo = txRepo;
|
this.txRepo = txRepo;
|
||||||
this.refundRepo = refundRepo;
|
this.refundRepo = refundRepo;
|
||||||
@ -48,6 +53,7 @@ public class PaymentService {
|
|||||||
this.webhookEventRepo = webhookEventRepo;
|
this.webhookEventRepo = webhookEventRepo;
|
||||||
this.cartService = cartService;
|
this.cartService = cartService;
|
||||||
this.pedidoService = pedidoService;
|
this.pedidoService = pedidoService;
|
||||||
|
this.facturacionService = facturacionService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Payment findFailedPaymentByOrderId(Long orderId) {
|
public Payment findFailedPaymentByOrderId(Long orderId) {
|
||||||
@ -253,6 +259,11 @@ public class PaymentService {
|
|||||||
p.setCapturedAt(LocalDateTime.now());
|
p.setCapturedAt(LocalDateTime.now());
|
||||||
pedidoService.setOrderAsPaid(p.getOrderId());
|
pedidoService.setOrderAsPaid(p.getOrderId());
|
||||||
|
|
||||||
|
Pedido pedido = pedidoService.getPedidoById(p.getOrderId());
|
||||||
|
SerieFactura serie = facturacionService.getDefaultSerieFactura();
|
||||||
|
|
||||||
|
facturacionService.crearNuevaFacturaAuto(pedido, serie, notif.isBizum() ? TipoPago.tpv_bizum : TipoPago.tpv_tarjeta, locale);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
p.setStatus(PaymentStatus.failed);
|
p.setStatus(PaymentStatus.failed);
|
||||||
p.setFailedAt(LocalDateTime.now());
|
p.setFailedAt(LocalDateTime.now());
|
||||||
@ -452,6 +463,11 @@ public class PaymentService {
|
|||||||
// 4) Procesar el pedido asociado al carrito (si existe) o marcar el pedido como pagado
|
// 4) Procesar el pedido asociado al carrito (si existe) o marcar el pedido como pagado
|
||||||
if(p.getOrderId() != null) {
|
if(p.getOrderId() != null) {
|
||||||
pedidoService.setOrderAsPaid(p.getOrderId());
|
pedidoService.setOrderAsPaid(p.getOrderId());
|
||||||
|
|
||||||
|
Pedido pedido = pedidoService.getPedidoById(p.getOrderId());
|
||||||
|
SerieFactura serie = facturacionService.getDefaultSerieFactura();
|
||||||
|
|
||||||
|
facturacionService.crearNuevaFacturaAuto(pedido, serie, TipoPago.transferencia, locale);
|
||||||
}
|
}
|
||||||
/*else if (cartId != null) {
|
/*else if (cartId != null) {
|
||||||
// Se procesa el pedido dejando el estado calculado en processOrder
|
// Se procesa el pedido dejando el estado calculado en processOrder
|
||||||
|
|||||||
@ -122,27 +122,6 @@ public class PdfService {
|
|||||||
|
|
||||||
model.put("titulo", presupuesto.getTitulo());
|
model.put("titulo", presupuesto.getTitulo());
|
||||||
|
|
||||||
/*
|
|
||||||
* Map<String, Object> resumen = presupuestoService.getTextosResumen(
|
|
||||||
* presupuesto, null, model, model, null)
|
|
||||||
*/
|
|
||||||
model.put("lineas", List.of(
|
|
||||||
Map.of("descripcion", "Impresión interior B/N offset 80 g",
|
|
||||||
"meta", "300 páginas · tinta negra · papel 80 g",
|
|
||||||
"uds", 1000,
|
|
||||||
"precio", 2.15,
|
|
||||||
"dto", 0,
|
|
||||||
"importe", 2150.0),
|
|
||||||
Map.of("descripcion", "Cubierta color 300 g laminado mate",
|
|
||||||
"meta", "Lomo 15 mm · 4/0 · laminado mate",
|
|
||||||
"uds", 1000,
|
|
||||||
"precio", 0.38,
|
|
||||||
"dto", 5.0,
|
|
||||||
"importe", 361.0)));
|
|
||||||
|
|
||||||
model.put("servicios", List.of(
|
|
||||||
Map.of("descripcion", "Transporte península", "unidades", 1, "precio", 90.00)));
|
|
||||||
|
|
||||||
Map<String, Object> specs = utils.getTextoPresupuesto(presupuesto, locale);
|
Map<String, Object> specs = utils.getTextoPresupuesto(presupuesto, locale);
|
||||||
model.put("specs", specs);
|
model.put("specs", specs);
|
||||||
|
|
||||||
|
|||||||
@ -59,6 +59,12 @@ public class PedidoService {
|
|||||||
this.messageSource = messageSource;
|
this.messageSource = messageSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Pedido getPedidoById(Long pedidoId) {
|
||||||
|
return pedidoRepository.findById(pedidoId).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public Pedido crearPedido(
|
public Pedido crearPedido(
|
||||||
Long cartId,
|
Long cartId,
|
||||||
|
|||||||
@ -23,6 +23,7 @@ app.logout=Cerrar sesión
|
|||||||
app.sidebar.inicio=Inicio
|
app.sidebar.inicio=Inicio
|
||||||
app.sidebar.presupuestos=Presupuestos
|
app.sidebar.presupuestos=Presupuestos
|
||||||
app.sidebar.pedidos=Pedidos
|
app.sidebar.pedidos=Pedidos
|
||||||
|
app.sidebar.facturas=Facturas
|
||||||
app.sidebar.configuracion=Configuración
|
app.sidebar.configuracion=Configuración
|
||||||
app.sidebar.usuarios=Usuarios
|
app.sidebar.usuarios=Usuarios
|
||||||
app.sidebar.direcciones=Mis Direcciones
|
app.sidebar.direcciones=Mis Direcciones
|
||||||
|
|||||||
0
src/main/resources/i18n/facturas_en.properties
Normal file
0
src/main/resources/i18n/facturas_en.properties
Normal file
24
src/main/resources/i18n/facturas_es.properties
Normal file
24
src/main/resources/i18n/facturas_es.properties
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
facturas.title=Facturas
|
||||||
|
facturas.breadcrumb=Facturas
|
||||||
|
facturas.breadcrumb.ver=Ver Factura
|
||||||
|
|
||||||
|
facturas.tabla.id=ID
|
||||||
|
facturas.tabla.cliente=Cliente
|
||||||
|
facturas.tabla.num-factura=Número de Factura
|
||||||
|
facturas.tabla.estado=Estado
|
||||||
|
facturas.tabla.estado-pago=Estado de Pago
|
||||||
|
facturas.tabla.total=Total
|
||||||
|
facturas.tabla.fecha-emision=Fecha de Emisión
|
||||||
|
facturas.tabla.acciones=Acciones
|
||||||
|
|
||||||
|
facturas.estado-pago.pendiente=Pendiente
|
||||||
|
facturas.estado-pago.pagada=Pagada
|
||||||
|
facturas.estado-pago.cancelada=Cancelada
|
||||||
|
|
||||||
|
facturas.estado.borrador=Borrador
|
||||||
|
facturas.estado.validada=Validada
|
||||||
|
|
||||||
|
facturas.delete.title=¿Estás seguro de que deseas eliminar esta factura?
|
||||||
|
facturas.delete.text=Esta acción no se puede deshacer.
|
||||||
|
facturas.delete.ok.title=Factura eliminada
|
||||||
|
facturas.delete.ok.text=La factura ha sido eliminada correctamente.
|
||||||
@ -2914,6 +2914,19 @@ File: Main Css File
|
|||||||
background-color: #0ac7fb !important;
|
background-color: #0ac7fb !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.accordion-fill-imprimelibros .accordion-item .accordion-button {
|
||||||
|
-webkit-box-shadow: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
.accordion-fill-imprimelibros .accordion-item .accordion-button:not(.collapsed) {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #92b2a7 !important;
|
||||||
|
}
|
||||||
|
.accordion-fill-imprimelibros .accordion-item .accordion-button:is(.collapsed) {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #4c5c63 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.accordion-warning .accordion-item {
|
.accordion-warning .accordion-item {
|
||||||
border-color: rgba(239, 174, 78, 0.6);
|
border-color: rgba(239, 174, 78, 0.6);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,129 @@
|
|||||||
|
/* global $, bootstrap, window */
|
||||||
|
$(() => {
|
||||||
|
// si jQuery está cargado, añade CSRF a AJAX
|
||||||
|
const csrfToken = document.querySelector('meta[name="_csrf"]')?.getAttribute('content');
|
||||||
|
const csrfHeader = document.querySelector('meta[name="_csrf_header"]')?.getAttribute('content');
|
||||||
|
if (window.$ && csrfToken && csrfHeader) {
|
||||||
|
$.ajaxSetup({
|
||||||
|
beforeSend: function (xhr) {
|
||||||
|
xhr.setRequestHeader(csrfHeader, csrfToken);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const language = document.documentElement.lang || 'es-ES';
|
||||||
|
|
||||||
|
const $table = $('#facturas-datatable'); // en tu HTML está así, aunque el id sea raro
|
||||||
|
const $addBtn = $('#addButton');
|
||||||
|
|
||||||
|
// -----------------------------
|
||||||
|
// DataTable server-side
|
||||||
|
// -----------------------------
|
||||||
|
const dt = $table.DataTable({
|
||||||
|
processing: true,
|
||||||
|
serverSide: true,
|
||||||
|
searching: true,
|
||||||
|
orderMulti: false,
|
||||||
|
pageLength: 10,
|
||||||
|
lengthMenu: [10, 25, 50, 100],
|
||||||
|
|
||||||
|
language: { url: '/assets/libs/datatables/i18n/' + language + '.json' },
|
||||||
|
|
||||||
|
ajax: {
|
||||||
|
url: '/facturas/api/datatables',
|
||||||
|
type: 'GET',
|
||||||
|
dataSrc: function (json) {
|
||||||
|
// DataTables espera {draw, recordsTotal, recordsFiltered, data}
|
||||||
|
return json.data || [];
|
||||||
|
},
|
||||||
|
error: function (xhr) {
|
||||||
|
console.error('DataTables error', xhr);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
columns: [
|
||||||
|
{ data: 'id' },
|
||||||
|
{ data: 'cliente' },
|
||||||
|
{ data: 'numero_factura' },
|
||||||
|
{ data: 'estado_label', name: 'estado' },
|
||||||
|
{ data: 'estado_pago_label', name: 'estado_pago' },
|
||||||
|
{ data: 'total' },
|
||||||
|
{ data: 'fecha_emision' },
|
||||||
|
{
|
||||||
|
data: 'actions',
|
||||||
|
orderable: false,
|
||||||
|
searchable: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
order: [[0, 'desc']]
|
||||||
|
});
|
||||||
|
|
||||||
|
// -----------------------------
|
||||||
|
// Add
|
||||||
|
// -----------------------------
|
||||||
|
$addBtn.on();
|
||||||
|
|
||||||
|
// -----------------------------
|
||||||
|
// Edit click
|
||||||
|
// -----------------------------
|
||||||
|
$table.on('click', '.btn-view-factura', function () {
|
||||||
|
const row = dt.row($(this).closest('tr')).data();
|
||||||
|
window.location.href = `/facturas/${row.id}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// -----------------------------
|
||||||
|
// Delete click
|
||||||
|
// -----------------------------
|
||||||
|
$table.on('click', '.btn-delete-factura', function () {
|
||||||
|
const row = dt.row($(this).closest('tr')).data();
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title: window.languageBundle.get(['facturas.delete.title']) || 'Eliminar factura',
|
||||||
|
html: window.languageBundle.get(['facturas.delete.text']) || 'Esta acción no se puede deshacer.',
|
||||||
|
icon: 'warning',
|
||||||
|
showCancelButton: true,
|
||||||
|
buttonsStyling: false,
|
||||||
|
customClass: {
|
||||||
|
confirmButton: 'btn btn-danger w-xs mt-2',
|
||||||
|
cancelButton: 'btn btn-light w-xs mt-2'
|
||||||
|
},
|
||||||
|
confirmButtonText: window.languageBundle.get(['app.eliminar']) || 'Eliminar',
|
||||||
|
cancelButtonText: window.languageBundle.get(['app.cancelar']) || 'Cancelar',
|
||||||
|
}).then((result) => {
|
||||||
|
if (!result.isConfirmed) return;
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: `/facturas/api/${row.id}`,
|
||||||
|
method: 'DELETE',
|
||||||
|
success: function () {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success', title: window.languageBundle.get(['facturas.delete.ok.title']) || 'Eliminado',
|
||||||
|
text: window.languageBundle.get(['facturas.delete.ok.text']) || 'La factura ha sido eliminada correctamente.',
|
||||||
|
showConfirmButton: false,
|
||||||
|
timer: 1800,
|
||||||
|
customClass: {
|
||||||
|
confirmButton: 'btn btn-secondary w-xs mt-2',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
dt.ajax.reload(null, false);
|
||||||
|
},
|
||||||
|
error: function (xhr) {
|
||||||
|
const msg = (xhr.responseJSON && xhr.responseJSON.message)
|
||||||
|
|| 'Error al eliminar la serie de facturación.';
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'No se pudo eliminar',
|
||||||
|
text: msg,
|
||||||
|
buttonsStyling: false,
|
||||||
|
customClass: {
|
||||||
|
confirmButton: 'btn btn-secondary me-2', // clases para el botón confirmar
|
||||||
|
cancelButton: 'btn btn-light' // clases para cancelar
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
$(() => {
|
||||||
|
|
||||||
|
});
|
||||||
@ -0,0 +1,115 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||||
|
layout:decorate="~{imprimelibros/layout}">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<th:block layout:fragment="pagetitle" />
|
||||||
|
<th:block th:replace="~{imprimelibros/partials/head-css :: head-css}" />
|
||||||
|
<th:block layout:fragment="pagecss">
|
||||||
|
<link th:href="@{/assets/css/presupuestador.css}" rel="stylesheet"
|
||||||
|
th:unless="${#authorization.expression('isAuthenticated()')}" />
|
||||||
|
<link th:href="@{/assets/libs/datatables/dataTables.bootstrap5.min.css}" rel="stylesheet" />
|
||||||
|
</th:block>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div th:replace="~{imprimelibros/partials/topbar :: topbar}" />
|
||||||
|
<div th:replace="~{imprimelibros/partials/sidebar :: sidebar}" />
|
||||||
|
|
||||||
|
<th:block layout:fragment="content">
|
||||||
|
<div th:if="${#authorization.expression('isAuthenticated()')}">
|
||||||
|
|
||||||
|
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="/"><i class="ri-home-5-fill"></i></a></li>
|
||||||
|
<li class="breadcrumb-item"><a href="/facturas" th:text="#{facturas.breadcrumb}"></a></li>
|
||||||
|
<li class="breadcrumb-item active" aria-current="page" th:text="#{facturas.breadcrumb.ver}">
|
||||||
|
Ver factura</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="container-fluid">
|
||||||
|
|
||||||
|
<div class="accordion accordion-fill-imprimelibros mb-3" id="cabeceraFactura">
|
||||||
|
<div class="accordion-item material-shadow">
|
||||||
|
<h2 class="accordion-header" id="cabeceraHeader">
|
||||||
|
<button class="accordion-button" type="button" data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#cabecera" aria-expanded="true" aria-controls="cabecera">
|
||||||
|
Datos de la factura
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="cabecera" class="accordion-collapse collapse show" aria-labelledby="cabeceraHeader"
|
||||||
|
data-bs-parent="#cabeceraFactura">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<div th:replace="~{imprimelibros/facturas/partials/factura-cabecera :: factura-cabecera (factura=${factura})}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="accordion accordion-fill-imprimelibros" id="lineasFactura">
|
||||||
|
<div class="accordion-item material-shadow">
|
||||||
|
<h2 class="accordion-header" id="lineasHeader">
|
||||||
|
<button class="accordion-button" type="button" data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#lineas" aria-expanded="true" aria-controls="lineas">
|
||||||
|
Líneas de factura
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="lineas" class="accordion-collapse collapse show" aria-labelledby="lineasHeader"
|
||||||
|
data-bs-parent="#lineasFactura">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<!-- <div th:replace="~{imprimelibros/facturas/partials/factura-lineas :: factura-lineas (factura=${factura})}" /> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="accordion accordion-fill-imprimelibros" id="pagosFactura">
|
||||||
|
<div class="accordion-item material-shadow">
|
||||||
|
<h2 class="accordion-header" id="pagosHeader">
|
||||||
|
<button class="accordion-button" type="button" data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#pagos" aria-expanded="true" aria-controls="pagos">
|
||||||
|
Pagos de factura
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="pagos" class="accordion-collapse collapse show" aria-labelledby="pagosHeader"
|
||||||
|
data-bs-parent="#pagosFactura">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<!-- <div th:replace="~{imprimelibros/facturas/partials/factura-cabecera :: factura-cabecera (factura=${factura})}" /> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th:block>
|
||||||
|
|
||||||
|
<th:block layout:fragment="modal" />
|
||||||
|
<th:block th:replace="~{theme/partials/vendor-scripts :: scripts}" />
|
||||||
|
<th:block layout:fragment="pagejs">
|
||||||
|
<script th:inline="javascript">
|
||||||
|
window.languageBundle = /*[[${languageBundle}]]*/ {};
|
||||||
|
</script>
|
||||||
|
<script th:src="@{/assets/libs/datatables/datatables.min.js}"></script>
|
||||||
|
<script th:src="@{/assets/libs/datatables/dataTables.bootstrap5.min.js}"></script>
|
||||||
|
|
||||||
|
<!-- JS de Buttons y dependencias -->
|
||||||
|
<script th:src="@{/assets/libs/datatables/dataTables.buttons.min.js}"></script>
|
||||||
|
<script th:src="@{/assets/libs/jszip/jszip.min.js}"></script>
|
||||||
|
<script th:src="@{/assets/libs/pdfmake/pdfmake.min.js}"></script>
|
||||||
|
<script th:src="@{/assets/libs/pdfmake/vfs_fonts.min.js}"></script>
|
||||||
|
<script th:src="@{/assets/libs/datatables/buttons.html5.min.js}"></script>
|
||||||
|
<script th:src="@{/assets/libs/datatables/buttons.print.min.js}"></script>
|
||||||
|
<script th:src="@{/assets/libs/datatables/buttons.colVis.min.js}"></script>
|
||||||
|
|
||||||
|
<script type="module" th:src="@{/assets/js/pages/imprimelibros/facturas/view.js}"></script>
|
||||||
|
|
||||||
|
</th:block>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||||
|
layout:decorate="~{imprimelibros/layout}">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<th:block layout:fragment="pagetitle" />
|
||||||
|
<th:block th:replace="~{imprimelibros/partials/head-css :: head-css}" />
|
||||||
|
<th:block layout:fragment="pagecss">
|
||||||
|
<link th:href="@{/assets/css/presupuestador.css}" rel="stylesheet"
|
||||||
|
th:unless="${#authorization.expression('isAuthenticated()')}" />
|
||||||
|
<link th:href="@{/assets/libs/datatables/dataTables.bootstrap5.min.css}" rel="stylesheet" />
|
||||||
|
</th:block>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div th:replace="~{imprimelibros/partials/topbar :: topbar}" />
|
||||||
|
<div th:replace="~{imprimelibros/partials/sidebar :: sidebar}" />
|
||||||
|
|
||||||
|
<th:block layout:fragment="content">
|
||||||
|
<div th:if="${#authorization.expression('isAuthenticated()')}">
|
||||||
|
|
||||||
|
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="/"><i class="ri-home-5-fill"></i></a></li>
|
||||||
|
<li class="breadcrumb-item active" aria-current="page" th:text="#{facturas.breadcrumb}">
|
||||||
|
Facturas</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="container-fluid">
|
||||||
|
|
||||||
|
<button type="button" class="btn btn-secondary mb-3" id="addButton">
|
||||||
|
<i class="ri-add-line align-bottom me-1"></i> <span
|
||||||
|
th:text="#{app.add}">Añadir</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<table id="facturas-datatable" class="table table-striped table-nowrap responsive w-100">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="text-start" scope="col" th:text="#{facturas.tabla.id}">ID</th>
|
||||||
|
<th class="text-start" scope="col" th:text="#{facturas.tabla.cliente}">Cliente</th>
|
||||||
|
<th class="text-start" scope="col" th:text="#{facturas.tabla.num-factura}">Número de Factura</th>
|
||||||
|
<th class="text-start" scope="col" th:text="#{facturas.tabla.estado}">Estado</th>
|
||||||
|
<th class="text-start" scope="col" th:text="#{facturas.tabla.estado-pago}">Estado de Pago</th>
|
||||||
|
<th class="text-start" scope="col" th:text="#{facturas.tabla.total}">Total</th>
|
||||||
|
<th class="text-start" scope="col" th:text="#{facturas.tabla.fecha-emision}">Fecha de Emisión</th>
|
||||||
|
<th class="text-start" scope="col" th:text="#{facturas.tabla.acciones}">Acciones</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th:block>
|
||||||
|
|
||||||
|
<th:block layout:fragment="modal" />
|
||||||
|
<th:block th:replace="~{theme/partials/vendor-scripts :: scripts}" />
|
||||||
|
<th:block layout:fragment="pagejs">
|
||||||
|
<script th:inline="javascript">
|
||||||
|
window.languageBundle = /*[[${languageBundle}]]*/ {};
|
||||||
|
</script>
|
||||||
|
<script th:src="@{/assets/libs/datatables/datatables.min.js}"></script>
|
||||||
|
<script th:src="@{/assets/libs/datatables/dataTables.bootstrap5.min.js}"></script>
|
||||||
|
|
||||||
|
<!-- JS de Buttons y dependencias -->
|
||||||
|
<script th:src="@{/assets/libs/datatables/dataTables.buttons.min.js}"></script>
|
||||||
|
<script th:src="@{/assets/libs/jszip/jszip.min.js}"></script>
|
||||||
|
<script th:src="@{/assets/libs/pdfmake/pdfmake.min.js}"></script>
|
||||||
|
<script th:src="@{/assets/libs/pdfmake/vfs_fonts.min.js}"></script>
|
||||||
|
<script th:src="@{/assets/libs/datatables/buttons.html5.min.js}"></script>
|
||||||
|
<script th:src="@{/assets/libs/datatables/buttons.print.min.js}"></script>
|
||||||
|
<script th:src="@{/assets/libs/datatables/buttons.colVis.min.js}"></script>
|
||||||
|
|
||||||
|
<script type="module" th:src="@{/assets/js/pages/imprimelibros/facturas/list.js}"></script>
|
||||||
|
|
||||||
|
</th:block>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
<div th:fragment="factura-cabecera (factura)">
|
||||||
|
|
||||||
|
<h3>
|
||||||
|
<span th:class="|text-${factura.estado.name() == 'borrador' ? 'warning' : 'success'}|"
|
||||||
|
th:text="#{|facturas.estado.${factura.estado.name()}|}">
|
||||||
|
</span>
|
||||||
|
/
|
||||||
|
<span th:class="|text-${factura.estadoPago.name() == 'pendiente' ? 'warning' : 'success'}|"
|
||||||
|
th:text="#{|facturas.estado-pago.${factura.estadoPago.name()}|}">
|
||||||
|
</span>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<!-- flag readonly -->
|
||||||
|
<th:block th:with="isReadonly=${factura.estado.name() == 'validada'}">
|
||||||
|
|
||||||
|
<div class="row g-3">
|
||||||
|
|
||||||
|
<!-- Número -->
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label class="form-label">Número</label>
|
||||||
|
<input type="text" class="form-control" th:value="${factura.numeroFactura}"
|
||||||
|
th:attrappend="readonly=${isReadonly} ? 'readonly' : null">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Serie -->
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label class="form-label">Serie facturación</label>
|
||||||
|
<input type="text" class="form-control"
|
||||||
|
th:value="${factura.serie != null ? factura.serie.nombreSerie : ''}" readonly>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Cliente -->
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Cliente</label>
|
||||||
|
<input type="text" class="form-control" th:value="${factura.cliente.fullName}" readonly>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Fecha emisión -->
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label class="form-label">Fecha</label>
|
||||||
|
<input type="text" class="form-control" th:value="${factura.fechaEmision != null
|
||||||
|
? #temporals.format(factura.fechaEmision, 'dd/MM/yyyy')
|
||||||
|
: ''}" th:attrappend="readonly=${isReadonly} ? 'readonly' : null">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Notas -->
|
||||||
|
<div class="col-md-12">
|
||||||
|
<label class="form-label">Notas</label>
|
||||||
|
<textarea class="form-control" rows="3" th:text="${factura.notas}"
|
||||||
|
th:attrappend="readonly=${isReadonly} ? 'readonly' : null">
|
||||||
|
</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</th:block>
|
||||||
|
</div>
|
||||||
@ -48,6 +48,11 @@
|
|||||||
<i class="ri-book-3-line"></i> <span th:text="#{app.sidebar.pedidos}">Pedidos</span>
|
<i class="ri-book-3-line"></i> <span th:text="#{app.sidebar.pedidos}">Pedidos</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li th:if="${#authentication.principal.role == 'SUPERADMIN' or #authentication.principal.role == 'ADMIN'}" class="nav-item">
|
||||||
|
<a class="nav-link menu-link" href="/facturas">
|
||||||
|
<i class="ri-bill-line"></i> <span th:text="#{app.sidebar.facturas}">Facturas</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link menu-link" href="/direcciones">
|
<a class="nav-link menu-link" href="/direcciones">
|
||||||
<i class="ri-truck-line"></i>
|
<i class="ri-truck-line"></i>
|
||||||
|
|||||||
30
src/test/java/com/imprimelibros/erp/genericTest.java
Normal file
30
src/test/java/com/imprimelibros/erp/genericTest.java
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package com.imprimelibros.erp;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import com.imprimelibros.erp.common.Utils;
|
||||||
|
import com.imprimelibros.erp.presupuesto.PresupuestoRepository;
|
||||||
|
import com.imprimelibros.erp.presupuesto.dto.Presupuesto;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
public class genericTest {
|
||||||
|
@Autowired
|
||||||
|
private Utils utils;
|
||||||
|
@Autowired
|
||||||
|
private PresupuestoRepository presupuestoRepository;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getTextoPresupuesto() {
|
||||||
|
|
||||||
|
Locale locale = Locale.forLanguageTag("es-ES");
|
||||||
|
Presupuesto presupuesto = presupuestoRepository.findById(86L).orElse(null);
|
||||||
|
Map<String, Object> texto = utils.getTextoPresupuesto(presupuesto, locale);
|
||||||
|
|
||||||
|
System.out.println("🧾 Texto del presupuesto:" + texto);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user