terminando pdf de facturas

This commit is contained in:
2026-01-02 21:47:06 +01:00
parent bf823281a5
commit 6bea279066
30 changed files with 7112 additions and 6245 deletions

View File

@ -2,15 +2,20 @@ package com.imprimelibros.erp.facturacion.service;
import com.imprimelibros.erp.common.Utils;
import com.imprimelibros.erp.facturacion.*;
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.FacturaLineaRepository;
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.pedidos.PedidoService;
import com.imprimelibros.erp.presupuesto.dto.Presupuesto;
import com.imprimelibros.erp.users.User;
import com.imprimelibros.erp.users.UserService;
import jakarta.persistence.EntityNotFoundException;
@ -24,6 +29,7 @@ import java.util.Map;
import java.util.Locale;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.security.Principal;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
@ -34,26 +40,34 @@ public class FacturacionService {
private final FacturaRepository facturaRepo;
private final SerieFacturaRepository serieRepo;
private final FacturaPagoRepository pagoRepo;
private final FacturaLineaRepository lineaFacturaRepository;
private final PedidoLineaRepository pedidoLineaRepo;
private final UserService userService;
private final Utils utils;
private final MessageSource messageSource;
private final PedidoService pedidoService;
public FacturacionService(
FacturaRepository facturaRepo,
FacturaLineaRepository lineaFacturaRepository,
SerieFacturaRepository serieRepo,
FacturaPagoRepository pagoRepo,
PedidoLineaRepository pedidoLineaRepo,
UserService userService,
Utils utils,
MessageSource messageSource) {
MessageSource messageSource,
PedidoService pedidoService) {
this.facturaRepo = facturaRepo;
this.lineaFacturaRepository = lineaFacturaRepository;
this.serieRepo = serieRepo;
this.pagoRepo = pagoRepo;
this.pedidoLineaRepo = pedidoLineaRepo;
this.userService = userService;
this.utils = utils;
this.messageSource = messageSource;
this.pedidoService = pedidoService;
}
public SerieFactura getDefaultSerieFactura() {
List<SerieFactura> series = serieRepo.findAll();
if (series.isEmpty()) {
@ -64,6 +78,11 @@ public class FacturacionService {
return series.get(0);
}
public Factura getFactura(Long facturaId) {
return facturaRepo.findById(facturaId)
.orElseThrow(() -> new EntityNotFoundException("Factura no encontrada: " + facturaId));
}
// -----------------------
// Nueva factura
// -----------------------
@ -113,7 +132,7 @@ public class FacturacionService {
factura = facturaRepo.save(factura);
if(pedidoPendientePago) {
if (pedidoPendientePago) {
return factura;
}
FacturaPago pago = new FacturaPago();
@ -149,6 +168,48 @@ public class FacturacionService {
}
}
@Transactional
public void guardarCabeceraYDireccionFacturacion(Long facturaId, FacturaGuardarDto dto) {
Factura factura = getFactura(facturaId);
// ✅ Solo editable si borrador (tu regla actual para cabecera/dirección)
if (factura.getEstado() != EstadoFactura.borrador) {
throw new IllegalStateException("Solo se puede guardar cabecera/dirección en borrador.");
}
// 1) Cabecera
if (dto.getCabecera() != null) {
var c = dto.getCabecera();
if (c.getSerieId() != null) {
SerieFactura serie = serieRepo.findById(c.getSerieId())
.orElseThrow(() -> new EntityNotFoundException("Serie no encontrada: " + c.getSerieId()));
factura.setSerie(serie);
}
if (c.getClienteId() != null) {
User cliente = userService.findById(c.getClienteId());
if(cliente == null){
throw new EntityNotFoundException("Cliente no encontrado: " + c.getClienteId());
}
factura.setCliente(cliente);
}
if (c.getFechaEmision() != null) {
factura.setFechaEmision(c.getFechaEmision());
}
}
// 2) Dirección de facturación del pedido asociado
Long pedidoId = factura.getPedidoId();
if (pedidoId != null && dto.getDireccionFacturacion() != null) {
pedidoService.upsertDireccionFacturacion(pedidoId, dto.getDireccionFacturacion());
}
facturaRepo.save(factura);
}
@Transactional
public Factura validarFactura(Long facturaId) {
Factura factura = facturaRepo.findById(facturaId)
@ -210,6 +271,20 @@ public class FacturacionService {
// -----------------------
// Líneas
// -----------------------
@Transactional
public void createLinea(Long facturaId, FacturaLineaUpsertDto req) {
Factura factura = this.getFactura(facturaId);
FacturaLinea lf = new FacturaLinea();
lf.setFactura(factura);
lf.setCantidad(1);
applyRequest(lf, req);
lineaFacturaRepository.save(lf);
this.recalcularTotales(factura);
}
@Transactional
public Factura upsertLinea(Long facturaId, FacturaLineaUpsertDto dto) {
@ -233,29 +308,15 @@ public class FacturacionService {
}
linea.setDescripcion(dto.getDescripcion());
linea.setCantidad(dto.getCantidad());
// Base por unidad o base total? Tu migración no define precio unitario.
// Asumimos que baseLinea es TOTAL de línea (sin IVA) y cantidad informativa.
linea.setBaseLinea(scale2(dto.getBaseLinea()));
linea.setBaseLinea(scale2(dto.getBase()));
// Iva por checks: calculamos importes, no porcentajes
BigDecimal iva4 = BigDecimal.ZERO;
BigDecimal iva21 = BigDecimal.ZERO;
linea.setIva4Linea(dto.getIva4());
linea.setIva21Linea(dto.getIva21());
if (dto.isAplicaIva4() && dto.isAplicaIva21()) {
throw new IllegalArgumentException("Una línea no puede tener IVA 4% y 21% a la vez.");
}
if (dto.isAplicaIva4()) {
iva4 = scale2(linea.getBaseLinea().multiply(new BigDecimal("0.04")));
}
if (dto.isAplicaIva21()) {
iva21 = scale2(linea.getBaseLinea().multiply(new BigDecimal("0.21")));
}
linea.setIva4Linea(iva4);
linea.setIva21Linea(iva21);
linea.setTotalLinea(scale2(linea.getBaseLinea().add(iva4).add(iva21)));
linea.setTotalLinea(scale2(linea.getBaseLinea()
.add(nvl(linea.getIva4Linea()))
.add(nvl(linea.getIva21Linea()))));
recalcularTotales(factura);
return facturaRepo.save(factura);
@ -284,7 +345,7 @@ public class FacturacionService {
// -----------------------
@Transactional
public Factura upsertPago(Long facturaId, FacturaPagoUpsertDto dto) {
public Factura upsertPago(Long facturaId, FacturaPagoUpsertDto dto, Principal principal) {
Factura factura = facturaRepo.findById(facturaId)
.orElseThrow(() -> new EntityNotFoundException("Factura no encontrada: " + facturaId));
@ -293,6 +354,8 @@ public class FacturacionService {
if (dto.getId() == null) {
pago = new FacturaPago();
pago.setFactura(factura);
pago.setCreatedBy(Utils.currentUser(principal));
pago.setCreatedAt(Instant.now());
factura.getPagos().add(pago);
} else {
pago = factura.getPagos().stream()
@ -305,7 +368,8 @@ public class FacturacionService {
pago.setCantidadPagada(scale2(dto.getCantidadPagada()));
pago.setFechaPago(dto.getFechaPago() != null ? dto.getFechaPago() : LocalDateTime.now());
pago.setNotas(dto.getNotas());
pago.setUpdatedAt(Instant.now());
pago.setUpdatedBy(Utils.currentUser(principal));
// El tipo_pago de la factura: si tiene un pago, lo reflejamos (último pago
// manda)
factura.setTipoPago(dto.getMetodoPago());
@ -315,14 +379,18 @@ public class FacturacionService {
}
@Transactional
public Factura borrarPago(Long facturaId, Long pagoId) {
public Factura borrarPago(Long facturaId, Long pagoId, Principal principal) {
Factura factura = facturaRepo.findById(facturaId)
.orElseThrow(() -> new EntityNotFoundException("Factura no encontrada: " + facturaId));
boolean removed = factura.getPagos().removeIf(p -> pagoId.equals(p.getId()));
if (!removed) {
throw new EntityNotFoundException("Pago no encontrado: " + pagoId);
}
FacturaPago pago = factura.getPagos().stream()
.filter(p -> pagoId.equals(p.getId()))
.findFirst()
.orElseThrow(() -> new EntityNotFoundException("Pago no encontrado: " + pagoId));
// soft delete
pago.setDeletedAt(Instant.now());
pago.setDeletedBy(Utils.currentUser(principal));
recalcularTotales(factura);
return facturaRepo.save(factura);
@ -364,6 +432,8 @@ public class FacturacionService {
BigDecimal pagado = BigDecimal.ZERO;
if (factura.getPagos() != null) {
for (FacturaPago p : factura.getPagos()) {
if (p.getDeletedAt() != null)
continue;
pagado = pagado.add(nvl(p.getCantidadPagada()));
}
}
@ -480,4 +550,20 @@ public class FacturacionService {
.replace("'", "&#39;");
}
private void applyRequest(FacturaLinea lf, FacturaLineaUpsertDto req) {
// HTML
lf.setDescripcion(req.getDescripcion() == null ? "" : req.getDescripcion());
BigDecimal base = nvl(req.getBase());
BigDecimal iva4 = nvl(req.getIva4());
BigDecimal iva21 = nvl(req.getIva21());
lf.setBaseLinea(base);
lf.setIva4Linea(iva4);
lf.setIva21Linea(iva21);
// total de línea (por ahora)
lf.setTotalLinea(base.add(iva4).add(iva21));
}
}