mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-13 00:48:49 +00:00
trabajando en el formulario de la factura
This commit is contained in:
@ -9,13 +9,12 @@ import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.annotations.Formula;
|
||||
|
||||
@Entity
|
||||
@Table(
|
||||
name = "facturas",
|
||||
uniqueConstraints = {
|
||||
@Table(name = "facturas", uniqueConstraints = {
|
||||
@UniqueConstraint(name = "uq_facturas_numero_factura", columnNames = "numero_factura")
|
||||
}
|
||||
)
|
||||
})
|
||||
public class Factura extends AbstractAuditedEntitySoftTs {
|
||||
|
||||
@Column(name = "pedido_id")
|
||||
@ -80,11 +79,15 @@ public class Factura extends AbstractAuditedEntitySoftTs {
|
||||
@OneToMany(mappedBy = "factura", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||
private List<FacturaPago> pagos = new ArrayList<>();
|
||||
|
||||
@Formula("(select u.fullname from users u where u.id = cliente_id)")
|
||||
private String clienteNombre;
|
||||
|
||||
// Helpers
|
||||
public void addLinea(FacturaLinea linea) {
|
||||
linea.setFactura(this);
|
||||
this.lineas.add(linea);
|
||||
}
|
||||
|
||||
public void removeLinea(FacturaLinea linea) {
|
||||
this.lineas.remove(linea);
|
||||
linea.setFactura(null);
|
||||
@ -94,63 +97,154 @@ public class Factura extends AbstractAuditedEntitySoftTs {
|
||||
pago.setFactura(this);
|
||||
this.pagos.add(pago);
|
||||
}
|
||||
|
||||
public void removePago(FacturaPago pago) {
|
||||
this.pagos.remove(pago);
|
||||
pago.setFactura(null);
|
||||
}
|
||||
|
||||
// Getters/Setters
|
||||
public Long getPedidoId() { return pedidoId; }
|
||||
public void setPedidoId(Long pedidoId) { this.pedidoId = pedidoId; }
|
||||
public Long getPedidoId() {
|
||||
return pedidoId;
|
||||
}
|
||||
|
||||
public Factura getFacturaRectificada() { return facturaRectificada; }
|
||||
public void setFacturaRectificada(Factura facturaRectificada) { this.facturaRectificada = facturaRectificada; }
|
||||
public void setPedidoId(Long pedidoId) {
|
||||
this.pedidoId = pedidoId;
|
||||
}
|
||||
|
||||
public Factura getFacturaRectificativa() { return facturaRectificativa; }
|
||||
public void setFacturaRectificativa(Factura facturaRectificativa) { this.facturaRectificativa = facturaRectificativa; }
|
||||
public Factura getFacturaRectificada() {
|
||||
return facturaRectificada;
|
||||
}
|
||||
|
||||
public User getCliente() { return cliente; }
|
||||
public void setCliente(User cliente) { this.cliente = cliente; }
|
||||
public void setFacturaRectificada(Factura facturaRectificada) {
|
||||
this.facturaRectificada = facturaRectificada;
|
||||
}
|
||||
|
||||
public SerieFactura getSerie() { return serie; }
|
||||
public void setSerie(SerieFactura serie) { this.serie = serie; }
|
||||
public Factura getFacturaRectificativa() {
|
||||
return facturaRectificativa;
|
||||
}
|
||||
|
||||
public String getNumeroFactura() { return numeroFactura; }
|
||||
public void setNumeroFactura(String numeroFactura) { this.numeroFactura = numeroFactura; }
|
||||
public void setFacturaRectificativa(Factura facturaRectificativa) {
|
||||
this.facturaRectificativa = facturaRectificativa;
|
||||
}
|
||||
|
||||
public EstadoFactura getEstado() { return estado; }
|
||||
public void setEstado(EstadoFactura estado) { this.estado = estado; }
|
||||
public User getCliente() {
|
||||
return cliente;
|
||||
}
|
||||
|
||||
public EstadoPagoFactura getEstadoPago() { return estadoPago; }
|
||||
public void setEstadoPago(EstadoPagoFactura estadoPago) { this.estadoPago = estadoPago; }
|
||||
public void setCliente(User cliente) {
|
||||
this.cliente = cliente;
|
||||
}
|
||||
|
||||
public TipoPago getTipoPago() { return tipoPago; }
|
||||
public void setTipoPago(TipoPago tipoPago) { this.tipoPago = tipoPago; }
|
||||
public SerieFactura getSerie() {
|
||||
return serie;
|
||||
}
|
||||
|
||||
public LocalDateTime getFechaEmision() { return fechaEmision; }
|
||||
public void setFechaEmision(LocalDateTime fechaEmision) { this.fechaEmision = fechaEmision; }
|
||||
public void setSerie(SerieFactura serie) {
|
||||
this.serie = serie;
|
||||
}
|
||||
|
||||
public BigDecimal getBaseImponible() { return baseImponible; }
|
||||
public void setBaseImponible(BigDecimal baseImponible) { this.baseImponible = baseImponible; }
|
||||
public String getNumeroFactura() {
|
||||
return numeroFactura;
|
||||
}
|
||||
|
||||
public BigDecimal getIva4() { return iva4; }
|
||||
public void setIva4(BigDecimal iva4) { this.iva4 = iva4; }
|
||||
public void setNumeroFactura(String numeroFactura) {
|
||||
this.numeroFactura = numeroFactura;
|
||||
}
|
||||
|
||||
public BigDecimal getIva21() { return iva21; }
|
||||
public void setIva21(BigDecimal iva21) { this.iva21 = iva21; }
|
||||
public EstadoFactura getEstado() {
|
||||
return estado;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalFactura() { return totalFactura; }
|
||||
public void setTotalFactura(BigDecimal totalFactura) { this.totalFactura = totalFactura; }
|
||||
public void setEstado(EstadoFactura estado) {
|
||||
this.estado = estado;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalPagado() { return totalPagado; }
|
||||
public void setTotalPagado(BigDecimal totalPagado) { this.totalPagado = totalPagado; }
|
||||
public EstadoPagoFactura getEstadoPago() {
|
||||
return estadoPago;
|
||||
}
|
||||
|
||||
public String getNotas() { return notas; }
|
||||
public void setNotas(String notas) { this.notas = notas; }
|
||||
public void setEstadoPago(EstadoPagoFactura estadoPago) {
|
||||
this.estadoPago = estadoPago;
|
||||
}
|
||||
|
||||
public List<FacturaLinea> getLineas() { return lineas; }
|
||||
public void setLineas(List<FacturaLinea> lineas) { this.lineas = lineas; }
|
||||
public TipoPago getTipoPago() {
|
||||
return tipoPago;
|
||||
}
|
||||
|
||||
public List<FacturaPago> getPagos() { return pagos; }
|
||||
public void setPagos(List<FacturaPago> pagos) { this.pagos = pagos; }
|
||||
public void setTipoPago(TipoPago tipoPago) {
|
||||
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 -> """
|
||||
<div class="hstack gap-3 flex-wrap">
|
||||
<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">
|
||||
<i class="ri-edit-2-line"></i>
|
||||
</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">
|
||||
<i class="ri-delete-bin-5-line"></i>
|
||||
</button>
|
||||
|
||||
@ -2,9 +2,10 @@ package com.imprimelibros.erp.facturacion.repo;
|
||||
|
||||
import com.imprimelibros.erp.facturacion.Factura;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -1,17 +1,31 @@
|
||||
package com.imprimelibros.erp.facturacion.service;
|
||||
|
||||
import com.imprimelibros.erp.common.Utils;
|
||||
import com.imprimelibros.erp.facturacion.*;
|
||||
import com.imprimelibros.erp.facturacion.dto.FacturaLineaUpsertDto;
|
||||
import com.imprimelibros.erp.facturacion.dto.FacturaPagoUpsertDto;
|
||||
import com.imprimelibros.erp.facturacion.repo.FacturaPagoRepository;
|
||||
import com.imprimelibros.erp.facturacion.repo.FacturaRepository;
|
||||
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 org.springframework.context.MessageSource;
|
||||
import org.springframework.stereotype.Service;
|
||||
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.RoundingMode;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Service
|
||||
@ -20,21 +34,121 @@ public class FacturacionService {
|
||||
private final FacturaRepository facturaRepo;
|
||||
private final SerieFacturaRepository serieRepo;
|
||||
private final FacturaPagoRepository pagoRepo;
|
||||
private final PedidoLineaRepository pedidoLineaRepo;
|
||||
private final Utils utils;
|
||||
private final MessageSource messageSource;
|
||||
|
||||
public FacturacionService(
|
||||
FacturaRepository facturaRepo,
|
||||
SerieFacturaRepository serieRepo,
|
||||
FacturaPagoRepository pagoRepo
|
||||
) {
|
||||
FacturaPagoRepository pagoRepo,
|
||||
PedidoLineaRepository pedidoLineaRepo,
|
||||
Utils utils,
|
||||
MessageSource messageSource) {
|
||||
this.facturaRepo = facturaRepo;
|
||||
this.serieRepo = serieRepo;
|
||||
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
|
||||
// -----------------------
|
||||
|
||||
@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
|
||||
public Factura validarFactura(Long facturaId) {
|
||||
Factura factura = facturaRepo.findById(facturaId)
|
||||
@ -56,7 +170,8 @@ public class FacturacionService {
|
||||
// Si ya tiene numero_factura, no reservamos otro
|
||||
if (factura.getNumeroFactura() == null || factura.getNumeroFactura().isBlank()) {
|
||||
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();
|
||||
String numeroFactura = buildNumeroFactura(serieLocked.getPrefijo(), next);
|
||||
@ -89,7 +204,7 @@ public class FacturacionService {
|
||||
private String buildNumeroFactura(String prefijo, long numero) {
|
||||
String pref = (prefijo == null) ? "" : prefijo.trim();
|
||||
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.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());
|
||||
|
||||
recalcularTotales(factura);
|
||||
@ -254,7 +370,8 @@ public class FacturacionService {
|
||||
factura.setTotalPagado(scale2(pagado));
|
||||
|
||||
// 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
|
||||
// - pendiente: resto
|
||||
if (factura.getEstadoPago() == EstadoPagoFactura.cancelada) {
|
||||
@ -277,4 +394,90 @@ public class FacturacionService {
|
||||
private static BigDecimal scale2(BigDecimal v) {
|
||||
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.imprimelibros.erp.cart.Cart;
|
||||
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.repo.PaymentRepository;
|
||||
import com.imprimelibros.erp.payments.repo.PaymentTransactionRepository;
|
||||
@ -33,6 +36,7 @@ public class PaymentService {
|
||||
private final ObjectMapper om = new ObjectMapper();
|
||||
private final CartService cartService;
|
||||
private final PedidoService pedidoService;
|
||||
private final FacturacionService facturacionService;
|
||||
|
||||
public PaymentService(PaymentRepository payRepo,
|
||||
PaymentTransactionRepository txRepo,
|
||||
@ -40,7 +44,8 @@ public class PaymentService {
|
||||
RedsysService redsysService,
|
||||
WebhookEventRepository webhookEventRepo,
|
||||
CartService cartService,
|
||||
PedidoService pedidoService) {
|
||||
PedidoService pedidoService,
|
||||
FacturacionService facturacionService) {
|
||||
this.payRepo = payRepo;
|
||||
this.txRepo = txRepo;
|
||||
this.refundRepo = refundRepo;
|
||||
@ -48,6 +53,7 @@ public class PaymentService {
|
||||
this.webhookEventRepo = webhookEventRepo;
|
||||
this.cartService = cartService;
|
||||
this.pedidoService = pedidoService;
|
||||
this.facturacionService = facturacionService;
|
||||
}
|
||||
|
||||
public Payment findFailedPaymentByOrderId(Long orderId) {
|
||||
@ -253,6 +259,11 @@ public class PaymentService {
|
||||
p.setCapturedAt(LocalDateTime.now());
|
||||
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 {
|
||||
p.setStatus(PaymentStatus.failed);
|
||||
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
|
||||
if(p.getOrderId() != null) {
|
||||
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) {
|
||||
// Se procesa el pedido dejando el estado calculado en processOrder
|
||||
|
||||
@ -122,27 +122,6 @@ public class PdfService {
|
||||
|
||||
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);
|
||||
model.put("specs", specs);
|
||||
|
||||
|
||||
@ -59,6 +59,12 @@ public class PedidoService {
|
||||
this.messageSource = messageSource;
|
||||
}
|
||||
|
||||
|
||||
public Pedido getPedidoById(Long pedidoId) {
|
||||
return pedidoRepository.findById(pedidoId).orElse(null);
|
||||
}
|
||||
|
||||
|
||||
@Transactional
|
||||
public Pedido crearPedido(
|
||||
Long cartId,
|
||||
|
||||
Reference in New Issue
Block a user