mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-13 00:48:49 +00:00
Compare commits
2 Commits
98a5fcaa0b
...
d7b5dedb38
| Author | SHA1 | Date | |
|---|---|---|---|
| d7b5dedb38 | |||
| 089641b601 |
3747
logs/erp.log
3747
logs/erp.log
File diff suppressed because it is too large
Load Diff
@ -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,9 @@
|
||||
package com.imprimelibros.erp.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
|
||||
@Configuration
|
||||
@EnableMethodSecurity
|
||||
public class MethodSecurityConfig {
|
||||
}
|
||||
@ -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 Long numeroActual = 1L;
|
||||
|
||||
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 Long getNumeroActual() { return numeroActual; }
|
||||
public void setNumeroActual(Long 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,201 @@
|
||||
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.SerieFactura;
|
||||
import com.imprimelibros.erp.facturacion.TipoSerieFactura;
|
||||
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.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/configuracion/series-facturacion")
|
||||
@PreAuthorize("hasRole('SUPERADMIN')")
|
||||
public class SeriesFacturacionController {
|
||||
|
||||
private final SerieFacturaRepository repo;
|
||||
private final TranslationService translationService;
|
||||
private final MessageSource messageSource;
|
||||
|
||||
public SeriesFacturacionController(SerieFacturaRepository repo, TranslationService translationService,
|
||||
MessageSource messageSource) {
|
||||
this.repo = repo;
|
||||
this.translationService = translationService;
|
||||
this.messageSource = messageSource;
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
// VISTA
|
||||
// -----------------------------
|
||||
@GetMapping
|
||||
public String listView(Model model, Locale locale) {
|
||||
|
||||
List<String> keys = List.of(
|
||||
"series-facturacion.modal.title.add",
|
||||
"series-facturacion.modal.title.edit",
|
||||
"app.guardar",
|
||||
"app.cancelar",
|
||||
"app.eliminar",
|
||||
"series-facturacion.delete.title",
|
||||
"series-facturacion.delete.text",
|
||||
"series-facturacion.delete.ok.title",
|
||||
"series-facturacion.delete.ok.text");
|
||||
|
||||
Map<String, String> translations = translationService.getTranslations(locale, keys);
|
||||
model.addAttribute("languageBundle", translations);
|
||||
|
||||
return "imprimelibros/configuracion/series-facturas/series-facturas-list";
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
// API: DataTables (server-side)
|
||||
// -----------------------------
|
||||
@GetMapping("/api/datatables")
|
||||
@ResponseBody
|
||||
public DataTablesResponse<Map<String, Object>> datatables(HttpServletRequest request, Locale locale) {
|
||||
|
||||
DataTablesRequest dt = DataTablesParser.from(request);
|
||||
|
||||
Specification<SerieFactura> notDeleted = (root, q, cb) -> cb.isNull(root.get("deletedAt"));
|
||||
long total = repo.count(notDeleted);
|
||||
|
||||
return DataTable
|
||||
.of(repo, SerieFactura.class, dt, List.of("nombreSerie", "prefijo"))
|
||||
.where(notDeleted)
|
||||
.orderable(List.of("id", "nombreSerie", "prefijo", "tipo", "numeroActual"))
|
||||
.onlyAddedColumns()
|
||||
.add("id", SerieFactura::getId)
|
||||
.add("nombre_serie", SerieFactura::getNombreSerie)
|
||||
.add("prefijo", SerieFactura::getPrefijo)
|
||||
.add("tipo", s -> s.getTipo() != null ? s.getTipo().name() : null)
|
||||
.add("tipo_label", s -> {
|
||||
if (s.getTipo() == null)
|
||||
return null;
|
||||
return messageSource.getMessage(
|
||||
"series-facturacion.tipo." + s.getTipo().name(),
|
||||
null,
|
||||
s.getTipo().name(),
|
||||
locale);
|
||||
})
|
||||
.add("numero_actual", SerieFactura::getNumeroActual)
|
||||
.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"
|
||||
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"
|
||||
data-id="%d">
|
||||
<i class="ri-delete-bin-5-line"></i>
|
||||
</button>
|
||||
</div>
|
||||
""".formatted(s.getId(), s.getId()))
|
||||
.toJson(total);
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
// API: CREATE
|
||||
// -----------------------------
|
||||
@PostMapping(value = "/api", consumes = "application/json")
|
||||
@ResponseBody
|
||||
public Map<String, Object> create(@RequestBody SerieFacturaPayload payload) {
|
||||
validate(payload);
|
||||
|
||||
SerieFactura s = new SerieFactura();
|
||||
s.setNombreSerie(payload.nombre_serie.trim());
|
||||
s.setPrefijo(payload.prefijo.trim());
|
||||
s.setTipo(TipoSerieFactura.facturacion); // fijo
|
||||
s.setNumeroActual(payload.numero_actual);
|
||||
|
||||
repo.save(s);
|
||||
return Map.of("ok", true, "id", s.getId());
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
// API: UPDATE
|
||||
// -----------------------------
|
||||
@PutMapping(value = "/api/{id}", consumes = "application/json")
|
||||
@ResponseBody
|
||||
public Map<String, Object> update(@PathVariable Long id, @RequestBody SerieFacturaPayload payload) {
|
||||
validate(payload);
|
||||
|
||||
SerieFactura s = repo.findById(id)
|
||||
.orElseThrow(() -> new EntityNotFoundException("Serie no encontrada: " + id));
|
||||
|
||||
if (s.getDeletedAt() != null) {
|
||||
throw new IllegalStateException("No se puede editar una serie eliminada.");
|
||||
}
|
||||
|
||||
s.setNombreSerie(payload.nombre_serie.trim());
|
||||
s.setPrefijo(payload.prefijo.trim());
|
||||
s.setTipo(TipoSerieFactura.facturacion);
|
||||
s.setNumeroActual(payload.numero_actual);
|
||||
|
||||
repo.save(s);
|
||||
return Map.of("ok", true);
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
// API: DELETE (soft)
|
||||
// -----------------------------
|
||||
@DeleteMapping("/api/{id}")
|
||||
@ResponseBody
|
||||
public ResponseEntity<?> delete(@PathVariable Long id) {
|
||||
SerieFactura s = repo.findById(id)
|
||||
.orElseThrow(() -> new EntityNotFoundException("Serie no encontrada: " + id));
|
||||
|
||||
if (s.getDeletedAt() == null) {
|
||||
s.setDeletedAt(Instant.now());
|
||||
s.setDeletedBy(null); // luego lo conectamos al usuario actual
|
||||
repo.save(s);
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(Map.of("ok", true));
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
// Payload + validación
|
||||
// -----------------------------
|
||||
public static class SerieFacturaPayload {
|
||||
public String nombre_serie;
|
||||
public String prefijo;
|
||||
public String tipo; // lo manda UI, pero en backend lo fijamos
|
||||
public Long numero_actual;
|
||||
}
|
||||
|
||||
private void validate(SerieFacturaPayload p) {
|
||||
if (p == null)
|
||||
throw new IllegalArgumentException("Body requerido.");
|
||||
if (p.nombre_serie == null || p.nombre_serie.trim().isBlank()) {
|
||||
throw new IllegalArgumentException("nombre_serie es obligatorio.");
|
||||
}
|
||||
if (p.prefijo == null || p.prefijo.trim().isBlank()) {
|
||||
throw new IllegalArgumentException("prefijo es obligatorio.");
|
||||
}
|
||||
if (p.prefijo.trim().length() > 10) {
|
||||
throw new IllegalArgumentException("prefijo máximo 10 caracteres.");
|
||||
}
|
||||
if (p.numero_actual == null || p.numero_actual < 1) {
|
||||
throw new IllegalArgumentException("numero_actual debe ser >= 1.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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,28 @@
|
||||
package com.imprimelibros.erp.facturacion.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
public class SerieFacturaForm {
|
||||
|
||||
@NotBlank
|
||||
@Size(max = 100)
|
||||
private String nombreSerie;
|
||||
|
||||
@NotBlank
|
||||
@Size(max = 10)
|
||||
private String prefijo;
|
||||
|
||||
@NotNull
|
||||
private Long numeroActual;
|
||||
|
||||
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 Long getNumeroActual() { return numeroActual; }
|
||||
public void setNumeroActual(Long numeroActual) { this.numeroActual = numeroActual; }
|
||||
}
|
||||
@ -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,22 @@
|
||||
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 org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
import jakarta.persistence.LockModeType;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface SerieFacturaRepository extends JpaRepository<SerieFactura, Long>, JpaSpecificationExecutor<SerieFactura> {
|
||||
|
||||
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);
|
||||
List<SerieFactura> findAllByDeletedAtIsNullOrderByNombreSerieAsc();
|
||||
}
|
||||
@ -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(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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
407
src/main/resources/db/changelog/changesets/0023-facturacion.yml
Normal file
407
src/main/resources/db/changelog/changesets/0023-facturacion.yml
Normal file
@ -0,0 +1,407 @@
|
||||
databaseChangeLog:
|
||||
|
||||
- changeSet:
|
||||
id: 20251230-01-pedidos-lineas-enviado
|
||||
author: jjo
|
||||
changes:
|
||||
- modifyDataType:
|
||||
tableName: pedidos_lineas
|
||||
columnName: estado
|
||||
newDataType: >
|
||||
ENUM(
|
||||
'pendiente_pago',
|
||||
'procesando_pago',
|
||||
'denegado_pago',
|
||||
'aprobado',
|
||||
'maquetacion',
|
||||
'haciendo_ferro',
|
||||
'esperando_aceptacion_ferro',
|
||||
'produccion',
|
||||
'terminado',
|
||||
'enviado',
|
||||
'cancelado'
|
||||
)
|
||||
rollback:
|
||||
- modifyDataType:
|
||||
tableName: pedidos_lineas
|
||||
columnName: estado
|
||||
newDataType: >
|
||||
ENUM(
|
||||
'pendiente_pago',
|
||||
'procesando_pago',
|
||||
'denegado_pago',
|
||||
'aprobado',
|
||||
'maquetacion',
|
||||
'haciendo_ferro',
|
||||
'esperando_aceptacion_ferro',
|
||||
'produccion',
|
||||
'terminado',
|
||||
'cancelado'
|
||||
)
|
||||
|
||||
# -------------------------------------------------
|
||||
|
||||
- changeSet:
|
||||
id: 20251230-02-series-facturas
|
||||
author: jjo
|
||||
changes:
|
||||
- createTable:
|
||||
tableName: series_facturas
|
||||
columns:
|
||||
- column:
|
||||
name: id
|
||||
type: BIGINT
|
||||
autoIncrement: true
|
||||
constraints:
|
||||
primaryKey: true
|
||||
nullable: false
|
||||
|
||||
- column:
|
||||
name: nombre_serie
|
||||
type: VARCHAR(100)
|
||||
constraints:
|
||||
nullable: false
|
||||
|
||||
- column:
|
||||
name: prefijo
|
||||
type: VARCHAR(10)
|
||||
constraints:
|
||||
nullable: false
|
||||
|
||||
- column:
|
||||
name: tipo
|
||||
type: ENUM('facturacion')
|
||||
defaultValue: facturacion
|
||||
|
||||
- column:
|
||||
name: numero_actual
|
||||
type: BIGINT
|
||||
defaultValueNumeric: 1
|
||||
|
||||
- column:
|
||||
name: created_at
|
||||
type: TIMESTAMP
|
||||
- column:
|
||||
name: updated_at
|
||||
type: TIMESTAMP
|
||||
- column:
|
||||
name: deleted_at
|
||||
type: TIMESTAMP
|
||||
|
||||
- column:
|
||||
name: created_by
|
||||
type: BIGINT
|
||||
- column:
|
||||
name: updated_by
|
||||
type: BIGINT
|
||||
- column:
|
||||
name: deleted_by
|
||||
type: BIGINT
|
||||
|
||||
- addForeignKeyConstraint:
|
||||
constraintName: fk_series_facturas_created_by
|
||||
baseTableName: series_facturas
|
||||
baseColumnNames: created_by
|
||||
referencedTableName: users
|
||||
referencedColumnNames: id
|
||||
onDelete: SET NULL
|
||||
|
||||
- addForeignKeyConstraint:
|
||||
constraintName: fk_series_facturas_updated_by
|
||||
baseTableName: series_facturas
|
||||
baseColumnNames: updated_by
|
||||
referencedTableName: users
|
||||
referencedColumnNames: id
|
||||
onDelete: SET NULL
|
||||
|
||||
- addForeignKeyConstraint:
|
||||
constraintName: fk_series_facturas_deleted_by
|
||||
baseTableName: series_facturas
|
||||
baseColumnNames: deleted_by
|
||||
referencedTableName: users
|
||||
referencedColumnNames: id
|
||||
onDelete: SET NULL
|
||||
|
||||
rollback:
|
||||
- dropTable:
|
||||
tableName: series_facturas
|
||||
|
||||
# -------------------------------------------------
|
||||
|
||||
- changeSet:
|
||||
id: 20251230-03-facturas
|
||||
author: jjo
|
||||
changes:
|
||||
- createTable:
|
||||
tableName: facturas
|
||||
columns:
|
||||
- column:
|
||||
name: id
|
||||
type: BIGINT
|
||||
autoIncrement: true
|
||||
constraints:
|
||||
primaryKey: true
|
||||
nullable: false
|
||||
|
||||
- column:
|
||||
name: pedido_id
|
||||
type: BIGINT
|
||||
|
||||
- column:
|
||||
name: factura_rectificada_id
|
||||
type: BIGINT
|
||||
|
||||
- column:
|
||||
name: factura_rectificativa_id
|
||||
type: BIGINT
|
||||
|
||||
- column:
|
||||
name: cliente_id
|
||||
type: BIGINT
|
||||
|
||||
- column:
|
||||
name: serie_id
|
||||
type: BIGINT
|
||||
|
||||
- column:
|
||||
name: numero_factura
|
||||
type: VARCHAR(50)
|
||||
|
||||
- column:
|
||||
name: estado
|
||||
type: ENUM('borrador','validada')
|
||||
defaultValue: borrador
|
||||
|
||||
- column:
|
||||
name: estado_pago
|
||||
type: ENUM('pendiente','pagada','cancelada')
|
||||
defaultValue: pendiente
|
||||
|
||||
- column:
|
||||
name: tipo_pago
|
||||
type: ENUM('tpv_tarjeta','tpv_bizum','transferencia','otros')
|
||||
defaultValue: otros
|
||||
|
||||
- column:
|
||||
name: fecha_emision
|
||||
type: DATETIME
|
||||
|
||||
- column:
|
||||
name: base_imponible
|
||||
type: DECIMAL(10,2)
|
||||
- column:
|
||||
name: iva_4
|
||||
type: DECIMAL(10,2)
|
||||
- column:
|
||||
name: iva_21
|
||||
type: DECIMAL(10,2)
|
||||
- column:
|
||||
name: total_factura
|
||||
type: DECIMAL(10,2)
|
||||
- column:
|
||||
name: total_pagado
|
||||
type: DECIMAL(10,2)
|
||||
defaultValueNumeric: 0.00
|
||||
|
||||
- column:
|
||||
name: notas
|
||||
type: TEXT
|
||||
|
||||
- column:
|
||||
name: created_at
|
||||
type: TIMESTAMP
|
||||
- column:
|
||||
name: updated_at
|
||||
type: TIMESTAMP
|
||||
- column:
|
||||
name: deleted_at
|
||||
type: TIMESTAMP
|
||||
|
||||
- column:
|
||||
name: created_by
|
||||
type: BIGINT
|
||||
- column:
|
||||
name: updated_by
|
||||
type: BIGINT
|
||||
- column:
|
||||
name: deleted_by
|
||||
type: BIGINT
|
||||
|
||||
- addUniqueConstraint:
|
||||
constraintName: uq_facturas_numero_factura
|
||||
tableName: facturas
|
||||
columnNames: numero_factura
|
||||
|
||||
- addForeignKeyConstraint:
|
||||
constraintName: fk_facturas_pedido
|
||||
baseTableName: facturas
|
||||
baseColumnNames: pedido_id
|
||||
referencedTableName: pedidos
|
||||
referencedColumnNames: id
|
||||
|
||||
- addForeignKeyConstraint:
|
||||
constraintName: fk_facturas_cliente
|
||||
baseTableName: facturas
|
||||
baseColumnNames: cliente_id
|
||||
referencedTableName: users
|
||||
referencedColumnNames: id
|
||||
|
||||
- addForeignKeyConstraint:
|
||||
constraintName: fk_facturas_serie
|
||||
baseTableName: facturas
|
||||
baseColumnNames: serie_id
|
||||
referencedTableName: series_facturas
|
||||
referencedColumnNames: id
|
||||
|
||||
- addForeignKeyConstraint:
|
||||
constraintName: fk_facturas_rectificada
|
||||
baseTableName: facturas
|
||||
baseColumnNames: factura_rectificada_id
|
||||
referencedTableName: facturas
|
||||
referencedColumnNames: id
|
||||
|
||||
- addForeignKeyConstraint:
|
||||
constraintName: fk_facturas_rectificativa
|
||||
baseTableName: facturas
|
||||
baseColumnNames: factura_rectificativa_id
|
||||
referencedTableName: facturas
|
||||
referencedColumnNames: id
|
||||
|
||||
rollback:
|
||||
- dropTable:
|
||||
tableName: facturas
|
||||
|
||||
# -------------------------------------------------
|
||||
|
||||
- changeSet:
|
||||
id: 20251230-04-facturas-lineas
|
||||
author: jjo
|
||||
changes:
|
||||
- createTable:
|
||||
tableName: facturas_lineas
|
||||
columns:
|
||||
- column:
|
||||
name: id
|
||||
type: BIGINT
|
||||
autoIncrement: true
|
||||
constraints:
|
||||
primaryKey: true
|
||||
nullable: false
|
||||
|
||||
- column:
|
||||
name: factura_id
|
||||
type: BIGINT
|
||||
- column:
|
||||
name: descripcion
|
||||
type: TEXT
|
||||
- column:
|
||||
name: cantidad
|
||||
type: INT
|
||||
- column:
|
||||
name: base_linea
|
||||
type: DECIMAL(10,2)
|
||||
- column:
|
||||
name: iva_4_linea
|
||||
type: DECIMAL(10,2)
|
||||
- column:
|
||||
name: iva_21_linea
|
||||
type: DECIMAL(10,2)
|
||||
- column:
|
||||
name: total_linea
|
||||
type: DECIMAL(10,2)
|
||||
|
||||
- column:
|
||||
name: created_at
|
||||
type: TIMESTAMP
|
||||
- column:
|
||||
name: updated_at
|
||||
type: TIMESTAMP
|
||||
- column:
|
||||
name: deleted_at
|
||||
type: TIMESTAMP
|
||||
|
||||
- column:
|
||||
name: created_by
|
||||
type: BIGINT
|
||||
- column:
|
||||
name: updated_by
|
||||
type: BIGINT
|
||||
- column:
|
||||
name: deleted_by
|
||||
type: BIGINT
|
||||
|
||||
- addForeignKeyConstraint:
|
||||
constraintName: fk_facturas_lineas_factura
|
||||
baseTableName: facturas_lineas
|
||||
baseColumnNames: factura_id
|
||||
referencedTableName: facturas
|
||||
referencedColumnNames: id
|
||||
|
||||
rollback:
|
||||
- dropTable:
|
||||
tableName: facturas_lineas
|
||||
|
||||
# -------------------------------------------------
|
||||
|
||||
- changeSet:
|
||||
id: 20251230-05-facturas-pagos
|
||||
author: jjo
|
||||
changes:
|
||||
- createTable:
|
||||
tableName: facturas_pagos
|
||||
columns:
|
||||
- column:
|
||||
name: id
|
||||
type: BIGINT
|
||||
autoIncrement: true
|
||||
constraints:
|
||||
primaryKey: true
|
||||
nullable: false
|
||||
|
||||
- column:
|
||||
name: factura_id
|
||||
type: BIGINT
|
||||
- column:
|
||||
name: metodo_pago
|
||||
type: ENUM('tpv_tarjeta','tpv_bizum','transferencia','otros')
|
||||
defaultValue: otros
|
||||
- column:
|
||||
name: cantidad_pagada
|
||||
type: DECIMAL(10,2)
|
||||
- column:
|
||||
name: fecha_pago
|
||||
type: DATETIME
|
||||
- column:
|
||||
name: notas
|
||||
type: TEXT
|
||||
|
||||
- column:
|
||||
name: created_at
|
||||
type: TIMESTAMP
|
||||
- column:
|
||||
name: updated_at
|
||||
type: TIMESTAMP
|
||||
- column:
|
||||
name: deleted_at
|
||||
type: TIMESTAMP
|
||||
|
||||
- column:
|
||||
name: created_by
|
||||
type: BIGINT
|
||||
- column:
|
||||
name: updated_by
|
||||
type: BIGINT
|
||||
- column:
|
||||
name: deleted_by
|
||||
type: BIGINT
|
||||
|
||||
- addForeignKeyConstraint:
|
||||
constraintName: fk_facturas_pagos_factura
|
||||
baseTableName: facturas_pagos
|
||||
baseColumnNames: factura_id
|
||||
referencedTableName: facturas
|
||||
referencedColumnNames: id
|
||||
|
||||
rollback:
|
||||
- dropTable:
|
||||
tableName: facturas_pagos
|
||||
@ -42,4 +42,6 @@ databaseChangeLog:
|
||||
- include:
|
||||
file: db/changelog/changesets/0021-add-email-and-is-palets-to-pedidos-direcciones.yml
|
||||
- include:
|
||||
file: db/changelog/changesets/0022-add-estados-pago-to-pedidos-lineas-3.yml
|
||||
file: db/changelog/changesets/0022-add-estados-pago-to-pedidos-lineas-3.yml
|
||||
- include:
|
||||
file: db/changelog/changesets/0023-facturacion.yml
|
||||
@ -29,4 +29,6 @@ app.sidebar.direcciones=Mis Direcciones
|
||||
app.sidebar.direcciones-admin=Administrar Direcciones
|
||||
app.sidebar.gestion-pagos=Gestión de Pagos
|
||||
|
||||
app.errors.403=No tienes permiso para acceder a esta página.
|
||||
app.errors.403=No tienes permiso para acceder a esta página.
|
||||
|
||||
app.validation.required=Campo obligatorio
|
||||
26
src/main/resources/i18n/series_facturacion_es.properties
Normal file
26
src/main/resources/i18n/series_facturacion_es.properties
Normal file
@ -0,0 +1,26 @@
|
||||
series-facturacion.title=Series de Facturación
|
||||
series-facturacion.breadcrumb=Series de Facturación
|
||||
|
||||
series-facturacion.tabla.id=ID
|
||||
series-facturacion.tabla.nombre=Nombre
|
||||
series-facturacion.tabla.prefijo=Prefijo
|
||||
series-facturacion.tabla.tipo=Tipo
|
||||
series-facturacion.tabla.numero-actual=Número Actual
|
||||
series-facturacion.tabla.acciones=Acciones
|
||||
|
||||
series-facturacion.delete.title=¿Estás seguro de que deseas eliminar esta serie de facturación?
|
||||
series-facturacion.delete.text=Esta acción no se puede deshacer.
|
||||
series-facturacion.delete.ok.title=Serie de facturación eliminada
|
||||
series-facturacion.delete.ok.text=La serie de facturación ha sido eliminada correctamente.
|
||||
|
||||
series-facturacion.tipo.facturacion=Facturación
|
||||
|
||||
series-facturacion.form.nombre=Nombre
|
||||
series-facturacion.form.prefijo=Prefijo
|
||||
series-facturacion.form.prefijo.help=Ej: FAC, DIG, REC...
|
||||
series-facturacion.form.tipo=Tipo
|
||||
series-facturacion.tipo.facturacion=Facturación
|
||||
series-facturacion.form.numero-actual=Número actual
|
||||
|
||||
series-facturacion.modal.title.add=Nueva Serie de Facturación
|
||||
series-facturacion.modal.title.edit=Editar Serie de Facturación
|
||||
@ -0,0 +1,222 @@
|
||||
/* global $, bootstrap, window */
|
||||
$(() => {
|
||||
// si jQuery está cargado, añade CSRF a AJAX
|
||||
const csrfToken = document.querySelector('meta[name="_csrf"]')?.getAttribute('content');
|
||||
const csrfHeader = document.querySelector('meta[name="_csrf_header"]')?.getAttribute('content');
|
||||
if (window.$ && csrfToken && csrfHeader) {
|
||||
$.ajaxSetup({
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader(csrfHeader, csrfToken);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const language = document.documentElement.lang || 'es-ES';
|
||||
|
||||
const $table = $('#series-datatable'); // en tu HTML está así, aunque el id sea raro
|
||||
const $addBtn = $('#addButton');
|
||||
|
||||
const $modal = $('#serieFacturacionModal');
|
||||
const modal = new bootstrap.Modal($modal[0]);
|
||||
|
||||
const $form = $('#serieFacturacionForm');
|
||||
const $alert = $('#serieFacturacionAlert');
|
||||
const $saveBtn = $('#serieFacturacionSaveBtn');
|
||||
|
||||
function showError(msg) {
|
||||
$alert.removeClass('d-none').text(msg || 'Error');
|
||||
}
|
||||
|
||||
function clearError() {
|
||||
$alert.addClass('d-none').text('');
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
clearError();
|
||||
$form[0].reset();
|
||||
$form.removeClass('was-validated');
|
||||
$('#serie_id').val('');
|
||||
$('#numero_actual').val('1');
|
||||
$('#tipo').val('facturacion');
|
||||
}
|
||||
|
||||
function openAddModal() {
|
||||
resetForm();
|
||||
$('#serieFacturacionModalTitle').text(window.languageBundle?.['series-facturacion.modal.title.add'] || 'Añadir serie');
|
||||
modal.show();
|
||||
}
|
||||
|
||||
function openEditModal(row) {
|
||||
resetForm();
|
||||
$('#serieFacturacionModalTitle').text(window.languageBundle?.['series-facturacion.modal.title.edit'] || 'Editar serie');
|
||||
|
||||
$('#serie_id').val(row.id);
|
||||
$('#nombre_serie').val(row.nombre_serie);
|
||||
$('#prefijo').val(row.prefijo);
|
||||
$('#tipo').val(row.tipo || 'facturacion');
|
||||
$('#numero_actual').val(row.numero_actual);
|
||||
|
||||
modal.show();
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
// DataTable server-side
|
||||
// -----------------------------
|
||||
const dt = $table.DataTable({
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
searching: true,
|
||||
orderMulti: false,
|
||||
pageLength: 10,
|
||||
lengthMenu: [10, 25, 50, 100],
|
||||
|
||||
language: { url: '/assets/libs/datatables/i18n/' + language + '.json' },
|
||||
|
||||
ajax: {
|
||||
url: '/configuracion/series-facturacion/api/datatables',
|
||||
type: 'GET',
|
||||
dataSrc: function (json) {
|
||||
// DataTables espera {draw, recordsTotal, recordsFiltered, data}
|
||||
return json.data || [];
|
||||
},
|
||||
error: function (xhr) {
|
||||
console.error('DataTables error', xhr);
|
||||
}
|
||||
},
|
||||
|
||||
columns: [
|
||||
{ data: 'id' },
|
||||
{ data: 'nombre_serie' },
|
||||
{ data: 'prefijo' },
|
||||
{ data: 'tipo_label', name: 'tipo' },
|
||||
{ data: 'numero_actual' },
|
||||
{
|
||||
data: 'actions',
|
||||
orderable: false,
|
||||
searchable: false
|
||||
}
|
||||
],
|
||||
|
||||
order: [[0, 'desc']]
|
||||
});
|
||||
|
||||
// -----------------------------
|
||||
// Add
|
||||
// -----------------------------
|
||||
$addBtn.on('click', () => openAddModal());
|
||||
|
||||
// -----------------------------
|
||||
// Edit click
|
||||
// -----------------------------
|
||||
$table.on('click', '.btn-edit-serie', function () {
|
||||
const row = dt.row($(this).closest('tr')).data();
|
||||
openEditModal(row);
|
||||
});
|
||||
|
||||
// -----------------------------
|
||||
// Delete click
|
||||
// -----------------------------
|
||||
$table.on('click', '.btn-delete-serie', function () {
|
||||
const row = dt.row($(this).closest('tr')).data();
|
||||
|
||||
Swal.fire({
|
||||
title: window.languageBundle.get(['series-facturacion.delete.title']) || 'Eliminar serie',
|
||||
html: window.languageBundle.get(['series-facturacion.delete.text']) || 'Esta acción no se puede deshacer.',
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
buttonsStyling: false,
|
||||
customClass: {
|
||||
confirmButton: 'btn btn-danger w-xs mt-2',
|
||||
cancelButton: 'btn btn-light w-xs mt-2'
|
||||
},
|
||||
confirmButtonText: window.languageBundle.get(['app.eliminar']) || 'Eliminar',
|
||||
cancelButtonText: window.languageBundle.get(['app.cancelar']) || 'Cancelar',
|
||||
}).then((result) => {
|
||||
if (!result.isConfirmed) return;
|
||||
|
||||
$.ajax({
|
||||
url: `/configuracion/series-facturacion/api/${row.id}`,
|
||||
method: 'DELETE',
|
||||
success: function () {
|
||||
Swal.fire({
|
||||
icon: 'success', title: window.languageBundle.get(['series-facturacion.delete.ok.title']) || 'Eliminado',
|
||||
text: window.languageBundle.get(['series-facturacion.delete.ok.text']) || 'La serie de facturación ha sido eliminada correctamente.',
|
||||
showConfirmButton: false,
|
||||
timer: 1800,
|
||||
customClass: {
|
||||
confirmButton: 'btn btn-secondary w-xs mt-2',
|
||||
},
|
||||
});
|
||||
dt.ajax.reload(null, false);
|
||||
},
|
||||
error: function (xhr) {
|
||||
const msg = (xhr.responseJSON && xhr.responseJSON.message)
|
||||
|| 'Error al eliminar la serie de facturación.';
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'No se pudo eliminar',
|
||||
text: msg,
|
||||
buttonsStyling: false,
|
||||
customClass: {
|
||||
confirmButton: 'btn btn-secondary me-2', // clases para el botón confirmar
|
||||
cancelButton: 'btn btn-light' // clases para cancelar
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// -----------------------------
|
||||
// Save (create/update)
|
||||
// -----------------------------
|
||||
$saveBtn.on('click', function () {
|
||||
clearError();
|
||||
|
||||
// Validación Bootstrap
|
||||
const formEl = $form[0];
|
||||
if (!formEl.checkValidity()) {
|
||||
$form.addClass('was-validated');
|
||||
return;
|
||||
}
|
||||
|
||||
const id = $('#serie_id').val();
|
||||
const payload = {
|
||||
nombre_serie: $('#nombre_serie').val().trim(),
|
||||
prefijo: $('#prefijo').val().trim(),
|
||||
tipo: $('#tipo').val(),
|
||||
numero_actual: Number($('#numero_actual').val())
|
||||
};
|
||||
|
||||
const isEdit = !!id;
|
||||
const url = isEdit
|
||||
? `/configuracion/series-facturacion/api/${id}`
|
||||
: `/configuracion/series-facturacion/api`;
|
||||
|
||||
const method = isEdit ? 'PUT' : 'POST';
|
||||
|
||||
$saveBtn.prop('disabled', true);
|
||||
|
||||
$.ajax({
|
||||
url,
|
||||
method,
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
data: JSON.stringify(payload),
|
||||
success: function () {
|
||||
modal.hide();
|
||||
dt.ajax.reload(null, false);
|
||||
},
|
||||
error: function (xhr) {
|
||||
const msg = xhr.responseJSON?.message || xhr.responseText || 'No se pudo guardar.';
|
||||
showError(msg);
|
||||
},
|
||||
complete: function () {
|
||||
$saveBtn.prop('disabled', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// limpiar estado al cerrar
|
||||
$modal.on('hidden.bs.modal', () => resetForm());
|
||||
});
|
||||
@ -0,0 +1,119 @@
|
||||
<!doctype html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<body>
|
||||
|
||||
<!-- Fragment: Modal para Alta/Edición de Serie de Facturación -->
|
||||
<th:block th:fragment="modal">
|
||||
|
||||
<div class="modal fade" id="serieFacturacionModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="serieFacturacionModalTitle" th:text="#{series-facturacion.modal.title.add}">
|
||||
Añadir serie
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<!-- Body -->
|
||||
<div class="modal-body">
|
||||
|
||||
<!-- Alert placeholder (JS lo rellena) -->
|
||||
<div id="serieFacturacionAlert" class="alert alert-danger d-none" role="alert"></div>
|
||||
|
||||
<form id="serieFacturacionForm" novalidate>
|
||||
<!-- Para editar: el JS setea este id -->
|
||||
<input type="hidden" id="serie_id" name="id" value="">
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="nombre_serie" class="form-label" th:text="#{series-facturacion.form.nombre}">
|
||||
Nombre
|
||||
</label>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
id="nombre_serie"
|
||||
name="nombre_serie"
|
||||
maxlength="100"
|
||||
required>
|
||||
<div class="invalid-feedback" th:text="#{app.validation.required}">
|
||||
Campo obligatorio
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="prefijo" class="form-label" th:text="#{series-facturacion.form.prefijo}">
|
||||
Prefijo
|
||||
</label>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
id="prefijo"
|
||||
name="prefijo"
|
||||
maxlength="10"
|
||||
required>
|
||||
<div class="invalid-feedback" th:text="#{app.validation.required}">
|
||||
Campo obligatorio
|
||||
</div>
|
||||
<div class="form-text" th:text="#{series-facturacion.form.prefijo.help}">
|
||||
Ej: FAC, F25...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="tipo" class="form-label" th:text="#{series-facturacion.form.tipo}">
|
||||
Tipo
|
||||
</label>
|
||||
<!-- En BD solo hay facturacion, pero lo dejamos como select por UI -->
|
||||
<select class="form-select" id="tipo" name="tipo" required>
|
||||
<option value="facturacion" th:text="#{series-facturacion.tipo.facturacion}">
|
||||
Facturación
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="numero_actual" class="form-label" th:text="#{series-facturacion.form.numero-actual}">
|
||||
Número actual
|
||||
</label>
|
||||
<input type="number"
|
||||
class="form-control"
|
||||
id="numero_actual"
|
||||
name="numero_actual"
|
||||
min="1"
|
||||
step="1"
|
||||
value="1"
|
||||
required>
|
||||
<div class="invalid-feedback" th:text="#{app.validation.required}">
|
||||
Campo obligatorio
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="modal-footer">
|
||||
<button type="button"
|
||||
class="btn btn-light"
|
||||
data-bs-dismiss="modal"
|
||||
th:text="#{app.cancelar}">
|
||||
Cancelar
|
||||
</button>
|
||||
|
||||
<button type="button"
|
||||
class="btn btn-secondary"
|
||||
id="serieFacturacionSaveBtn">
|
||||
<i class="ri-save-line align-bottom me-1"></i>
|
||||
<span th:text="#{app.guardar}">Guardar</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</th:block>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,83 @@
|
||||
<!doctype html>
|
||||
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{imprimelibros/layout}">
|
||||
|
||||
<head>
|
||||
<th:block layout:fragment="pagetitle" />
|
||||
<th:block th:replace="~{imprimelibros/partials/head-css :: head-css}" />
|
||||
<th:block layout:fragment="pagecss">
|
||||
<link th:href="@{/assets/css/presupuestador.css}" rel="stylesheet"
|
||||
th:unless="${#authorization.expression('isAuthenticated()')}" />
|
||||
<link th:href="@{/assets/libs/datatables/dataTables.bootstrap5.min.css}" rel="stylesheet" />
|
||||
</th:block>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div th:replace="~{imprimelibros/partials/topbar :: topbar}" />
|
||||
<div th:replace="~{imprimelibros/partials/sidebar :: sidebar}" />
|
||||
|
||||
<th:block layout:fragment="content">
|
||||
<div th:if="${#authorization.expression('isAuthenticated()')}">
|
||||
|
||||
<!-- Modales-->
|
||||
<div th:replace="~{imprimelibros/configuracion/series-facturas/series-facturacion-modal :: modal}" />
|
||||
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/"><i class="ri-home-5-fill"></i></a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page" th:text="#{series-facturacion.breadcrumb}">
|
||||
Series de Facturación</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<div class="container-fluid">
|
||||
|
||||
<button type="button" class="btn btn-secondary mb-3" id="addButton">
|
||||
<i class="ri-add-line align-bottom me-1"></i> <span
|
||||
th:text="#{app.add}">Añadir</span>
|
||||
</button>
|
||||
|
||||
<table id="series-datatable" class="table table-striped table-nowrap responsive w-100">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-start" scope="col" th:text="#{series-facturacion.tabla.id}">ID</th>
|
||||
<th class="text-start" scope="col" th:text="#{series-facturacion.tabla.nombre}">Nombre</th>
|
||||
<th class="text-start" scope="col" th:text="#{series-facturacion.tabla.prefijo}">Prefijo</th>
|
||||
<th class="text-start" scope="col" th:text="#{series-facturacion.tabla.tipo}">Tipo</th>
|
||||
<th class="text-start" scope="col" th:text="#{series-facturacion.tabla.numero-actual}">Número Actual</th>
|
||||
<th class="text-start" scope="col" th:text="#{series-facturacion.tabla.acciones}">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</th:block>
|
||||
|
||||
<th:block layout:fragment="modal" />
|
||||
<th:block th:replace="~{theme/partials/vendor-scripts :: scripts}" />
|
||||
<th:block layout:fragment="pagejs">
|
||||
<script th:inline="javascript">
|
||||
window.languageBundle = /*[[${languageBundle}]]*/ {};
|
||||
</script>
|
||||
<script th:src="@{/assets/libs/datatables/datatables.min.js}"></script>
|
||||
<script th:src="@{/assets/libs/datatables/dataTables.bootstrap5.min.js}"></script>
|
||||
|
||||
<!-- JS de Buttons y dependencias -->
|
||||
<script th:src="@{/assets/libs/datatables/dataTables.buttons.min.js}"></script>
|
||||
<script th:src="@{/assets/libs/jszip/jszip.min.js}"></script>
|
||||
<script th:src="@{/assets/libs/pdfmake/pdfmake.min.js}"></script>
|
||||
<script th:src="@{/assets/libs/pdfmake/vfs_fonts.min.js}"></script>
|
||||
<script th:src="@{/assets/libs/datatables/buttons.html5.min.js}"></script>
|
||||
<script th:src="@{/assets/libs/datatables/buttons.print.min.js}"></script>
|
||||
<script th:src="@{/assets/libs/datatables/buttons.colVis.min.js}"></script>
|
||||
|
||||
<script type="module" th:src="@{/assets/js/pages/imprimelibros/configuracion/series-facturacion/list.js}"></script>
|
||||
|
||||
</th:block>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@ -88,6 +88,14 @@
|
||||
</a>
|
||||
</li>
|
||||
</div>
|
||||
<div th:if="${#authentication.principal.role == 'SUPERADMIN'}">
|
||||
<li class="nav-item">
|
||||
<a href="/configuracion/series-facturacion" class="nav-link">
|
||||
<i class="ri-file-list-3-line"></i>
|
||||
<span th:text="#{series-facturacion.title}">Series de facturación</span>
|
||||
</a>
|
||||
</li>
|
||||
</div>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user