mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-13 00:48:49 +00:00
añadidos entidades, repos y facturacionservice
This commit is contained in:
@ -0,0 +1,67 @@
|
||||
package com.imprimelibros.erp.common.jpa;
|
||||
|
||||
import com.imprimelibros.erp.users.User;
|
||||
import jakarta.persistence.*;
|
||||
import org.springframework.data.annotation.CreatedBy;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.annotation.LastModifiedBy;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
@MappedSuperclass
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
public abstract class AbstractAuditedEntitySoftTs {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@CreatedDate
|
||||
@Column(name = "created_at", updatable = false)
|
||||
private Instant createdAt;
|
||||
|
||||
@LastModifiedDate
|
||||
@Column(name = "updated_at")
|
||||
private Instant updatedAt;
|
||||
|
||||
@CreatedBy
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "created_by")
|
||||
private User createdBy;
|
||||
|
||||
@LastModifiedBy
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "updated_by")
|
||||
private User updatedBy;
|
||||
|
||||
@Column(name = "deleted_at")
|
||||
private Instant deletedAt;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "deleted_by")
|
||||
private User deletedBy;
|
||||
|
||||
// Getters/Setters
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
|
||||
public Instant getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(Instant createdAt) { this.createdAt = createdAt; }
|
||||
|
||||
public Instant getUpdatedAt() { return updatedAt; }
|
||||
public void setUpdatedAt(Instant updatedAt) { this.updatedAt = updatedAt; }
|
||||
|
||||
public User getCreatedBy() { return createdBy; }
|
||||
public void setCreatedBy(User createdBy) { this.createdBy = createdBy; }
|
||||
|
||||
public User getUpdatedBy() { return updatedBy; }
|
||||
public void setUpdatedBy(User updatedBy) { this.updatedBy = updatedBy; }
|
||||
|
||||
public Instant getDeletedAt() { return deletedAt; }
|
||||
public void setDeletedAt(Instant deletedAt) { this.deletedAt = deletedAt; }
|
||||
|
||||
public User getDeletedBy() { return deletedBy; }
|
||||
public void setDeletedBy(User deletedBy) { this.deletedBy = deletedBy; }
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
package com.imprimelibros.erp.facturacion;
|
||||
|
||||
public enum EstadoFactura {
|
||||
borrador,
|
||||
validada
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package com.imprimelibros.erp.facturacion;
|
||||
|
||||
public enum EstadoPagoFactura {
|
||||
pendiente,
|
||||
pagada,
|
||||
cancelada
|
||||
}
|
||||
156
src/main/java/com/imprimelibros/erp/facturacion/Factura.java
Normal file
156
src/main/java/com/imprimelibros/erp/facturacion/Factura.java
Normal file
@ -0,0 +1,156 @@
|
||||
package com.imprimelibros.erp.facturacion;
|
||||
|
||||
import com.imprimelibros.erp.common.jpa.AbstractAuditedEntitySoftTs;
|
||||
import com.imprimelibros.erp.users.User;
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Entity
|
||||
@Table(
|
||||
name = "facturas",
|
||||
uniqueConstraints = {
|
||||
@UniqueConstraint(name = "uq_facturas_numero_factura", columnNames = "numero_factura")
|
||||
}
|
||||
)
|
||||
public class Factura extends AbstractAuditedEntitySoftTs {
|
||||
|
||||
@Column(name = "pedido_id")
|
||||
private Long pedidoId;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "factura_rectificada_id")
|
||||
private Factura facturaRectificada;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "factura_rectificativa_id")
|
||||
private Factura facturaRectificativa;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "cliente_id")
|
||||
private User cliente;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "serie_id")
|
||||
private SerieFactura serie;
|
||||
|
||||
@Column(name = "numero_factura", length = 50)
|
||||
private String numeroFactura;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "estado", nullable = false, length = 20)
|
||||
private EstadoFactura estado = EstadoFactura.borrador;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "estado_pago", nullable = false, length = 20)
|
||||
private EstadoPagoFactura estadoPago = EstadoPagoFactura.pendiente;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "tipo_pago", nullable = false, length = 30)
|
||||
private TipoPago tipoPago = TipoPago.otros;
|
||||
|
||||
@Column(name = "fecha_emision")
|
||||
private LocalDateTime fechaEmision;
|
||||
|
||||
@Column(name = "base_imponible", precision = 10, scale = 2)
|
||||
private BigDecimal baseImponible;
|
||||
|
||||
@Column(name = "iva_4", precision = 10, scale = 2)
|
||||
private BigDecimal iva4;
|
||||
|
||||
@Column(name = "iva_21", precision = 10, scale = 2)
|
||||
private BigDecimal iva21;
|
||||
|
||||
@Column(name = "total_factura", precision = 10, scale = 2)
|
||||
private BigDecimal totalFactura;
|
||||
|
||||
@Column(name = "total_pagado", precision = 10, scale = 2)
|
||||
private BigDecimal totalPagado = new BigDecimal("0.00");
|
||||
|
||||
@Lob
|
||||
@Column(name = "notas")
|
||||
private String notas;
|
||||
|
||||
@OneToMany(mappedBy = "factura", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||
private List<FacturaLinea> lineas = new ArrayList<>();
|
||||
|
||||
@OneToMany(mappedBy = "factura", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||
private List<FacturaPago> pagos = new ArrayList<>();
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
public void addPago(FacturaPago pago) {
|
||||
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 Factura getFacturaRectificada() { return facturaRectificada; }
|
||||
public void setFacturaRectificada(Factura facturaRectificada) { this.facturaRectificada = facturaRectificada; }
|
||||
|
||||
public Factura getFacturaRectificativa() { return facturaRectificativa; }
|
||||
public void setFacturaRectificativa(Factura facturaRectificativa) { this.facturaRectificativa = facturaRectificativa; }
|
||||
|
||||
public User getCliente() { return cliente; }
|
||||
public void setCliente(User cliente) { this.cliente = cliente; }
|
||||
|
||||
public SerieFactura getSerie() { return serie; }
|
||||
public void setSerie(SerieFactura serie) { this.serie = serie; }
|
||||
|
||||
public String getNumeroFactura() { return numeroFactura; }
|
||||
public void setNumeroFactura(String numeroFactura) { this.numeroFactura = numeroFactura; }
|
||||
|
||||
public EstadoFactura getEstado() { return estado; }
|
||||
public void setEstado(EstadoFactura estado) { this.estado = estado; }
|
||||
|
||||
public EstadoPagoFactura getEstadoPago() { return estadoPago; }
|
||||
public void setEstadoPago(EstadoPagoFactura estadoPago) { this.estadoPago = estadoPago; }
|
||||
|
||||
public TipoPago getTipoPago() { return tipoPago; }
|
||||
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,56 @@
|
||||
package com.imprimelibros.erp.facturacion;
|
||||
|
||||
import com.imprimelibros.erp.common.jpa.AbstractAuditedEntitySoftTs;
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Entity
|
||||
@Table(name = "facturas_lineas")
|
||||
public class FacturaLinea extends AbstractAuditedEntitySoftTs {
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "factura_id")
|
||||
private Factura factura;
|
||||
|
||||
@Lob
|
||||
@Column(name = "descripcion")
|
||||
private String descripcion;
|
||||
|
||||
@Column(name = "cantidad")
|
||||
private Integer cantidad;
|
||||
|
||||
@Column(name = "base_linea", precision = 10, scale = 2)
|
||||
private BigDecimal baseLinea;
|
||||
|
||||
@Column(name = "iva_4_linea", precision = 10, scale = 2)
|
||||
private BigDecimal iva4Linea;
|
||||
|
||||
@Column(name = "iva_21_linea", precision = 10, scale = 2)
|
||||
private BigDecimal iva21Linea;
|
||||
|
||||
@Column(name = "total_linea", precision = 10, scale = 2)
|
||||
private BigDecimal totalLinea;
|
||||
|
||||
// Getters/Setters
|
||||
public Factura getFactura() { return factura; }
|
||||
public void setFactura(Factura factura) { this.factura = factura; }
|
||||
|
||||
public String getDescripcion() { return descripcion; }
|
||||
public void setDescripcion(String descripcion) { this.descripcion = descripcion; }
|
||||
|
||||
public Integer getCantidad() { return cantidad; }
|
||||
public void setCantidad(Integer cantidad) { this.cantidad = cantidad; }
|
||||
|
||||
public BigDecimal getBaseLinea() { return baseLinea; }
|
||||
public void setBaseLinea(BigDecimal baseLinea) { this.baseLinea = baseLinea; }
|
||||
|
||||
public BigDecimal getIva4Linea() { return iva4Linea; }
|
||||
public void setIva4Linea(BigDecimal iva4Linea) { this.iva4Linea = iva4Linea; }
|
||||
|
||||
public BigDecimal getIva21Linea() { return iva21Linea; }
|
||||
public void setIva21Linea(BigDecimal iva21Linea) { this.iva21Linea = iva21Linea; }
|
||||
|
||||
public BigDecimal getTotalLinea() { return totalLinea; }
|
||||
public void setTotalLinea(BigDecimal totalLinea) { this.totalLinea = totalLinea; }
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
package com.imprimelibros.erp.facturacion;
|
||||
|
||||
import com.imprimelibros.erp.common.jpa.AbstractAuditedEntitySoftTs;
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "facturas_pagos")
|
||||
public class FacturaPago extends AbstractAuditedEntitySoftTs {
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "factura_id")
|
||||
private Factura factura;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "metodo_pago", nullable = false, length = 30)
|
||||
private TipoPago metodoPago = TipoPago.otros;
|
||||
|
||||
@Column(name = "cantidad_pagada", precision = 10, scale = 2)
|
||||
private BigDecimal cantidadPagada;
|
||||
|
||||
@Column(name = "fecha_pago")
|
||||
private LocalDateTime fechaPago;
|
||||
|
||||
@Lob
|
||||
@Column(name = "notas")
|
||||
private String notas;
|
||||
|
||||
// Getters/Setters
|
||||
public Factura getFactura() { return factura; }
|
||||
public void setFactura(Factura factura) { this.factura = factura; }
|
||||
|
||||
public TipoPago getMetodoPago() { return metodoPago; }
|
||||
public void setMetodoPago(TipoPago metodoPago) { this.metodoPago = metodoPago; }
|
||||
|
||||
public BigDecimal getCantidadPagada() { return cantidadPagada; }
|
||||
public void setCantidadPagada(BigDecimal cantidadPagada) { this.cantidadPagada = cantidadPagada; }
|
||||
|
||||
public LocalDateTime getFechaPago() { return fechaPago; }
|
||||
public void setFechaPago(LocalDateTime fechaPago) { this.fechaPago = fechaPago; }
|
||||
|
||||
public String getNotas() { return notas; }
|
||||
public void setNotas(String notas) { this.notas = notas; }
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
package com.imprimelibros.erp.facturacion;
|
||||
|
||||
import com.imprimelibros.erp.common.jpa.AbstractAuditedEntitySoftTs;
|
||||
import jakarta.persistence.*;
|
||||
|
||||
@Entity
|
||||
@Table(name = "series_facturas")
|
||||
public class SerieFactura extends AbstractAuditedEntitySoftTs {
|
||||
|
||||
@Column(name = "nombre_serie", nullable = false, length = 100)
|
||||
private String nombreSerie;
|
||||
|
||||
@Column(name = "prefijo", nullable = false, length = 10)
|
||||
private String prefijo;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "tipo", nullable = false, length = 50)
|
||||
private TipoSerieFactura tipo = TipoSerieFactura.facturacion;
|
||||
|
||||
@Column(name = "numero_actual", nullable = false)
|
||||
private Integer numeroActual = 1;
|
||||
|
||||
public String getNombreSerie() { return nombreSerie; }
|
||||
public void setNombreSerie(String nombreSerie) { this.nombreSerie = nombreSerie; }
|
||||
|
||||
public String getPrefijo() { return prefijo; }
|
||||
public void setPrefijo(String prefijo) { this.prefijo = prefijo; }
|
||||
|
||||
public TipoSerieFactura getTipo() { return tipo; }
|
||||
public void setTipo(TipoSerieFactura tipo) { this.tipo = tipo; }
|
||||
|
||||
public Integer getNumeroActual() { return numeroActual; }
|
||||
public void setNumeroActual(Integer numeroActual) { this.numeroActual = numeroActual; }
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package com.imprimelibros.erp.facturacion;
|
||||
|
||||
public enum TipoPago {
|
||||
tpv_tarjeta,
|
||||
tpv_bizum,
|
||||
transferencia,
|
||||
otros
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
package com.imprimelibros.erp.facturacion;
|
||||
|
||||
public enum TipoSerieFactura {
|
||||
facturacion
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
package com.imprimelibros.erp.facturacion.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class FacturaLineaUpsertDto {
|
||||
|
||||
private Long id; // null => nueva línea
|
||||
|
||||
@NotBlank
|
||||
private String descripcion;
|
||||
|
||||
@NotNull
|
||||
private Integer cantidad;
|
||||
|
||||
@NotNull
|
||||
private BigDecimal baseLinea; // base imponible de la línea (sin IVA)
|
||||
|
||||
private boolean aplicaIva4;
|
||||
private boolean aplicaIva21;
|
||||
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
|
||||
public String getDescripcion() { return descripcion; }
|
||||
public void setDescripcion(String descripcion) { this.descripcion = descripcion; }
|
||||
|
||||
public Integer getCantidad() { return cantidad; }
|
||||
public void setCantidad(Integer cantidad) { this.cantidad = cantidad; }
|
||||
|
||||
public BigDecimal getBaseLinea() { return baseLinea; }
|
||||
public void setBaseLinea(BigDecimal baseLinea) { this.baseLinea = baseLinea; }
|
||||
|
||||
public boolean isAplicaIva4() { return aplicaIva4; }
|
||||
public void setAplicaIva4(boolean aplicaIva4) { this.aplicaIva4 = aplicaIva4; }
|
||||
|
||||
public boolean isAplicaIva21() { return aplicaIva21; }
|
||||
public void setAplicaIva21(boolean aplicaIva21) { this.aplicaIva21 = aplicaIva21; }
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
package com.imprimelibros.erp.facturacion.dto;
|
||||
|
||||
import com.imprimelibros.erp.facturacion.TipoPago;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class FacturaPagoUpsertDto {
|
||||
|
||||
private Long id; // null => nuevo pago
|
||||
|
||||
@NotNull
|
||||
private TipoPago metodoPago;
|
||||
|
||||
@NotNull
|
||||
private BigDecimal cantidadPagada;
|
||||
|
||||
private LocalDateTime fechaPago;
|
||||
private String notas;
|
||||
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
|
||||
public TipoPago getMetodoPago() { return metodoPago; }
|
||||
public void setMetodoPago(TipoPago metodoPago) { this.metodoPago = metodoPago; }
|
||||
|
||||
public BigDecimal getCantidadPagada() { return cantidadPagada; }
|
||||
public void setCantidadPagada(BigDecimal cantidadPagada) { this.cantidadPagada = cantidadPagada; }
|
||||
|
||||
public LocalDateTime getFechaPago() { return fechaPago; }
|
||||
public void setFechaPago(LocalDateTime fechaPago) { this.fechaPago = fechaPago; }
|
||||
|
||||
public String getNotas() { return notas; }
|
||||
public void setNotas(String notas) { this.notas = notas; }
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package com.imprimelibros.erp.facturacion.repo;
|
||||
|
||||
import com.imprimelibros.erp.facturacion.FacturaLinea;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface FacturaLineaRepository extends JpaRepository<FacturaLinea, Long> {
|
||||
List<FacturaLinea> findByFacturaId(Long facturaId);
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package com.imprimelibros.erp.facturacion.repo;
|
||||
|
||||
import com.imprimelibros.erp.facturacion.FacturaPago;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface FacturaPagoRepository extends JpaRepository<FacturaPago, Long> {
|
||||
List<FacturaPago> findByFacturaId(Long facturaId);
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package com.imprimelibros.erp.facturacion.repo;
|
||||
|
||||
import com.imprimelibros.erp.facturacion.Factura;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface FacturaRepository extends JpaRepository<Factura, Long> {
|
||||
Optional<Factura> findByNumeroFactura(String numeroFactura);
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
package com.imprimelibros.erp.facturacion.repo;
|
||||
|
||||
import com.imprimelibros.erp.facturacion.SerieFactura;
|
||||
import com.imprimelibros.erp.facturacion.TipoSerieFactura;
|
||||
import org.springframework.data.jpa.repository.*;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import jakarta.persistence.LockModeType;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface SerieFacturaRepository extends JpaRepository<SerieFactura, Long> {
|
||||
|
||||
Optional<SerieFactura> findByTipo(TipoSerieFactura tipo);
|
||||
|
||||
@Lock(LockModeType.PESSIMISTIC_WRITE)
|
||||
@Query("select s from SerieFactura s where s.id = :id")
|
||||
Optional<SerieFactura> findByIdForUpdate(@Param("id") Long id);
|
||||
}
|
||||
@ -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