mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-19 07:10:21 +00:00
añadidos entidades, repos y facturacionservice
This commit is contained in:
@ -0,0 +1,280 @@
|
||||
package com.imprimelibros.erp.facturacion.service;
|
||||
|
||||
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 jakarta.persistence.EntityNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Service
|
||||
public class FacturacionService {
|
||||
|
||||
private final FacturaRepository facturaRepo;
|
||||
private final SerieFacturaRepository serieRepo;
|
||||
private final FacturaPagoRepository pagoRepo;
|
||||
|
||||
public FacturacionService(
|
||||
FacturaRepository facturaRepo,
|
||||
SerieFacturaRepository serieRepo,
|
||||
FacturaPagoRepository pagoRepo
|
||||
) {
|
||||
this.facturaRepo = facturaRepo;
|
||||
this.serieRepo = serieRepo;
|
||||
this.pagoRepo = pagoRepo;
|
||||
}
|
||||
|
||||
// -----------------------
|
||||
// Estado / Numeración
|
||||
// -----------------------
|
||||
|
||||
@Transactional
|
||||
public Factura validarFactura(Long facturaId) {
|
||||
Factura factura = facturaRepo.findById(facturaId)
|
||||
.orElseThrow(() -> new EntityNotFoundException("Factura no encontrada: " + facturaId));
|
||||
|
||||
// Puedes permitir validar desde borrador solamente (lo normal)
|
||||
if (factura.getEstado() == EstadoFactura.validada) {
|
||||
return factura;
|
||||
}
|
||||
|
||||
if (factura.getFechaEmision() == null) {
|
||||
factura.setFechaEmision(LocalDateTime.now());
|
||||
}
|
||||
|
||||
if (factura.getSerie() == null) {
|
||||
throw new IllegalStateException("La factura no tiene serie asignada.");
|
||||
}
|
||||
|
||||
// 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()));
|
||||
|
||||
long next = (serieLocked.getNumeroActual() == null) ? 1L : serieLocked.getNumeroActual();
|
||||
String numeroFactura = buildNumeroFactura(serieLocked.getPrefijo(), next);
|
||||
|
||||
factura.setNumeroFactura(numeroFactura);
|
||||
|
||||
// Incrementar contador para la siguiente
|
||||
serieLocked.setNumeroActual((int) (next + 1)); // si cambias numero_actual a BIGINT en entidad, quita el cast
|
||||
serieRepo.save(serieLocked);
|
||||
}
|
||||
|
||||
recalcularTotales(factura);
|
||||
factura.setEstado(EstadoFactura.validada);
|
||||
|
||||
return facturaRepo.save(factura);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Factura volverABorrador(Long facturaId) {
|
||||
Factura factura = facturaRepo.findById(facturaId)
|
||||
.orElseThrow(() -> new EntityNotFoundException("Factura no encontrada: " + facturaId));
|
||||
|
||||
factura.setEstado(EstadoFactura.borrador);
|
||||
// No tocamos numero_factura (se conserva) -> evita duplicados y auditoría rara
|
||||
|
||||
recalcularTotales(factura);
|
||||
return facturaRepo.save(factura);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// -----------------------
|
||||
// Líneas
|
||||
// -----------------------
|
||||
|
||||
@Transactional
|
||||
public Factura upsertLinea(Long facturaId, FacturaLineaUpsertDto dto) {
|
||||
Factura factura = facturaRepo.findById(facturaId)
|
||||
.orElseThrow(() -> new EntityNotFoundException("Factura no encontrada: " + facturaId));
|
||||
|
||||
if (factura.getEstado() != EstadoFactura.borrador) {
|
||||
throw new IllegalStateException("Solo se pueden editar líneas en facturas en borrador.");
|
||||
}
|
||||
|
||||
FacturaLinea linea;
|
||||
if (dto.getId() == null) {
|
||||
linea = new FacturaLinea();
|
||||
linea.setFactura(factura);
|
||||
factura.getLineas().add(linea);
|
||||
} else {
|
||||
linea = factura.getLineas().stream()
|
||||
.filter(l -> dto.getId().equals(l.getId()))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new EntityNotFoundException("Línea no encontrada: " + dto.getId()));
|
||||
}
|
||||
|
||||
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()));
|
||||
|
||||
// Iva por checks: calculamos importes, no porcentajes
|
||||
BigDecimal iva4 = BigDecimal.ZERO;
|
||||
BigDecimal iva21 = BigDecimal.ZERO;
|
||||
|
||||
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)));
|
||||
|
||||
recalcularTotales(factura);
|
||||
return facturaRepo.save(factura);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Factura borrarLinea(Long facturaId, Long lineaId) {
|
||||
Factura factura = facturaRepo.findById(facturaId)
|
||||
.orElseThrow(() -> new EntityNotFoundException("Factura no encontrada: " + facturaId));
|
||||
|
||||
if (factura.getEstado() != EstadoFactura.borrador) {
|
||||
throw new IllegalStateException("Solo se pueden borrar líneas en facturas en borrador.");
|
||||
}
|
||||
|
||||
boolean removed = factura.getLineas().removeIf(l -> lineaId.equals(l.getId()));
|
||||
if (!removed) {
|
||||
throw new EntityNotFoundException("Línea no encontrada: " + lineaId);
|
||||
}
|
||||
|
||||
recalcularTotales(factura);
|
||||
return facturaRepo.save(factura);
|
||||
}
|
||||
|
||||
// -----------------------
|
||||
// Pagos
|
||||
// -----------------------
|
||||
|
||||
@Transactional
|
||||
public Factura upsertPago(Long facturaId, FacturaPagoUpsertDto dto) {
|
||||
Factura factura = facturaRepo.findById(facturaId)
|
||||
.orElseThrow(() -> new EntityNotFoundException("Factura no encontrada: " + facturaId));
|
||||
|
||||
// Permitir añadir pagos tanto en borrador como validada (según tu regla)
|
||||
FacturaPago pago;
|
||||
if (dto.getId() == null) {
|
||||
pago = new FacturaPago();
|
||||
pago.setFactura(factura);
|
||||
factura.getPagos().add(pago);
|
||||
} else {
|
||||
pago = factura.getPagos().stream()
|
||||
.filter(p -> dto.getId().equals(p.getId()))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new EntityNotFoundException("Pago no encontrado: " + dto.getId()));
|
||||
}
|
||||
|
||||
pago.setMetodoPago(dto.getMetodoPago());
|
||||
pago.setCantidadPagada(scale2(dto.getCantidadPagada()));
|
||||
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)
|
||||
factura.setTipoPago(dto.getMetodoPago());
|
||||
|
||||
recalcularTotales(factura);
|
||||
return facturaRepo.save(factura);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Factura borrarPago(Long facturaId, Long pagoId) {
|
||||
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);
|
||||
}
|
||||
|
||||
recalcularTotales(factura);
|
||||
return facturaRepo.save(factura);
|
||||
}
|
||||
|
||||
// -----------------------
|
||||
// Recalcular totales
|
||||
// -----------------------
|
||||
|
||||
@Transactional
|
||||
public void recalcularTotales(Long facturaId) {
|
||||
Factura factura = facturaRepo.findById(facturaId)
|
||||
.orElseThrow(() -> new EntityNotFoundException("Factura no encontrada: " + facturaId));
|
||||
recalcularTotales(factura);
|
||||
facturaRepo.save(factura);
|
||||
}
|
||||
|
||||
private void recalcularTotales(Factura factura) {
|
||||
BigDecimal base = BigDecimal.ZERO;
|
||||
BigDecimal iva4 = BigDecimal.ZERO;
|
||||
BigDecimal iva21 = BigDecimal.ZERO;
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
|
||||
if (factura.getLineas() != null) {
|
||||
for (FacturaLinea l : factura.getLineas()) {
|
||||
base = base.add(nvl(l.getBaseLinea()));
|
||||
iva4 = iva4.add(nvl(l.getIva4Linea()));
|
||||
iva21 = iva21.add(nvl(l.getIva21Linea()));
|
||||
total = total.add(nvl(l.getTotalLinea()));
|
||||
}
|
||||
}
|
||||
|
||||
factura.setBaseImponible(scale2(base));
|
||||
factura.setIva4(scale2(iva4));
|
||||
factura.setIva21(scale2(iva21));
|
||||
factura.setTotalFactura(scale2(total));
|
||||
|
||||
// total_pagado
|
||||
BigDecimal pagado = BigDecimal.ZERO;
|
||||
if (factura.getPagos() != null) {
|
||||
for (FacturaPago p : factura.getPagos()) {
|
||||
pagado = pagado.add(nvl(p.getCantidadPagada()));
|
||||
}
|
||||
}
|
||||
factura.setTotalPagado(scale2(pagado));
|
||||
|
||||
// estado_pago
|
||||
// - 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) {
|
||||
return;
|
||||
}
|
||||
|
||||
BigDecimal totalFactura = nvl(factura.getTotalFactura());
|
||||
if (totalFactura.compareTo(BigDecimal.ZERO) > 0 &&
|
||||
factura.getTotalPagado().compareTo(totalFactura) >= 0) {
|
||||
factura.setEstadoPago(EstadoPagoFactura.pagada);
|
||||
} else {
|
||||
factura.setEstadoPago(EstadoPagoFactura.pendiente);
|
||||
}
|
||||
}
|
||||
|
||||
private static BigDecimal nvl(BigDecimal v) {
|
||||
return v == null ? BigDecimal.ZERO : v;
|
||||
}
|
||||
|
||||
private static BigDecimal scale2(BigDecimal v) {
|
||||
return (v == null ? BigDecimal.ZERO : v).setScale(2, RoundingMode.HALF_UP);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user