mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-12 16:38:48 +00:00
726 lines
29 KiB
Java
726 lines
29 KiB
Java
package com.imprimelibros.erp.facturacion.service;
|
|
|
|
import com.imprimelibros.erp.common.Utils;
|
|
import com.imprimelibros.erp.configurationERP.VariableService;
|
|
import com.imprimelibros.erp.facturacion.*;
|
|
import com.imprimelibros.erp.facturacion.dto.DireccionFacturacionDto;
|
|
import com.imprimelibros.erp.facturacion.dto.FacturaDireccionMapper;
|
|
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.FacturaDireccionRepository;
|
|
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.PedidoDireccion;
|
|
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 com.imprimelibros.erp.direcciones.Direccion;
|
|
import com.imprimelibros.erp.direcciones.DireccionRepository;
|
|
|
|
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.Comparator;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Optional;
|
|
import java.util.stream.Collectors;
|
|
import java.util.Locale;
|
|
import java.math.BigDecimal;
|
|
import java.math.RoundingMode;
|
|
import java.security.Principal;
|
|
import java.text.Collator;
|
|
import java.time.Instant;
|
|
import java.time.LocalDate;
|
|
import java.time.LocalDateTime;
|
|
|
|
@Service
|
|
public class FacturacionService {
|
|
|
|
private final FacturaRepository facturaRepo;
|
|
private final SerieFacturaRepository serieRepo;
|
|
private final FacturaPagoRepository pagoRepo;
|
|
private final FacturaLineaRepository lineaFacturaRepository;
|
|
private final DireccionRepository direccionRepo;
|
|
private final PedidoLineaRepository pedidoLineaRepo;
|
|
private final UserService userService;
|
|
private final Utils utils;
|
|
private final MessageSource messageSource;
|
|
private final PedidoService pedidoService;
|
|
private final VariableService variableService;
|
|
|
|
public FacturacionService(
|
|
FacturaRepository facturaRepo,
|
|
FacturaLineaRepository lineaFacturaRepository,
|
|
SerieFacturaRepository serieRepo,
|
|
FacturaPagoRepository pagoRepo,
|
|
DireccionRepository direccionRepo,
|
|
PedidoLineaRepository pedidoLineaRepo,
|
|
UserService userService,
|
|
Utils utils,
|
|
MessageSource messageSource,
|
|
PedidoService pedidoService,
|
|
VariableService variableService) {
|
|
this.facturaRepo = facturaRepo;
|
|
this.lineaFacturaRepository = lineaFacturaRepository;
|
|
this.serieRepo = serieRepo;
|
|
this.pagoRepo = pagoRepo;
|
|
this.direccionRepo = direccionRepo;
|
|
this.pedidoLineaRepo = pedidoLineaRepo;
|
|
this.userService = userService;
|
|
this.utils = utils;
|
|
this.messageSource = messageSource;
|
|
this.pedidoService = pedidoService;
|
|
this.variableService = variableService;
|
|
}
|
|
|
|
public SerieFactura getDefaultSerieFactura() {
|
|
|
|
Long defaultSerieId = variableService.getValorEntero("serie_facturacion_default").longValue();
|
|
SerieFactura serie = serieRepo.findById(defaultSerieId).orElse(null);
|
|
if (serie == null) {
|
|
throw new IllegalStateException("No hay ninguna serie de facturación configurada.");
|
|
}
|
|
return serie;
|
|
}
|
|
|
|
public Factura getFactura(Long facturaId) {
|
|
return facturaRepo.findById(facturaId)
|
|
.orElseThrow(() -> new EntityNotFoundException("Factura no encontrada: " + facturaId));
|
|
}
|
|
|
|
public Long getFacturaIdFromPedidoId(Long pedidoId) {
|
|
Factura factura = facturaRepo.findByPedidoId(pedidoId);
|
|
if (factura == null) {
|
|
throw new EntityNotFoundException("Factura no encontrada para el pedido: " + pedidoId);
|
|
}
|
|
return factura.getId();
|
|
}
|
|
|
|
// -----------------------
|
|
// 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);
|
|
}
|
|
if(pedido.getEnvio() > 0){
|
|
FacturaLinea lineaEnvio = new FacturaLinea();
|
|
lineaEnvio.setDescripcion(messageSource.getMessage("facturas.lineas.gastos-envio", null, "Gastos de envío", locale));
|
|
lineaEnvio.setCantidad(1);
|
|
BigDecimal baseEnvio = BigDecimal.valueOf(pedido.getEnvio()).setScale(2, RoundingMode.HALF_UP);
|
|
lineaEnvio.setBaseLinea(baseEnvio);
|
|
BigDecimal iva21Envio = baseEnvio.multiply(BigDecimal.valueOf(0.21)).setScale(2, RoundingMode.HALF_UP);
|
|
lineaEnvio.setIva21Linea(iva21Envio);
|
|
lineaEnvio.setIva4Linea(BigDecimal.ZERO);
|
|
lineaEnvio.setTotalLinea(baseEnvio.add(iva21Envio));
|
|
lineaEnvio.setCreatedBy(pedido.getCreatedBy());
|
|
lineaEnvio.setCreatedAt(Instant.now());
|
|
lineaEnvio.setFactura(factura);
|
|
lineasFactura.add(lineaEnvio);
|
|
}
|
|
PedidoDireccion direccionPedido = pedidoService.getDireccionFacturacionPedido(pedido.getId());
|
|
if(direccionPedido == null){
|
|
throw new IllegalStateException("El pedido no tiene una dirección de facturación asociada.");
|
|
}
|
|
FacturaDireccion fd = FacturaDireccionMapper.fromPedidoDireccion(direccionPedido);
|
|
|
|
factura.addDireccion(fd);
|
|
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;
|
|
}
|
|
|
|
@Transactional
|
|
public Factura crearNuevaFactura(Long userId, Long serieId, Long direccionId, Long facturaRectificadaId) {
|
|
User cliente = userService.findById(userId);
|
|
if (cliente == null) {
|
|
throw new EntityNotFoundException("Cliente no encontrado: " + userId);
|
|
}
|
|
|
|
SerieFactura serie = serieRepo.findById(serieId)
|
|
.orElseThrow(() -> new EntityNotFoundException("Serie no encontrada: " + serieId));
|
|
|
|
Factura factura = new Factura();
|
|
factura.setCliente(cliente);
|
|
factura.setPedidoId(null);
|
|
factura.setSerie(serie);
|
|
factura.setEstado(EstadoFactura.borrador);
|
|
factura.setEstadoPago(EstadoPagoFactura.pendiente);
|
|
factura.setFechaEmision(LocalDateTime.now());
|
|
factura.setCreatedAt(Instant.now());
|
|
factura.setUpdatedAt(Instant.now());
|
|
factura.setNumeroFactura(null);
|
|
factura.setBaseImponible(BigDecimal.ZERO);
|
|
factura.setIva4(BigDecimal.ZERO);
|
|
factura.setIva21(BigDecimal.ZERO);
|
|
factura.setTotalFactura(BigDecimal.ZERO);
|
|
factura.setTotalPagado(BigDecimal.ZERO);
|
|
factura.setLineas(new ArrayList<>());
|
|
factura.setPagos(new ArrayList<>());
|
|
Direccion direccion = direccionRepo.findById(direccionId)
|
|
.orElseThrow(() -> new EntityNotFoundException("Dirección de factura no encontrada: " + direccionId));
|
|
FacturaDireccion facturaDireccion = FacturaDireccionMapper.fromDireccion(direccion);
|
|
factura.addDireccion(facturaDireccion);
|
|
if(facturaRectificadaId != null){
|
|
Factura facturaRectificada = facturaRepo.findById(facturaRectificadaId)
|
|
.orElseThrow(() -> new EntityNotFoundException("Factura rectificada no encontrada: " + facturaRectificadaId));
|
|
factura.setFacturaRectificativa(facturaRectificada);
|
|
facturaRectificada.setFacturaRectificada(factura);
|
|
}
|
|
return facturaRepo.save(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 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());
|
|
|
|
}
|
|
upsertDireccionFacturacion(facturaId, dto.getDireccionFacturacion());
|
|
|
|
facturaRepo.save(factura);
|
|
}
|
|
|
|
@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(next + 1);
|
|
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);
|
|
}
|
|
|
|
@Transactional
|
|
public Boolean upsertDireccionFacturacion(Long facturaId, DireccionFacturacionDto direccionData) {
|
|
try {
|
|
Factura factura = facturaRepo.findById(facturaId)
|
|
.orElseThrow(() -> new EntityNotFoundException("Factura no encontrada: " + facturaId));
|
|
|
|
// ✅ Solo editable si borrador (tu regla actual para cabecera/dirección)
|
|
if (factura.getEstado() != EstadoFactura.borrador) {
|
|
throw new IllegalStateException("Solo se puede guardar dirección en borrador.");
|
|
}
|
|
|
|
factura.getDirecciones().clear();
|
|
factura.addDireccion(direccionData.toFacturaDireccion());
|
|
facturaRepo.save(factura);
|
|
return true;
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public Map<String, Object> getForSelectFacturasRectificables(String q1, String q2, Long userId) {
|
|
try {
|
|
String search = Optional.ofNullable(q1).orElse(q2);
|
|
if (search != null) {
|
|
search = search.trim();
|
|
}
|
|
final String q = (search == null || search.isEmpty())
|
|
? null
|
|
: search.toLowerCase();
|
|
|
|
List<Factura> all = facturaRepo.findByClienteIdAndEstadoAndEstadoPagoAndSerieId(
|
|
userId,
|
|
EstadoFactura.validada,
|
|
EstadoPagoFactura.pagada,
|
|
variableService.getValorEntero("serie_facturacion_default").longValue());
|
|
|
|
// Mapear a opciones id/text con i18n y filtrar por búsqueda si llega
|
|
List<Map<String, String>> options = all.stream()
|
|
.map(f -> {
|
|
String id = f.getId().toString();
|
|
String text = f.getNumeroFactura();
|
|
Map<String, String> m = new HashMap<>();
|
|
m.put("id", id); // lo normal en Select2: id = valor que guardarás (code3)
|
|
m.put("text", text); // texto mostrado, i18n con fallback a keyword
|
|
return m;
|
|
})
|
|
.filter(opt -> {
|
|
if (q == null || q.isEmpty())
|
|
return true;
|
|
String text = opt.get("text").toLowerCase();
|
|
return text.contains(q);
|
|
})
|
|
.sorted(Comparator.comparing(m -> m.get("text"), Collator.getInstance()))
|
|
.collect(Collectors.toList());
|
|
|
|
// Estructura Select2
|
|
Map<String, Object> resp = new HashMap<>();
|
|
resp.put("results", options);
|
|
return resp;
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
return Map.of("results", List.of());
|
|
}
|
|
}
|
|
|
|
private String buildNumeroFactura(String prefijo, long numero) {
|
|
String pref = (prefijo == null) ? "" : prefijo.trim();
|
|
String num = String.format("%05d", numero);
|
|
return pref.isBlank() ? num : (pref + " " + num + "/" + LocalDate.now().getYear());
|
|
}
|
|
|
|
|
|
// -----------------------
|
|
// 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) {
|
|
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.setBaseLinea(scale2(dto.getBase()));
|
|
|
|
linea.setIva4Linea(dto.getIva4());
|
|
linea.setIva21Linea(dto.getIva21());
|
|
|
|
linea.setTotalLinea(scale2(linea.getBaseLinea()
|
|
.add(nvl(linea.getIva4Linea()))
|
|
.add(nvl(linea.getIva21Linea()))));
|
|
|
|
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, Principal principal) {
|
|
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);
|
|
pago.setCreatedBy(Utils.currentUser(principal));
|
|
pago.setCreatedAt(Instant.now());
|
|
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());
|
|
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());
|
|
|
|
recalcularTotales(factura);
|
|
return facturaRepo.save(factura);
|
|
}
|
|
|
|
@Transactional
|
|
public Factura borrarPago(Long facturaId, Long pagoId, Principal principal) {
|
|
Factura factura = facturaRepo.findById(facturaId)
|
|
.orElseThrow(() -> new EntityNotFoundException("Factura no encontrada: " + facturaId));
|
|
|
|
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);
|
|
}
|
|
|
|
// -----------------------
|
|
// 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()) {
|
|
if (p.getDeletedAt() != null)
|
|
continue;
|
|
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);
|
|
}
|
|
|
|
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("'", "'");
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
}
|