mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-23 17:20:21 +00:00
terminado (provisional) modulo de facturas
This commit is contained in:
12906
logs/erp.log
12906
logs/erp.log
File diff suppressed because one or more lines are too long
@ -79,6 +79,9 @@ public class Factura extends AbstractAuditedEntitySoftTs {
|
|||||||
@OneToMany(mappedBy = "factura", cascade = CascadeType.ALL, orphanRemoval = true)
|
@OneToMany(mappedBy = "factura", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||||
private List<FacturaPago> pagos = new ArrayList<>();
|
private List<FacturaPago> pagos = new ArrayList<>();
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "factura", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||||
|
private List<FacturaDireccion> direcciones = new ArrayList<>();
|
||||||
|
|
||||||
@Formula("(select u.fullname from users u where u.id = cliente_id)")
|
@Formula("(select u.fullname from users u where u.id = cliente_id)")
|
||||||
private String clienteNombre;
|
private String clienteNombre;
|
||||||
|
|
||||||
@ -247,4 +250,22 @@ public class Factura extends AbstractAuditedEntitySoftTs {
|
|||||||
public void setPagos(List<FacturaPago> pagos) {
|
public void setPagos(List<FacturaPago> pagos) {
|
||||||
this.pagos = pagos;
|
this.pagos = pagos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<FacturaDireccion> getDirecciones() {
|
||||||
|
return direcciones;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDirecciones(List<FacturaDireccion> direcciones) {
|
||||||
|
this.direcciones = direcciones;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FacturaDireccion getDireccionFacturacion() {
|
||||||
|
return (direcciones == null || direcciones.isEmpty()) ? null : direcciones.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDireccion(FacturaDireccion direccion) {
|
||||||
|
direccion.setFactura(this);
|
||||||
|
this.direcciones.add(direccion);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,209 @@
|
|||||||
|
package com.imprimelibros.erp.facturacion;
|
||||||
|
|
||||||
|
import com.imprimelibros.erp.direcciones.Direccion.TipoIdentificacionFiscal;
|
||||||
|
import com.imprimelibros.erp.paises.Paises;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "facturas_direcciones",
|
||||||
|
indexes = {
|
||||||
|
@Index(name = "idx_facturas_direcciones_factura_id", columnList = "factura_id")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public class FacturaDireccion {
|
||||||
|
|
||||||
|
@Column(name = "id")
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
||||||
|
@JoinColumn(name = "factura_id", nullable = false,
|
||||||
|
foreignKey = @ForeignKey(name = "fk_facturas_direcciones_factura"))
|
||||||
|
private Factura factura;
|
||||||
|
|
||||||
|
@Column(name = "unidades")
|
||||||
|
private Integer unidades; // MEDIUMINT UNSIGNED
|
||||||
|
|
||||||
|
@Column(name = "email", length = 255)
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@Column(name = "att", length = 150, nullable = false)
|
||||||
|
private String att;
|
||||||
|
|
||||||
|
@Column(name = "direccion", length = 255, nullable = false)
|
||||||
|
private String direccion;
|
||||||
|
|
||||||
|
@Column(name = "cp", nullable = false)
|
||||||
|
private Integer cp; // MEDIUMINT UNSIGNED
|
||||||
|
|
||||||
|
@Column(name = "ciudad", length = 100, nullable = false)
|
||||||
|
private String ciudad;
|
||||||
|
|
||||||
|
@Column(name = "provincia", length = 100, nullable = false)
|
||||||
|
private String provincia;
|
||||||
|
|
||||||
|
@Column(name = "pais_code3", length = 3, nullable = false)
|
||||||
|
private String paisCode3 = "esp";
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "pais_code3", referencedColumnName = "code3", insertable = false, updatable = false)
|
||||||
|
private Paises pais;
|
||||||
|
|
||||||
|
@Column(name = "telefono", length = 30)
|
||||||
|
private String telefono;
|
||||||
|
|
||||||
|
@Column(name = "instrucciones", length = 255)
|
||||||
|
private String instrucciones;
|
||||||
|
|
||||||
|
@Column(name = "razon_social", length = 150)
|
||||||
|
private String razonSocial;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(name = "tipo_identificacion_fiscal", length = 20, nullable = false)
|
||||||
|
private TipoIdentificacionFiscal tipoIdentificacionFiscal = TipoIdentificacionFiscal.DNI;
|
||||||
|
|
||||||
|
@Column(name = "identificacion_fiscal", length = 50)
|
||||||
|
private String identificacionFiscal;
|
||||||
|
|
||||||
|
@Column(name = "created_at", nullable = false, updatable = false)
|
||||||
|
private java.time.Instant createdAt;
|
||||||
|
|
||||||
|
|
||||||
|
// Getters / Setters
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Factura getFactura() {
|
||||||
|
return factura;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFactura(Factura factura) {
|
||||||
|
this.factura = factura;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getUnidades() {
|
||||||
|
return unidades;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUnidades(Integer unidades) {
|
||||||
|
this.unidades = unidades;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAtt() {
|
||||||
|
return att;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAtt(String att) {
|
||||||
|
this.att = att;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDireccion() {
|
||||||
|
return direccion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDireccion(String direccion) {
|
||||||
|
this.direccion = direccion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getCp() {
|
||||||
|
return cp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCp(Integer cp) {
|
||||||
|
this.cp = cp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCiudad() {
|
||||||
|
return ciudad;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCiudad(String ciudad) {
|
||||||
|
this.ciudad = ciudad;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProvincia() {
|
||||||
|
return provincia;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProvincia(String provincia) {
|
||||||
|
this.provincia = provincia;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPaisCode3() {
|
||||||
|
return paisCode3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPaisCode3(String paisCode3) {
|
||||||
|
this.paisCode3 = paisCode3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Paises getPais() {
|
||||||
|
return pais;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPais(Paises pais) {
|
||||||
|
this.pais = pais;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTelefono() {
|
||||||
|
return telefono;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTelefono(String telefono) {
|
||||||
|
this.telefono = telefono;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getInstrucciones() {
|
||||||
|
return instrucciones;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInstrucciones(String instrucciones) {
|
||||||
|
this.instrucciones = instrucciones;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRazonSocial() {
|
||||||
|
return razonSocial;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRazonSocial(String razonSocial) {
|
||||||
|
this.razonSocial = razonSocial;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TipoIdentificacionFiscal getTipoIdentificacionFiscal() {
|
||||||
|
return tipoIdentificacionFiscal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTipoIdentificacionFiscal(TipoIdentificacionFiscal tipoIdentificacionFiscal) {
|
||||||
|
this.tipoIdentificacionFiscal = tipoIdentificacionFiscal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIdentificacionFiscal() {
|
||||||
|
return identificacionFiscal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIdentificacionFiscal(String identificacionFiscal) {
|
||||||
|
this.identificacionFiscal = identificacionFiscal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public java.time.Instant getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
public void setCreatedAt(java.time.Instant createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,11 +1,15 @@
|
|||||||
package com.imprimelibros.erp.facturacion.controller;
|
package com.imprimelibros.erp.facturacion.controller;
|
||||||
|
|
||||||
|
import com.imprimelibros.erp.configurationERP.VariableService;
|
||||||
import com.imprimelibros.erp.datatables.DataTable;
|
import com.imprimelibros.erp.datatables.DataTable;
|
||||||
import com.imprimelibros.erp.datatables.DataTablesParser;
|
import com.imprimelibros.erp.datatables.DataTablesParser;
|
||||||
import com.imprimelibros.erp.datatables.DataTablesRequest;
|
import com.imprimelibros.erp.datatables.DataTablesRequest;
|
||||||
import com.imprimelibros.erp.datatables.DataTablesResponse;
|
import com.imprimelibros.erp.datatables.DataTablesResponse;
|
||||||
|
import com.imprimelibros.erp.direcciones.DireccionService;
|
||||||
import com.imprimelibros.erp.facturacion.EstadoFactura;
|
import com.imprimelibros.erp.facturacion.EstadoFactura;
|
||||||
import com.imprimelibros.erp.facturacion.Factura;
|
import com.imprimelibros.erp.facturacion.Factura;
|
||||||
|
import com.imprimelibros.erp.facturacion.FacturaDireccion;
|
||||||
|
import com.imprimelibros.erp.facturacion.dto.FacturaAddRequestDto;
|
||||||
import com.imprimelibros.erp.facturacion.dto.FacturaGuardarDto;
|
import com.imprimelibros.erp.facturacion.dto.FacturaGuardarDto;
|
||||||
import com.imprimelibros.erp.facturacion.dto.FacturaLineaUpsertDto;
|
import com.imprimelibros.erp.facturacion.dto.FacturaLineaUpsertDto;
|
||||||
import com.imprimelibros.erp.facturacion.dto.FacturaPagoUpsertDto;
|
import com.imprimelibros.erp.facturacion.dto.FacturaPagoUpsertDto;
|
||||||
@ -23,6 +27,7 @@ import org.springframework.context.MessageSource;
|
|||||||
import org.springframework.data.jpa.domain.Specification;
|
import org.springframework.data.jpa.domain.Specification;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
@ -30,6 +35,7 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -45,17 +51,21 @@ public class FacturasController {
|
|||||||
private final TranslationService translationService;
|
private final TranslationService translationService;
|
||||||
private final MessageSource messageSource;
|
private final MessageSource messageSource;
|
||||||
private final PedidoService pedidoService;
|
private final PedidoService pedidoService;
|
||||||
|
private final VariableService variableService;
|
||||||
|
private final DireccionService direccionService;
|
||||||
|
|
||||||
public FacturasController(
|
public FacturasController(
|
||||||
FacturaRepository repo,
|
FacturaRepository repo,
|
||||||
TranslationService translationService,
|
TranslationService translationService,
|
||||||
MessageSource messageSource,
|
MessageSource messageSource,
|
||||||
PedidoService pedidoService, FacturacionService facturacionService) {
|
PedidoService pedidoService, FacturacionService facturacionService, VariableService variableService, DireccionService direccionService) {
|
||||||
this.repo = repo;
|
this.repo = repo;
|
||||||
this.translationService = translationService;
|
this.translationService = translationService;
|
||||||
this.messageSource = messageSource;
|
this.messageSource = messageSource;
|
||||||
this.pedidoService = pedidoService;
|
this.pedidoService = pedidoService;
|
||||||
this.facturacionService = facturacionService;
|
this.facturacionService = facturacionService;
|
||||||
|
this.direccionService = direccionService;
|
||||||
|
this.variableService = variableService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
@ -73,14 +83,56 @@ public class FacturasController {
|
|||||||
return "imprimelibros/facturas/facturas-list";
|
return "imprimelibros/facturas/facturas-list";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/add")
|
||||||
|
public String facturaAdd(Model model, Locale locale) {
|
||||||
|
|
||||||
|
List<String> keys = List.of(
|
||||||
|
"facturas.form.cliente.placeholder",
|
||||||
|
"facturas.add.form.validation.title",
|
||||||
|
"facturas.add.form.validation",
|
||||||
|
"facturas.error.create"
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, String> translations = translationService.getTranslations(locale, keys);
|
||||||
|
model.addAttribute("languageBundle", translations);
|
||||||
|
|
||||||
|
model.addAttribute("defaultSerieRectificativa", variableService.getValorEntero("serie_facturacion_rect_default"));
|
||||||
|
|
||||||
|
return "imprimelibros/facturas/facturas-add-form";
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/add")
|
||||||
|
@ResponseBody
|
||||||
|
public Map<String, Object> facturaAddPost(
|
||||||
|
Model model,
|
||||||
|
@RequestBody FacturaAddRequestDto request,
|
||||||
|
Locale locale) {
|
||||||
|
|
||||||
|
Factura nuevaFactura = facturacionService.crearNuevaFactura(
|
||||||
|
request.getUser(),
|
||||||
|
request.getSerie(),
|
||||||
|
request.getDireccion(),
|
||||||
|
request.getFactura_rectificada()
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
if(nuevaFactura == null){
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("message", messageSource.getMessage("facturas.error.create", null, "No se ha podido crear la factura. Revise los datos e inténtelo de nuevo.", locale));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
result.put("success", true);
|
||||||
|
result.put("facturaId", nuevaFactura.getId());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
public String facturaDetail(@PathVariable Long id, Model model, Locale locale) {
|
public String facturaDetail(@PathVariable Long id, Model model, Locale locale) {
|
||||||
Factura factura = repo.findById(id)
|
Factura factura = repo.findById(id)
|
||||||
.orElseThrow(() -> new EntityNotFoundException("Factura no encontrada con ID: " + id));
|
.orElseThrow(() -> new EntityNotFoundException("Factura no encontrada con ID: " + id));
|
||||||
|
|
||||||
PedidoDireccion direccionFacturacion = pedidoService
|
|
||||||
.getPedidoDireccionFacturacionByPedidoId(factura.getPedidoId());
|
|
||||||
|
|
||||||
List<String> keys = List.of(
|
List<String> keys = List.of(
|
||||||
"facturas.lineas.error.base",
|
"facturas.lineas.error.base",
|
||||||
"facturas.lineas.delete.title",
|
"facturas.lineas.delete.title",
|
||||||
@ -97,6 +149,8 @@ public class FacturasController {
|
|||||||
Map<String, String> translations = translationService.getTranslations(locale, keys);
|
Map<String, String> translations = translationService.getTranslations(locale, keys);
|
||||||
model.addAttribute("languageBundle", translations);
|
model.addAttribute("languageBundle", translations);
|
||||||
|
|
||||||
|
FacturaDireccion direccionFacturacion = factura.getDireccionFacturacion();
|
||||||
|
|
||||||
model.addAttribute("direccionFacturacion", direccionFacturacion);
|
model.addAttribute("direccionFacturacion", direccionFacturacion);
|
||||||
model.addAttribute("factura", factura);
|
model.addAttribute("factura", factura);
|
||||||
|
|
||||||
@ -116,8 +170,8 @@ public class FacturasController {
|
|||||||
Factura factura = repo.findById(id)
|
Factura factura = repo.findById(id)
|
||||||
.orElseThrow(() -> new EntityNotFoundException("Factura no encontrada con ID: " + id));
|
.orElseThrow(() -> new EntityNotFoundException("Factura no encontrada con ID: " + id));
|
||||||
|
|
||||||
PedidoDireccion direccionFacturacion = pedidoService
|
FacturaDireccion direccionFacturacion = factura.getDireccionFacturacion();
|
||||||
.getPedidoDireccionFacturacionByPedidoId(factura.getPedidoId());
|
|
||||||
|
|
||||||
model.addAttribute("direccionFacturacion", direccionFacturacion);
|
model.addAttribute("direccionFacturacion", direccionFacturacion);
|
||||||
model.addAttribute("factura", factura);
|
model.addAttribute("factura", factura);
|
||||||
@ -134,7 +188,7 @@ public class FacturasController {
|
|||||||
return ResponseEntity.badRequest().body("Solo se pueden validar facturas en estado 'borrador'.");
|
return ResponseEntity.badRequest().body("Solo se pueden validar facturas en estado 'borrador'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
factura.setEstado(EstadoFactura.validada);
|
facturacionService.validarFactura(factura.getId());
|
||||||
repo.save(factura);
|
repo.save(factura);
|
||||||
|
|
||||||
return ResponseEntity.ok().build();
|
return ResponseEntity.ok().build();
|
||||||
@ -178,6 +232,8 @@ public class FacturasController {
|
|||||||
return ResponseEntity.ok(Map.of("ok", true));
|
return ResponseEntity.ok(Map.of("ok", true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* -----------------------------
|
* -----------------------------
|
||||||
* Pagos
|
* Pagos
|
||||||
@ -296,4 +352,39 @@ public class FacturasController {
|
|||||||
.toJson(total);
|
.toJson(total);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -----------------------------
|
||||||
|
// API: select2 Direcciones
|
||||||
|
// -----------------------------
|
||||||
|
@GetMapping("/api/get-direcciones")
|
||||||
|
@ResponseBody
|
||||||
|
public Map<String, Object> getSelect2Facturacion(
|
||||||
|
@RequestParam(value = "q", required = false) String q1,
|
||||||
|
@RequestParam(value = "term", required = false) String q2,
|
||||||
|
@RequestParam(value = "user_id", required = true) Long userId,
|
||||||
|
Authentication auth) {
|
||||||
|
|
||||||
|
|
||||||
|
return direccionService.getForSelectFacturacion(q1, q2, userId);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------
|
||||||
|
// API: select2 facturas rectificables
|
||||||
|
// -----------------------------
|
||||||
|
@GetMapping("/api/get-facturas-rectificables")
|
||||||
|
@ResponseBody
|
||||||
|
public Map<String, Object> getSelect2FacturasRectificables(
|
||||||
|
@RequestParam(value = "q", required = false) String q1,
|
||||||
|
@RequestParam(value = "term", required = false) String q2,
|
||||||
|
@RequestParam(value = "user_id", required = true) Long userId,
|
||||||
|
Authentication auth) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return Map.of("results", List.of());
|
||||||
|
}
|
||||||
|
return facturacionService.getForSelectFacturasRectificables(q1, q2, userId);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
package com.imprimelibros.erp.facturacion.dto;
|
package com.imprimelibros.erp.facturacion.dto;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
import com.imprimelibros.erp.facturacion.FacturaDireccion;
|
||||||
import com.imprimelibros.erp.pedidos.PedidoDireccion;
|
import com.imprimelibros.erp.pedidos.PedidoDireccion;
|
||||||
|
|
||||||
public class DireccionFacturacionDto {
|
public class DireccionFacturacionDto {
|
||||||
@ -76,6 +79,13 @@ public class DireccionFacturacionDto {
|
|||||||
this.telefono = telefono;
|
this.telefono = telefono;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public FacturaDireccion toFacturaDireccion() {
|
||||||
|
FacturaDireccion fd = new FacturaDireccion();
|
||||||
|
applyTo(fd);
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
public PedidoDireccion toPedidoDireccion() {
|
public PedidoDireccion toPedidoDireccion() {
|
||||||
PedidoDireccion pd = new PedidoDireccion();
|
PedidoDireccion pd = new PedidoDireccion();
|
||||||
applyTo(pd);
|
applyTo(pd);
|
||||||
@ -84,6 +94,7 @@ public class DireccionFacturacionDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void applyTo(PedidoDireccion pd) {
|
public void applyTo(PedidoDireccion pd) {
|
||||||
|
pd.setAtt("");
|
||||||
pd.setRazonSocial(this.razonSocial);
|
pd.setRazonSocial(this.razonSocial);
|
||||||
pd.setIdentificacionFiscal(this.identificacionFiscal);
|
pd.setIdentificacionFiscal(this.identificacionFiscal);
|
||||||
pd.setDireccion(this.direccion);
|
pd.setDireccion(this.direccion);
|
||||||
@ -107,4 +118,30 @@ public class DireccionFacturacionDto {
|
|||||||
pd.setTelefono(this.telefono);
|
pd.setTelefono(this.telefono);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void applyTo(FacturaDireccion fd ) {
|
||||||
|
fd.setAtt("");
|
||||||
|
fd.setRazonSocial(this.razonSocial);
|
||||||
|
fd.setIdentificacionFiscal(this.identificacionFiscal);
|
||||||
|
fd.setDireccion(this.direccion);
|
||||||
|
|
||||||
|
// CP robusto
|
||||||
|
Integer cpInt = null;
|
||||||
|
if (this.cp != null && !this.cp.isBlank()) {
|
||||||
|
try {
|
||||||
|
cpInt = Integer.valueOf(this.cp.trim());
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
// si quieres, lanza IllegalArgumentException para validarlo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fd.setCp(cpInt);
|
||||||
|
|
||||||
|
fd.setCiudad(this.ciudad);
|
||||||
|
fd.setProvincia(this.provincia);
|
||||||
|
|
||||||
|
fd.setPaisCode3(this.paisKeyword);
|
||||||
|
|
||||||
|
fd.setTelefono(this.telefono);
|
||||||
|
fd.setCreatedAt(Instant.now());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,36 @@
|
|||||||
|
package com.imprimelibros.erp.facturacion.dto;
|
||||||
|
|
||||||
|
public class FacturaAddRequestDto {
|
||||||
|
|
||||||
|
private Long user;
|
||||||
|
private Long serie;
|
||||||
|
private Long direccion;
|
||||||
|
private Long factura_rectificada;
|
||||||
|
|
||||||
|
// getters y setters
|
||||||
|
public Long getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
public void setUser(Long user) {
|
||||||
|
this.user = user;
|
||||||
|
}
|
||||||
|
public Long getSerie() {
|
||||||
|
return serie;
|
||||||
|
}
|
||||||
|
public void setSerie(Long serie) {
|
||||||
|
this.serie = serie;
|
||||||
|
}
|
||||||
|
public Long getDireccion() {
|
||||||
|
return direccion;
|
||||||
|
}
|
||||||
|
public void setDireccion(Long direccion) {
|
||||||
|
this.direccion = direccion;
|
||||||
|
}
|
||||||
|
public Long getFactura_rectificada() {
|
||||||
|
return factura_rectificada;
|
||||||
|
}
|
||||||
|
public void setFactura_rectificada(Long factura_rectificada) {
|
||||||
|
this.factura_rectificada = factura_rectificada;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,67 @@
|
|||||||
|
package com.imprimelibros.erp.facturacion.dto;
|
||||||
|
|
||||||
|
import com.imprimelibros.erp.pedidos.PedidoDireccion;
|
||||||
|
import com.imprimelibros.erp.facturacion.FacturaDireccion;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
import com.imprimelibros.erp.direcciones.Direccion.TipoIdentificacionFiscal;
|
||||||
|
|
||||||
|
public final class FacturaDireccionMapper {
|
||||||
|
|
||||||
|
private FacturaDireccionMapper() {}
|
||||||
|
|
||||||
|
public static FacturaDireccion fromPedidoDireccion(PedidoDireccion src) {
|
||||||
|
if (src == null) return null;
|
||||||
|
|
||||||
|
FacturaDireccion dst = new FacturaDireccion();
|
||||||
|
|
||||||
|
dst.setUnidades(src.getUnidades());
|
||||||
|
dst.setEmail(src.getEmail());
|
||||||
|
dst.setAtt(src.getAtt());
|
||||||
|
dst.setDireccion(src.getDireccion());
|
||||||
|
dst.setCp(src.getCp());
|
||||||
|
dst.setCiudad(src.getCiudad());
|
||||||
|
dst.setProvincia(src.getProvincia());
|
||||||
|
dst.setPaisCode3(src.getPaisCode3());
|
||||||
|
dst.setTelefono(src.getTelefono());
|
||||||
|
dst.setInstrucciones(src.getInstrucciones());
|
||||||
|
dst.setRazonSocial(src.getRazonSocial());
|
||||||
|
dst.setCreatedAt(Instant.now());
|
||||||
|
|
||||||
|
// OJO: en PedidoDireccion usas Direccion.TipoIdentificacionFiscal
|
||||||
|
// En FacturaDireccion usa el enum que hayas definido/importado.
|
||||||
|
dst.setTipoIdentificacionFiscal(
|
||||||
|
TipoIdentificacionFiscal.valueOf(src.getTipoIdentificacionFiscal().name())
|
||||||
|
);
|
||||||
|
|
||||||
|
dst.setIdentificacionFiscal(src.getIdentificacionFiscal());
|
||||||
|
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FacturaDireccion fromDireccion(com.imprimelibros.erp.direcciones.Direccion src) {
|
||||||
|
if (src == null) return null;
|
||||||
|
|
||||||
|
FacturaDireccion dst = new FacturaDireccion();
|
||||||
|
|
||||||
|
dst.setUnidades(null);
|
||||||
|
dst.setEmail(src.getUser().getUserName());
|
||||||
|
dst.setAtt(src.getAtt());
|
||||||
|
dst.setDireccion(src.getDireccion());
|
||||||
|
dst.setCp(src.getCp());
|
||||||
|
dst.setCiudad(src.getCiudad());
|
||||||
|
dst.setProvincia(src.getProvincia());
|
||||||
|
dst.setPaisCode3(src.getPais().getCode3());
|
||||||
|
dst.setTelefono(src.getTelefono());
|
||||||
|
dst.setInstrucciones(src.getInstrucciones());
|
||||||
|
dst.setRazonSocial(src.getRazonSocial());
|
||||||
|
dst.setCreatedAt(Instant.now());
|
||||||
|
|
||||||
|
dst.setTipoIdentificacionFiscal(src.getTipoIdentificacionFiscal());
|
||||||
|
|
||||||
|
dst.setIdentificacionFiscal(src.getIdentificacionFiscal());
|
||||||
|
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
package com.imprimelibros.erp.facturacion.repo;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import com.imprimelibros.erp.facturacion.FacturaDireccion;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface FacturaDireccionRepository extends JpaRepository<FacturaDireccion, Long> {
|
||||||
|
|
||||||
|
List<FacturaDireccion> findByFacturaId(Long facturaId);
|
||||||
|
|
||||||
|
Optional<FacturaDireccion> findFirstByFacturaIdOrderByIdAsc(Long facturaId);
|
||||||
|
}
|
||||||
@ -1,11 +1,21 @@
|
|||||||
package com.imprimelibros.erp.facturacion.repo;
|
package com.imprimelibros.erp.facturacion.repo;
|
||||||
|
|
||||||
|
import com.imprimelibros.erp.facturacion.EstadoFactura;
|
||||||
|
import com.imprimelibros.erp.facturacion.EstadoPagoFactura;
|
||||||
import com.imprimelibros.erp.facturacion.Factura;
|
import com.imprimelibros.erp.facturacion.Factura;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public interface FacturaRepository extends JpaRepository<Factura, Long>, JpaSpecificationExecutor<Factura> {
|
public interface FacturaRepository extends JpaRepository<Factura, Long>, JpaSpecificationExecutor<Factura> {
|
||||||
Optional<Factura> findByNumeroFactura(String numeroFactura);
|
Optional<Factura> findByNumeroFactura(String numeroFactura);
|
||||||
|
Factura findByPedidoId(Long pedidoId);
|
||||||
|
List<Factura> findByClienteIdAndEstadoAndEstadoPagoAndSerieId(
|
||||||
|
Long clienteId,
|
||||||
|
EstadoFactura estado,
|
||||||
|
EstadoPagoFactura estadoPago,
|
||||||
|
Long serieId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,20 +3,26 @@ package com.imprimelibros.erp.facturacion.service;
|
|||||||
import com.imprimelibros.erp.common.Utils;
|
import com.imprimelibros.erp.common.Utils;
|
||||||
import com.imprimelibros.erp.configurationERP.VariableService;
|
import com.imprimelibros.erp.configurationERP.VariableService;
|
||||||
import com.imprimelibros.erp.facturacion.*;
|
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.FacturaGuardarDto;
|
||||||
import com.imprimelibros.erp.facturacion.dto.FacturaLineaUpsertDto;
|
import com.imprimelibros.erp.facturacion.dto.FacturaLineaUpsertDto;
|
||||||
import com.imprimelibros.erp.facturacion.dto.FacturaPagoUpsertDto;
|
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.FacturaLineaRepository;
|
||||||
import com.imprimelibros.erp.facturacion.repo.FacturaPagoRepository;
|
import com.imprimelibros.erp.facturacion.repo.FacturaPagoRepository;
|
||||||
import com.imprimelibros.erp.facturacion.repo.FacturaRepository;
|
import com.imprimelibros.erp.facturacion.repo.FacturaRepository;
|
||||||
import com.imprimelibros.erp.facturacion.repo.SerieFacturaRepository;
|
import com.imprimelibros.erp.facturacion.repo.SerieFacturaRepository;
|
||||||
import com.imprimelibros.erp.pedidos.Pedido;
|
import com.imprimelibros.erp.pedidos.Pedido;
|
||||||
|
import com.imprimelibros.erp.pedidos.PedidoDireccion;
|
||||||
import com.imprimelibros.erp.pedidos.PedidoLinea;
|
import com.imprimelibros.erp.pedidos.PedidoLinea;
|
||||||
import com.imprimelibros.erp.pedidos.PedidoLineaRepository;
|
import com.imprimelibros.erp.pedidos.PedidoLineaRepository;
|
||||||
import com.imprimelibros.erp.pedidos.PedidoService;
|
import com.imprimelibros.erp.pedidos.PedidoService;
|
||||||
import com.imprimelibros.erp.presupuesto.dto.Presupuesto;
|
import com.imprimelibros.erp.presupuesto.dto.Presupuesto;
|
||||||
import com.imprimelibros.erp.users.User;
|
import com.imprimelibros.erp.users.User;
|
||||||
import com.imprimelibros.erp.users.UserService;
|
import com.imprimelibros.erp.users.UserService;
|
||||||
|
import com.imprimelibros.erp.direcciones.Direccion;
|
||||||
|
import com.imprimelibros.erp.direcciones.DireccionRepository;
|
||||||
|
|
||||||
import jakarta.persistence.EntityNotFoundException;
|
import jakarta.persistence.EntityNotFoundException;
|
||||||
|
|
||||||
@ -25,12 +31,17 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
|
import java.text.Collator;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@ -42,6 +53,7 @@ public class FacturacionService {
|
|||||||
private final SerieFacturaRepository serieRepo;
|
private final SerieFacturaRepository serieRepo;
|
||||||
private final FacturaPagoRepository pagoRepo;
|
private final FacturaPagoRepository pagoRepo;
|
||||||
private final FacturaLineaRepository lineaFacturaRepository;
|
private final FacturaLineaRepository lineaFacturaRepository;
|
||||||
|
private final DireccionRepository direccionRepo;
|
||||||
private final PedidoLineaRepository pedidoLineaRepo;
|
private final PedidoLineaRepository pedidoLineaRepo;
|
||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
private final Utils utils;
|
private final Utils utils;
|
||||||
@ -54,6 +66,7 @@ public class FacturacionService {
|
|||||||
FacturaLineaRepository lineaFacturaRepository,
|
FacturaLineaRepository lineaFacturaRepository,
|
||||||
SerieFacturaRepository serieRepo,
|
SerieFacturaRepository serieRepo,
|
||||||
FacturaPagoRepository pagoRepo,
|
FacturaPagoRepository pagoRepo,
|
||||||
|
DireccionRepository direccionRepo,
|
||||||
PedidoLineaRepository pedidoLineaRepo,
|
PedidoLineaRepository pedidoLineaRepo,
|
||||||
UserService userService,
|
UserService userService,
|
||||||
Utils utils,
|
Utils utils,
|
||||||
@ -64,6 +77,7 @@ public class FacturacionService {
|
|||||||
this.lineaFacturaRepository = lineaFacturaRepository;
|
this.lineaFacturaRepository = lineaFacturaRepository;
|
||||||
this.serieRepo = serieRepo;
|
this.serieRepo = serieRepo;
|
||||||
this.pagoRepo = pagoRepo;
|
this.pagoRepo = pagoRepo;
|
||||||
|
this.direccionRepo = direccionRepo;
|
||||||
this.pedidoLineaRepo = pedidoLineaRepo;
|
this.pedidoLineaRepo = pedidoLineaRepo;
|
||||||
this.userService = userService;
|
this.userService = userService;
|
||||||
this.utils = utils;
|
this.utils = utils;
|
||||||
@ -87,6 +101,14 @@ public class FacturacionService {
|
|||||||
.orElseThrow(() -> new EntityNotFoundException("Factura no encontrada: " + 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
|
// Nueva factura
|
||||||
// -----------------------
|
// -----------------------
|
||||||
@ -147,6 +169,13 @@ public class FacturacionService {
|
|||||||
lineaEnvio.setFactura(factura);
|
lineaEnvio.setFactura(factura);
|
||||||
lineasFactura.add(lineaEnvio);
|
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.setLineas(lineasFactura);
|
||||||
|
|
||||||
factura = facturaRepo.save(factura);
|
factura = facturaRepo.save(factura);
|
||||||
@ -166,6 +195,46 @@ public class FacturacionService {
|
|||||||
return factura;
|
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
|
// Estado / Numeración
|
||||||
// -----------------------
|
// -----------------------
|
||||||
@ -225,6 +294,7 @@ public class FacturacionService {
|
|||||||
pedidoService.upsertDireccionFacturacion(pedidoId, dto.getDireccionFacturacion());
|
pedidoService.upsertDireccionFacturacion(pedidoId, dto.getDireccionFacturacion());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
upsertDireccionFacturacion(facturaId, dto.getDireccionFacturacion());
|
||||||
|
|
||||||
facturaRepo.save(factura);
|
facturaRepo.save(factura);
|
||||||
}
|
}
|
||||||
@ -281,12 +351,79 @@ public class FacturacionService {
|
|||||||
return facturaRepo.save(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) {
|
private String buildNumeroFactura(String prefijo, long numero) {
|
||||||
String pref = (prefijo == null) ? "" : prefijo.trim();
|
String pref = (prefijo == null) ? "" : prefijo.trim();
|
||||||
String num = String.format("%05d", numero);
|
String num = String.format("%05d", numero);
|
||||||
return pref.isBlank() ? num : (pref + " " + num + "/" + LocalDate.now().getYear());
|
return pref.isBlank() ? num : (pref + " " + num + "/" + LocalDate.now().getYear());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// -----------------------
|
// -----------------------
|
||||||
// Líneas
|
// Líneas
|
||||||
// -----------------------
|
// -----------------------
|
||||||
|
|||||||
@ -0,0 +1,55 @@
|
|||||||
|
package com.imprimelibros.erp.pedidos;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class PedidoEstadoService {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(PedidoEstadoService.class);
|
||||||
|
|
||||||
|
private final PedidoLineaRepository pedidoLineaRepository;
|
||||||
|
private final PedidoService pedidoService;
|
||||||
|
|
||||||
|
public PedidoEstadoService(PedidoLineaRepository pedidoLineaRepository, PedidoService pedidoService) {
|
||||||
|
this.pedidoLineaRepository = pedidoLineaRepository;
|
||||||
|
this.pedidoService = pedidoService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ejecuta cada noche a las 4:00 AM
|
||||||
|
*/
|
||||||
|
@Scheduled(cron = "0 0 4 * * *")
|
||||||
|
public void actualizarEstadosPedidos() {
|
||||||
|
|
||||||
|
List<PedidoLinea> pedidosLineas = pedidoLineaRepository.findPedidosLineasParaActualizarEstado();
|
||||||
|
|
||||||
|
for (PedidoLinea linea : pedidosLineas) {
|
||||||
|
try {
|
||||||
|
Map<String, Object> resultado = pedidoService.actualizarEstado(linea.getId(), Locale.getDefault());
|
||||||
|
|
||||||
|
if (!Boolean.TRUE.equals(resultado.get("success"))) {
|
||||||
|
log.error("Error al actualizar estado. pedidoLineaId={} message={}",
|
||||||
|
linea.getId(), resultado.get("message"));
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.error("Excepción actualizando estado. pedidoLineaId={}", linea.getId(), ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rate limit / delay
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
log.error("Job interrumpido mientras dormía (rate limit).");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
package com.imprimelibros.erp.pedidos;
|
package com.imprimelibros.erp.pedidos;
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -9,7 +10,25 @@ import java.util.List;
|
|||||||
public interface PedidoLineaRepository extends JpaRepository<PedidoLinea, Long> {
|
public interface PedidoLineaRepository extends JpaRepository<PedidoLinea, Long> {
|
||||||
|
|
||||||
List<PedidoLinea> findByPedidoId(Long pedidoId);
|
List<PedidoLinea> findByPedidoId(Long pedidoId);
|
||||||
|
|
||||||
List<PedidoLinea> findByPedidoIdOrderByIdAsc(Long pedidoId);
|
List<PedidoLinea> findByPedidoIdOrderByIdAsc(Long pedidoId);
|
||||||
|
|
||||||
List<PedidoLinea> findByPresupuestoId(Long presupuestoId);
|
List<PedidoLinea> findByPresupuestoId(Long presupuestoId);
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
SELECT pl
|
||||||
|
FROM PedidoLinea pl
|
||||||
|
JOIN pl.presupuesto p
|
||||||
|
WHERE pl.estadoManual = false
|
||||||
|
AND pl.estado IN (
|
||||||
|
'haciendo_ferro',
|
||||||
|
'esperando_aceptacion_ferro',
|
||||||
|
'produccion',
|
||||||
|
'terminado'
|
||||||
|
)
|
||||||
|
AND p.proveedor = 'Safekat'
|
||||||
|
AND p.proveedorRef1 IS NOT NULL
|
||||||
|
AND p.proveedorRef2 IS NOT NULL
|
||||||
|
""")
|
||||||
|
List<PedidoLinea> findPedidosLineasParaActualizarEstado();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import com.imprimelibros.erp.presupuesto.service.PresupuestoService;
|
|||||||
import com.imprimelibros.erp.users.UserService;
|
import com.imprimelibros.erp.users.UserService;
|
||||||
import com.imprimelibros.erp.direcciones.DireccionService;
|
import com.imprimelibros.erp.direcciones.DireccionService;
|
||||||
import com.imprimelibros.erp.externalApi.skApiClient;
|
import com.imprimelibros.erp.externalApi.skApiClient;
|
||||||
|
import com.imprimelibros.erp.facturacion.FacturaDireccion;
|
||||||
import com.imprimelibros.erp.facturacion.dto.DireccionFacturacionDto;
|
import com.imprimelibros.erp.facturacion.dto.DireccionFacturacionDto;
|
||||||
import com.imprimelibros.erp.pedidos.PedidoLinea.Estado;
|
import com.imprimelibros.erp.pedidos.PedidoLinea.Estado;
|
||||||
|
|
||||||
@ -191,24 +192,24 @@ public class PedidoService {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
Pedido pedido = pedidoRepository.findById(pedidoId).orElse(null);
|
Pedido pedido = pedidoRepository.findById(pedidoId).orElse(null);
|
||||||
if (pedido == null) {
|
if (pedido != null) {
|
||||||
return false;
|
|
||||||
|
PedidoDireccion direccionPedido = pedidoDireccionRepository.findByPedidoIdAndFacturacionTrue(pedidoId);
|
||||||
|
|
||||||
|
if (direccionPedido == null) {
|
||||||
|
// crear
|
||||||
|
direccionPedido = direccionData.toPedidoDireccion();
|
||||||
|
direccionPedido.setPedido(pedido);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// actualizar en la existente (NO crees una nueva, para conservar ID)
|
||||||
|
direccionData.applyTo(direccionPedido); // si implementas applyTo()
|
||||||
|
direccionPedido.setFacturacion(true); // por si acaso
|
||||||
|
}
|
||||||
|
|
||||||
|
pedidoDireccionRepository.save(direccionPedido);
|
||||||
}
|
}
|
||||||
|
|
||||||
PedidoDireccion direccionPedido = pedidoDireccionRepository.findByPedidoIdAndFacturacionTrue(pedidoId);
|
|
||||||
|
|
||||||
if (direccionPedido == null) {
|
|
||||||
// crear
|
|
||||||
direccionPedido = direccionData.toPedidoDireccion();
|
|
||||||
direccionPedido.setPedido(pedido);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// actualizar en la existente (NO crees una nueva, para conservar ID)
|
|
||||||
direccionData.applyTo(direccionPedido); // si implementas applyTo()
|
|
||||||
direccionPedido.setFacturacion(true); // por si acaso
|
|
||||||
}
|
|
||||||
|
|
||||||
pedidoDireccionRepository.save(direccionPedido);
|
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@ -299,43 +300,81 @@ public class PedidoService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, Object> actualizarEstado(Long pedidoLineaId, Locale locale) {
|
public Map<String, Object> actualizarEstado(Long pedidoLineaId, Locale locale) {
|
||||||
|
|
||||||
PedidoLinea pedidoLinea = pedidoLineaRepository.findById(pedidoLineaId).orElse(null);
|
PedidoLinea pedidoLinea = pedidoLineaRepository.findById(pedidoLineaId).orElse(null);
|
||||||
if (pedidoLinea == null) {
|
if (pedidoLinea == null) {
|
||||||
return Map.of("success", false,
|
return Map.of(
|
||||||
|
"success", false,
|
||||||
"message", messageSource.getMessage("pedido.errors.linea-not-found", null, locale));
|
"message", messageSource.getMessage("pedido.errors.linea-not-found", null, locale));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pedidoLinea.getEstado().getPriority() >= PedidoLinea.Estado.haciendo_ferro.getPriority() &&
|
PedidoLinea.Estado estadoOld = pedidoLinea.getEstado();
|
||||||
pedidoLinea.getEstado().getPriority() < PedidoLinea.Estado.enviado.getPriority()) {
|
if (estadoOld == null) {
|
||||||
PedidoLinea.Estado estadoOld = pedidoLinea.getEstado();
|
return Map.of(
|
||||||
Map<String, Object> result = skApiClient.checkPedidoEstado(
|
"success", false,
|
||||||
Long.valueOf(pedidoLinea.getPresupuesto().getProveedorRef2().toString()), locale);
|
"message", messageSource.getMessage("pedido.errors.cannot-update", null, locale));
|
||||||
if (result == null || !result.containsKey("estado")) {
|
}
|
||||||
return Map.of(
|
|
||||||
"success", false,
|
|
||||||
"message", messageSource.getMessage("pedido.errors.update-server-error", null, locale));
|
|
||||||
}
|
|
||||||
PedidoLinea.Estado estadoSk = PedidoLinea.Estado.valueOf((String) result.get("estado"));
|
|
||||||
if (estadoOld == estadoSk) {
|
|
||||||
return Map.of(
|
|
||||||
"success", true,
|
|
||||||
"state", messageSource.getMessage("pedido.estado." + estadoSk.name(), null, locale),
|
|
||||||
"stateKey", estadoSk.name(),
|
|
||||||
"message", messageSource.getMessage("pedido.success.same-estado", null, locale));
|
|
||||||
}
|
|
||||||
|
|
||||||
pedidoLinea.setEstado(estadoSk);
|
// Rango: >= haciendo_ferro y < enviado
|
||||||
pedidoLineaRepository.save(pedidoLinea);
|
if (estadoOld.getPriority() < PedidoLinea.Estado.haciendo_ferro.getPriority()
|
||||||
|
|| estadoOld.getPriority() >= PedidoLinea.Estado.enviado.getPriority()) {
|
||||||
|
return Map.of(
|
||||||
|
"success", false,
|
||||||
|
"message", messageSource.getMessage("pedido.errors.cannot-update", null, locale));
|
||||||
|
}
|
||||||
|
|
||||||
|
var presupuesto = pedidoLinea.getPresupuesto();
|
||||||
|
if (presupuesto == null || presupuesto.getProveedorRef2() == null) {
|
||||||
|
return Map.of(
|
||||||
|
"success", false,
|
||||||
|
"message", messageSource.getMessage("pedido.errors.update-server-error", null, locale));
|
||||||
|
}
|
||||||
|
|
||||||
|
Long refExterna;
|
||||||
|
try {
|
||||||
|
refExterna = Long.valueOf(presupuesto.getProveedorRef2().toString());
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return Map.of(
|
||||||
|
"success", false,
|
||||||
|
"message", messageSource.getMessage("pedido.errors.update-server-error", null, locale));
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> result = skApiClient.checkPedidoEstado(refExterna, locale);
|
||||||
|
|
||||||
|
if (result == null || result.get("estado") == null) {
|
||||||
|
return Map.of(
|
||||||
|
"success", false,
|
||||||
|
"message", messageSource.getMessage("pedido.errors.update-server-error", null, locale));
|
||||||
|
}
|
||||||
|
|
||||||
|
String estadoStr = String.valueOf(result.get("estado"));
|
||||||
|
|
||||||
|
PedidoLinea.Estado estadoSk;
|
||||||
|
try {
|
||||||
|
// si la API devuelve minúsculas tipo "produccion", esto funciona
|
||||||
|
estadoSk = PedidoLinea.Estado.valueOf(estadoStr.trim().toLowerCase());
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return Map.of(
|
||||||
|
"success", false,
|
||||||
|
"message", messageSource.getMessage("pedido.errors.update-server-error", null, locale));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (estadoOld == estadoSk) {
|
||||||
return Map.of(
|
return Map.of(
|
||||||
"success", true,
|
"success", true,
|
||||||
"state", messageSource.getMessage("pedido.estado." + estadoSk.name(), null, locale),
|
"state", messageSource.getMessage("pedido.estado." + estadoSk.name(), null, locale),
|
||||||
"stateKey", estadoSk.name(),
|
"stateKey", estadoSk.name(),
|
||||||
"message", messageSource.getMessage("pedido.success.estado-actualizado", null, locale));
|
"message", messageSource.getMessage("pedido.success.same-estado", null, locale));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pedidoLinea.setEstado(estadoSk);
|
||||||
|
pedidoLineaRepository.save(pedidoLinea);
|
||||||
|
|
||||||
return Map.of(
|
return Map.of(
|
||||||
"success", false,
|
"success", true,
|
||||||
"message", messageSource.getMessage("pedido.errors.cannot-update", null, locale));
|
"state", messageSource.getMessage("pedido.estado." + estadoSk.name(), null, locale),
|
||||||
|
"stateKey", estadoSk.name(),
|
||||||
|
"message", messageSource.getMessage("pedido.success.estado-actualizado", null, locale));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Boolean markPedidoAsMaquetacionDone(Long pedidoId) {
|
public Boolean markPedidoAsMaquetacionDone(Long pedidoId) {
|
||||||
|
|||||||
@ -26,6 +26,7 @@ import com.imprimelibros.erp.datatables.DataTable;
|
|||||||
import com.imprimelibros.erp.datatables.DataTablesParser;
|
import com.imprimelibros.erp.datatables.DataTablesParser;
|
||||||
import com.imprimelibros.erp.datatables.DataTablesRequest;
|
import com.imprimelibros.erp.datatables.DataTablesRequest;
|
||||||
import com.imprimelibros.erp.datatables.DataTablesResponse;
|
import com.imprimelibros.erp.datatables.DataTablesResponse;
|
||||||
|
import com.imprimelibros.erp.facturacion.service.FacturacionService;
|
||||||
import com.imprimelibros.erp.i18n.TranslationService;
|
import com.imprimelibros.erp.i18n.TranslationService;
|
||||||
import com.imprimelibros.erp.paises.PaisesService;
|
import com.imprimelibros.erp.paises.PaisesService;
|
||||||
import com.imprimelibros.erp.presupuesto.service.PresupuestoService;
|
import com.imprimelibros.erp.presupuesto.service.PresupuestoService;
|
||||||
@ -52,10 +53,12 @@ public class PedidosController {
|
|||||||
private final PedidoLineaRepository repoPedidoLinea;
|
private final PedidoLineaRepository repoPedidoLinea;
|
||||||
private final PaisesService paisesService;
|
private final PaisesService paisesService;
|
||||||
private final TranslationService translationService;
|
private final TranslationService translationService;
|
||||||
|
private final FacturacionService facturacionService;
|
||||||
|
|
||||||
public PedidosController(PedidoRepository repoPedido, PedidoService pedidoService, UserDao repoUser,
|
public PedidosController(PedidoRepository repoPedido, PedidoService pedidoService, UserDao repoUser,
|
||||||
MessageSource messageSource, TranslationService translationService,
|
MessageSource messageSource, TranslationService translationService,
|
||||||
PedidoLineaRepository repoPedidoLinea, PaisesService paisesService, PresupuestoService presupuestoService) {
|
PedidoLineaRepository repoPedidoLinea, PaisesService paisesService,
|
||||||
|
FacturacionService facturacionService, PresupuestoService presupuestoService) {
|
||||||
this.repoPedido = repoPedido;
|
this.repoPedido = repoPedido;
|
||||||
this.pedidoService = pedidoService;
|
this.pedidoService = pedidoService;
|
||||||
this.repoUser = repoUser;
|
this.repoUser = repoUser;
|
||||||
@ -63,6 +66,7 @@ public class PedidosController {
|
|||||||
this.translationService = translationService;
|
this.translationService = translationService;
|
||||||
this.repoPedidoLinea = repoPedidoLinea;
|
this.repoPedidoLinea = repoPedidoLinea;
|
||||||
this.paisesService = paisesService;
|
this.paisesService = paisesService;
|
||||||
|
this.facturacionService = facturacionService;
|
||||||
this.presupuestoService = presupuestoService;
|
this.presupuestoService = presupuestoService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,6 +240,7 @@ public class PedidosController {
|
|||||||
model.addAttribute("direccionFacturacion", direccionFacturacion);
|
model.addAttribute("direccionFacturacion", direccionFacturacion);
|
||||||
|
|
||||||
Boolean showCancel = false;
|
Boolean showCancel = false;
|
||||||
|
Boolean showDownloadFactura = true;
|
||||||
List<Map<String, Object>> lineas = pedidoService.getLineas(id, locale);
|
List<Map<String, Object>> lineas = pedidoService.getLineas(id, locale);
|
||||||
for (Map<String, Object> linea : lineas) {
|
for (Map<String, Object> linea : lineas) {
|
||||||
|
|
||||||
@ -243,6 +248,9 @@ public class PedidosController {
|
|||||||
((Number) linea.get("lineaId")).longValue()).orElse(null);
|
((Number) linea.get("lineaId")).longValue()).orElse(null);
|
||||||
if (pedidoLinea != null) {
|
if (pedidoLinea != null) {
|
||||||
Map<String, Boolean> buttons = new HashMap<>();
|
Map<String, Boolean> buttons = new HashMap<>();
|
||||||
|
if (pedidoLinea.getEstado() != PedidoLinea.Estado.enviado) {
|
||||||
|
showDownloadFactura = false;
|
||||||
|
}
|
||||||
if (pedidoLinea.getEstado().getPriority() >= PedidoLinea.Estado.esperando_aceptacion_ferro.getPriority()
|
if (pedidoLinea.getEstado().getPriority() >= PedidoLinea.Estado.esperando_aceptacion_ferro.getPriority()
|
||||||
&& pedidoLinea.getEstado().getPriority() <= PedidoLinea.Estado.produccion.getPriority()) {
|
&& pedidoLinea.getEstado().getPriority() <= PedidoLinea.Estado.produccion.getPriority()) {
|
||||||
|
|
||||||
@ -263,7 +271,9 @@ public class PedidosController {
|
|||||||
linea.put("buttons", buttons);
|
linea.put("buttons", buttons);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(pedidoLinea.getEstado() != PedidoLinea.Estado.cancelado && pedidoLinea.getEstado() != PedidoLinea.Estado.terminado && pedidoLinea.getEstado() != PedidoLinea.Estado.enviado) {
|
if (pedidoLinea.getEstado() != PedidoLinea.Estado.cancelado
|
||||||
|
&& pedidoLinea.getEstado() != PedidoLinea.Estado.terminado
|
||||||
|
&& pedidoLinea.getEstado() != PedidoLinea.Estado.enviado) {
|
||||||
showCancel = true;
|
showCancel = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -280,8 +290,16 @@ public class PedidosController {
|
|||||||
linea.put("direccionesEntrega", dirEntrega);
|
linea.put("direccionesEntrega", dirEntrega);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Long facturaId = null;
|
||||||
|
if (showDownloadFactura) {
|
||||||
|
facturaId = facturacionService.getFacturaIdFromPedidoId(id);
|
||||||
|
}
|
||||||
model.addAttribute("lineas", lineas);
|
model.addAttribute("lineas", lineas);
|
||||||
model.addAttribute("showCancel", showCancel);
|
model.addAttribute("showCancel", showCancel);
|
||||||
|
if (showDownloadFactura && facturaId != null) {
|
||||||
|
model.addAttribute("facturaId", facturaId);
|
||||||
|
model.addAttribute("showDownloadFactura", showDownloadFactura);
|
||||||
|
}
|
||||||
model.addAttribute("id", id);
|
model.addAttribute("id", id);
|
||||||
return "imprimelibros/pedidos/pedidos-view";
|
return "imprimelibros/pedidos/pedidos-view";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,7 +48,7 @@ databaseChangeLog:
|
|||||||
sql: |
|
sql: |
|
||||||
INSERT INTO variables (clave, valor)
|
INSERT INTO variables (clave, valor)
|
||||||
SELECT
|
SELECT
|
||||||
'sere_facturacion_rect_default',
|
'serie_facturacion_rect_default',
|
||||||
CAST(sf.id AS CHAR)
|
CAST(sf.id AS CHAR)
|
||||||
FROM series_facturas sf
|
FROM series_facturas sf
|
||||||
WHERE sf.prefijo = 'REC IL'
|
WHERE sf.prefijo = 'REC IL'
|
||||||
|
|||||||
@ -0,0 +1,114 @@
|
|||||||
|
databaseChangeLog:
|
||||||
|
- changeSet:
|
||||||
|
id: create-facturas-direcciones
|
||||||
|
author: jjo
|
||||||
|
|
||||||
|
changes:
|
||||||
|
- createTable:
|
||||||
|
tableName: facturas_direcciones
|
||||||
|
columns:
|
||||||
|
- column:
|
||||||
|
name: id
|
||||||
|
type: BIGINT
|
||||||
|
autoIncrement: true
|
||||||
|
constraints:
|
||||||
|
primaryKey: true
|
||||||
|
nullable: false
|
||||||
|
|
||||||
|
- column:
|
||||||
|
name: factura_id
|
||||||
|
type: BIGINT
|
||||||
|
constraints:
|
||||||
|
nullable: false
|
||||||
|
|
||||||
|
- column:
|
||||||
|
name: unidades
|
||||||
|
type: MEDIUMINT UNSIGNED
|
||||||
|
|
||||||
|
- column:
|
||||||
|
name: email
|
||||||
|
type: VARCHAR(255)
|
||||||
|
|
||||||
|
- column:
|
||||||
|
name: att
|
||||||
|
type: VARCHAR(150)
|
||||||
|
constraints:
|
||||||
|
nullable: false
|
||||||
|
|
||||||
|
- column:
|
||||||
|
name: direccion
|
||||||
|
type: VARCHAR(255)
|
||||||
|
constraints:
|
||||||
|
nullable: false
|
||||||
|
|
||||||
|
- column:
|
||||||
|
name: cp
|
||||||
|
type: MEDIUMINT UNSIGNED
|
||||||
|
constraints:
|
||||||
|
nullable: false
|
||||||
|
|
||||||
|
- column:
|
||||||
|
name: ciudad
|
||||||
|
type: VARCHAR(100)
|
||||||
|
constraints:
|
||||||
|
nullable: false
|
||||||
|
|
||||||
|
- column:
|
||||||
|
name: provincia
|
||||||
|
type: VARCHAR(100)
|
||||||
|
constraints:
|
||||||
|
nullable: false
|
||||||
|
|
||||||
|
- column:
|
||||||
|
name: pais_code3
|
||||||
|
type: CHAR(3)
|
||||||
|
defaultValue: esp
|
||||||
|
constraints:
|
||||||
|
nullable: false
|
||||||
|
|
||||||
|
- column:
|
||||||
|
name: telefono
|
||||||
|
type: VARCHAR(30)
|
||||||
|
|
||||||
|
- column:
|
||||||
|
name: instrucciones
|
||||||
|
type: VARCHAR(255)
|
||||||
|
|
||||||
|
- column:
|
||||||
|
name: razon_social
|
||||||
|
type: VARCHAR(150)
|
||||||
|
|
||||||
|
- column:
|
||||||
|
name: tipo_identificacion_fiscal
|
||||||
|
type: ENUM('DNI','NIE','CIF','Pasaporte','VAT_ID')
|
||||||
|
defaultValue: DNI
|
||||||
|
constraints:
|
||||||
|
nullable: false
|
||||||
|
|
||||||
|
- column:
|
||||||
|
name: identificacion_fiscal
|
||||||
|
type: VARCHAR(50)
|
||||||
|
|
||||||
|
- column:
|
||||||
|
name: created_at
|
||||||
|
type: TIMESTAMP
|
||||||
|
defaultValueComputed: CURRENT_TIMESTAMP
|
||||||
|
constraints:
|
||||||
|
nullable: false
|
||||||
|
|
||||||
|
- addForeignKeyConstraint:
|
||||||
|
constraintName: fk_facturas_direcciones_factura
|
||||||
|
baseTableName: facturas_direcciones
|
||||||
|
baseColumnNames: factura_id
|
||||||
|
referencedTableName: facturas
|
||||||
|
referencedColumnNames: id
|
||||||
|
onDelete: CASCADE
|
||||||
|
onUpdate: RESTRICT
|
||||||
|
|
||||||
|
rollback:
|
||||||
|
- dropForeignKeyConstraint:
|
||||||
|
baseTableName: facturas_direcciones
|
||||||
|
constraintName: fk_facturas_direcciones_factura
|
||||||
|
|
||||||
|
- dropTable:
|
||||||
|
tableName: facturas_direcciones
|
||||||
@ -47,3 +47,5 @@ databaseChangeLog:
|
|||||||
file: db/changelog/changesets/0023-facturacion.yml
|
file: db/changelog/changesets/0023-facturacion.yml
|
||||||
- include:
|
- include:
|
||||||
file: db/changelog/changesets/0024-series-facturacion-seeder.yml
|
file: db/changelog/changesets/0024-series-facturacion-seeder.yml
|
||||||
|
- include:
|
||||||
|
file: db/changelog/changesets/0025-create-facturas-direcciones.yml
|
||||||
@ -1,6 +1,7 @@
|
|||||||
facturas.title=Facturas
|
facturas.title=Facturas
|
||||||
facturas.breadcrumb=Facturas
|
facturas.breadcrumb=Facturas
|
||||||
facturas.breadcrumb.ver=Ver Factura
|
facturas.breadcrumb.ver=Ver Factura
|
||||||
|
facturas.breadcrumb.nueva=Nueva Factura
|
||||||
|
|
||||||
facturas.tabla.id=ID
|
facturas.tabla.id=ID
|
||||||
facturas.tabla.cliente=Cliente
|
facturas.tabla.cliente=Cliente
|
||||||
@ -19,10 +20,17 @@ facturas.estado.borrador=Borrador
|
|||||||
facturas.estado.validada=Validada
|
facturas.estado.validada=Validada
|
||||||
|
|
||||||
facturas.form.numero-factura=Número de Factura
|
facturas.form.numero-factura=Número de Factura
|
||||||
|
facturas.form.id=ID de la Factura
|
||||||
|
facturas.form.factura-rectificada=Factura rectificada
|
||||||
facturas.form.serie=Serie de facturación
|
facturas.form.serie=Serie de facturación
|
||||||
|
facturas.form.serie.placeholder=Seleccione una serie...
|
||||||
facturas.form.fecha-emision=Fecha de Emisión
|
facturas.form.fecha-emision=Fecha de Emisión
|
||||||
facturas.form.cliente=Cliente
|
facturas.form.cliente=Cliente
|
||||||
|
facturas.form.direccion-facturacion=Dirección de Facturación
|
||||||
|
facturas.form.direccion-facturacion.placeholder=Seleccione una dirección...
|
||||||
|
facturas.form.cliente.placeholder=Seleccione un cliente...
|
||||||
facturas.form.notas=Notas
|
facturas.form.notas=Notas
|
||||||
|
facturas.form.factura-rectificada=Factura rectificada
|
||||||
|
|
||||||
facturas.form.btn.validar=Validar Factura
|
facturas.form.btn.validar=Validar Factura
|
||||||
facturas.form.btn.borrador=Pasar a Borrador
|
facturas.form.btn.borrador=Pasar a Borrador
|
||||||
@ -85,3 +93,8 @@ facturas.delete.title=¿Estás seguro de que deseas eliminar esta factura?
|
|||||||
facturas.delete.text=Esta acción no se puede deshacer.
|
facturas.delete.text=Esta acción no se puede deshacer.
|
||||||
facturas.delete.ok.title=Factura eliminada
|
facturas.delete.ok.title=Factura eliminada
|
||||||
facturas.delete.ok.text=La factura ha sido eliminada correctamente.
|
facturas.delete.ok.text=La factura ha sido eliminada correctamente.
|
||||||
|
|
||||||
|
facturas.add.form.validation.title=Error al crear la factura
|
||||||
|
facturas.add.form.validation=Revise que todos los campos están rellenos
|
||||||
|
|
||||||
|
facturas.error.create=No se ha podido crear la factura. Revise los datos e inténtelo de nuevo.
|
||||||
|
|||||||
@ -57,7 +57,9 @@ pedido.view.aceptar-ferro=Aceptar ferro
|
|||||||
pedido.view.ferro-download=Descargar ferro
|
pedido.view.ferro-download=Descargar ferro
|
||||||
pedido.view.cub-download=Descargar cubierta
|
pedido.view.cub-download=Descargar cubierta
|
||||||
pedido.view.tapa-download=Descargar tapa
|
pedido.view.tapa-download=Descargar tapa
|
||||||
|
pedido.view.descargar-factura=Descargar factura
|
||||||
pedido.view.admin-actions=Acciones de administrador
|
pedido.view.admin-actions=Acciones de administrador
|
||||||
|
pedido.view.actions=Acciones
|
||||||
pedido.view.cancel-title=¿Estás seguro de que deseas cancelar este pedido?
|
pedido.view.cancel-title=¿Estás seguro de que deseas cancelar este pedido?
|
||||||
pedido.view.cancel-text=Esta acción no se puede deshacer.
|
pedido.view.cancel-text=Esta acción no se puede deshacer.
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,216 @@
|
|||||||
|
$(() => {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let $addBtn = $('#save-btn');
|
||||||
|
let $cancelBtn = $('#cancel-btn');
|
||||||
|
let $selectCliente = $('#clienteSelect');
|
||||||
|
let $serieInput = $('#serieInput');
|
||||||
|
let $direccionFacturacion = $('#direccionFacturacion');
|
||||||
|
let $facturaRectificada = $('#facturaRectificada');
|
||||||
|
let $divFacturaRectificada = $('#div-factura-rectificada');
|
||||||
|
|
||||||
|
// -----------------------------
|
||||||
|
// Initialize select2 for cliente selection
|
||||||
|
// -----------------------------
|
||||||
|
$selectCliente.select2({
|
||||||
|
placeholder: languageBundle['facturas.form.cliente.placeholder'],
|
||||||
|
width: '100%',
|
||||||
|
ajax: {
|
||||||
|
url: '/users/api/get-users',
|
||||||
|
dataType: 'json',
|
||||||
|
delay: 250,
|
||||||
|
data: function (params) {
|
||||||
|
return {
|
||||||
|
showUsername: true,
|
||||||
|
q: params.term,
|
||||||
|
page: params.page || 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
minimumInputLength: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
$selectCliente.on('select2:select', function (e) {
|
||||||
|
const data = e.params.data;
|
||||||
|
$serieInput.val(null).trigger('change');
|
||||||
|
$direccionFacturacion.val(null).trigger('change');
|
||||||
|
$facturaRectificada.val(null).trigger('change');
|
||||||
|
if (data && data.id) {
|
||||||
|
$serieInput.prop('disabled', false);
|
||||||
|
$direccionFacturacion.prop('disabled', false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$serieInput.prop('disabled', true);
|
||||||
|
$direccionFacturacion.prop('disabled', true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$serieInput.select2({
|
||||||
|
placeholder: languageBundle['facturas.form.serie.placeholder'],
|
||||||
|
width: '100%',
|
||||||
|
ajax: {
|
||||||
|
url: '/configuracion/series-facturacion/api/get-series',
|
||||||
|
dataType: 'json',
|
||||||
|
delay: 250,
|
||||||
|
data: function (params) {
|
||||||
|
return {
|
||||||
|
q: params.term,
|
||||||
|
page: params.page || 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
minimumInputLength: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
$serieInput.on('select2:select', function (e) {
|
||||||
|
const data = e.params.data;
|
||||||
|
const defaultRectSerieId = $serieInput.data('default-serie-rect');
|
||||||
|
if (data && data.id) {
|
||||||
|
if (data.id === defaultRectSerieId) {
|
||||||
|
$divFacturaRectificada.removeClass('d-none');
|
||||||
|
$facturaRectificada.val(null).trigger('change');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$divFacturaRectificada.addClass('d-none');
|
||||||
|
$facturaRectificada.val(null).trigger('change');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$direccionFacturacion.select2({
|
||||||
|
placeholder: languageBundle['facturas.form.direccion-facturacion.placeholder'],
|
||||||
|
width: '100%',
|
||||||
|
ajax: {
|
||||||
|
url: '/facturas/api/get-direcciones',
|
||||||
|
dataType: 'json',
|
||||||
|
delay: 250,
|
||||||
|
data: function (params) {
|
||||||
|
const clienteId = $selectCliente.val();
|
||||||
|
return {
|
||||||
|
user_id: clienteId,
|
||||||
|
q: params.term,
|
||||||
|
page: params.page || 1
|
||||||
|
};
|
||||||
|
},
|
||||||
|
processResults: (data) => {
|
||||||
|
const items = Array.isArray(data) ? data : (data.results || []);
|
||||||
|
return {
|
||||||
|
results: items.map(item => ({
|
||||||
|
id: item.id,
|
||||||
|
text: item.text, // ← Select2 necesita 'id' y 'text'
|
||||||
|
alias: item.alias || 'Sin alias',
|
||||||
|
att: item.att || '',
|
||||||
|
direccion: item.direccion || '',
|
||||||
|
cp: item.cp || '',
|
||||||
|
ciudad: item.ciudad || '',
|
||||||
|
html: `
|
||||||
|
<div>
|
||||||
|
<strong>${item.alias || 'Sin alias'}</strong><br>
|
||||||
|
${item.att ? `<small>${item.att}</small><br>` : ''}
|
||||||
|
<small>${item.direccion || ''}${item.cp ? ', ' + item.cp : ''}${item.ciudad ? ', ' + item.ciudad : ''}</small>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
})),
|
||||||
|
pagination: { more: false } // opcional, evita que espere más páginas
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
minimumInputLength: 0,
|
||||||
|
templateResult: data => {
|
||||||
|
if (data.loading) return data.text;
|
||||||
|
return $(data.html || data.text);
|
||||||
|
},
|
||||||
|
// Selección más compacta (solo alias + ciudad)
|
||||||
|
templateSelection: data => {
|
||||||
|
if (!data.id) return data.text;
|
||||||
|
const alias = data.alias || data.text;
|
||||||
|
const ciudad = data.ciudad ? ` — ${data.ciudad}` : '';
|
||||||
|
return $(`<span>${alias}${ciudad}</span>`);
|
||||||
|
},
|
||||||
|
escapeMarkup: m => m
|
||||||
|
});
|
||||||
|
|
||||||
|
$facturaRectificada.select2({
|
||||||
|
placeholder: languageBundle['facturas.form.factura-rectificada.placeholder'],
|
||||||
|
width: '100%',
|
||||||
|
ajax: {
|
||||||
|
url: '/facturas/api/get-facturas-rectificables',
|
||||||
|
dataType: 'json',
|
||||||
|
delay: 250,
|
||||||
|
data: function (params) {
|
||||||
|
const clienteId = $selectCliente.val();
|
||||||
|
return {
|
||||||
|
user_id: clienteId,
|
||||||
|
q: params.term,
|
||||||
|
page: params.page || 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
minimumInputLength: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
// -----------------------------
|
||||||
|
// Cancel button click
|
||||||
|
// -----------------------------
|
||||||
|
$cancelBtn.on('click', () => {
|
||||||
|
window.location.href = '/facturas';
|
||||||
|
});
|
||||||
|
|
||||||
|
// -----------------------------
|
||||||
|
// Save button click
|
||||||
|
// -----------------------------
|
||||||
|
$addBtn.on('click', () => {
|
||||||
|
const clienteId = $selectCliente.val();
|
||||||
|
const serieId = $serieInput.val();
|
||||||
|
const direccionId = $direccionFacturacion.val();
|
||||||
|
const facturaRectificadaId = $facturaRectificada.val();
|
||||||
|
|
||||||
|
if (!clienteId && !serieId && !direccionId) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: languageBundle['facturas.add.form.validation.title'],
|
||||||
|
text: languageBundle['facturas.add.form.validation']
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/facturas/add',
|
||||||
|
method: 'POST',
|
||||||
|
contentType: 'application/json',
|
||||||
|
data: JSON.stringify({
|
||||||
|
user: clienteId,
|
||||||
|
serie: serieId,
|
||||||
|
direccion: direccionId,
|
||||||
|
factura_rectificada: facturaRectificadaId
|
||||||
|
}),
|
||||||
|
success: function (response) {
|
||||||
|
if (response.success) {
|
||||||
|
window.location.href = '/facturas/' + response.facturaId;
|
||||||
|
} else {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Error',
|
||||||
|
text: response.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function () {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Error',
|
||||||
|
text: languageBundle['facturas.error.create']
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -62,7 +62,9 @@ $(() => {
|
|||||||
// -----------------------------
|
// -----------------------------
|
||||||
// Add
|
// Add
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
$addBtn.on();
|
$addBtn.on('click', () => {
|
||||||
|
window.location.href = '/facturas/add';
|
||||||
|
});
|
||||||
|
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
// Edit click
|
// Edit click
|
||||||
|
|||||||
@ -83,4 +83,20 @@ $(() => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($(".btn-download-factura").length) {
|
||||||
|
$(document).on('click', '.btn-download-factura', function () {
|
||||||
|
const facturaId = $(this).data('factura-id');
|
||||||
|
|
||||||
|
const url = `/api/pdf/factura/${facturaId}?mode=download`;
|
||||||
|
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.target = '_self'; // descarga en la misma pestaña
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { duplicar, reimprimir } from './presupuesto-utils.js';
|
||||||
(() => {
|
(() => {
|
||||||
// si jQuery está cargado, añade CSRF a AJAX
|
// si jQuery está cargado, añade CSRF a AJAX
|
||||||
const csrfToken = document.querySelector('meta[name="_csrf"]')?.getAttribute('content');
|
const csrfToken = document.querySelector('meta[name="_csrf"]')?.getAttribute('content');
|
||||||
@ -124,6 +125,27 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#presupuestos-clientes-user-datatable').on('click', '.btn-duplicate-privado', function (e) {
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
const id = $(this).data('id');
|
||||||
|
let data = table.row($(this).parents('tr')).data();
|
||||||
|
const tituloOriginal = data.titulo;
|
||||||
|
|
||||||
|
duplicar(id, tituloOriginal);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#presupuestos-clientes-user-datatable').on('click', '.btn-reprint-privado', function (e) {
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
const id = $(this).data('id');
|
||||||
|
let data = table.row($(this).parents('tr')).data();
|
||||||
|
const tituloOriginal = data.titulo;
|
||||||
|
|
||||||
|
reimprimir(id, tituloOriginal);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
$('#presupuestos-clientes-user-datatable').on('keyup', '.presupuesto-filter', function (e) {
|
$('#presupuestos-clientes-user-datatable').on('keyup', '.presupuesto-filter', function (e) {
|
||||||
const colName = $(this).data('col');
|
const colName = $(this).data('col');
|
||||||
const colIndex = table.column(colName + ':name').index();
|
const colIndex = table.column(colName + ':name').index();
|
||||||
|
|||||||
@ -0,0 +1,120 @@
|
|||||||
|
<!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" />
|
||||||
|
<link sec:authorize="isAuthenticated() and hasAnyRole('SUPERADMIN','ADMIN')"
|
||||||
|
th:href="@{/assets/libs/quill/quill.snow.css}" rel="stylesheet" type="text/css" />
|
||||||
|
</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()')}">
|
||||||
|
|
||||||
|
|
||||||
|
<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"><a href="/facturas" th:text="#{facturas.breadcrumb}"></a></li>
|
||||||
|
<li class="breadcrumb-item active" aria-current="page" th:text="#{facturas.breadcrumb.nueva}">
|
||||||
|
Nueva factura</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="container-fluid position-relative">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12 col-md-6 mb-3">
|
||||||
|
<label for="clienteSelect" class="form-label" th:text="#{facturas.form.cliente}">Cliente</label>
|
||||||
|
<select id="clienteSelect" class="form-select select2"
|
||||||
|
th:placeholder="#{facturas.form.cliente.placeholder}">
|
||||||
|
<option th:each="cliente : ${clientes}" th:value="${cliente.id}"
|
||||||
|
th:text="${cliente.nombre} + ' (' + cliente.email + ')'"></option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-6 mb-3">
|
||||||
|
<label for="serieInput" class="form-label" th:text="#{facturas.form.serie}">Serie de
|
||||||
|
facturación</label>
|
||||||
|
<select id="serieInput" class="form-select select2" disabled
|
||||||
|
th:data-default-serie-rect="${defaultSerieRectificativa}">
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- salto de fila SOLO en md+ -->
|
||||||
|
<div class="w-100 d-none d-md-block"></div>
|
||||||
|
|
||||||
|
<div class="col-12 col-md-6 mb-3">
|
||||||
|
<label for="direccionFacturacion" class="form-label"
|
||||||
|
th:text="#{facturas.form.direccion-facturacion}">
|
||||||
|
Factura rectificada
|
||||||
|
</label>
|
||||||
|
<select id="direccionFacturacion" class="form-select select2" disabled></select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-md-6 mb-3 d-none" id="div-factura-rectificada">
|
||||||
|
<label for="facturaRectificada" class="form-label"
|
||||||
|
th:text="#{facturas.form.factura-rectificada}">
|
||||||
|
Factura rectificada
|
||||||
|
</label>
|
||||||
|
<select id="facturaRectificada" class="form-select select2"></select>
|
||||||
|
</div>
|
||||||
|
</div> <!-- end row -->
|
||||||
|
|
||||||
|
<div class="row mt-3 justify-content-md-end g-2">
|
||||||
|
<div class="col-12 col-md-auto">
|
||||||
|
<button type="button" th:text="#{app.guardar}" id="save-btn" class="btn btn-secondary w-100">
|
||||||
|
Guardar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-md-auto">
|
||||||
|
<button type="button" th:text="#{app.cancelar}" id="cancel-btn" class="btn btn-light w-100">
|
||||||
|
Cancelar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</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 sec:authorize="isAuthenticated() and hasAnyRole('SUPERADMIN','ADMIN')"
|
||||||
|
th:src="@{/assets/libs/quill/quill.min.js}"></script>
|
||||||
|
|
||||||
|
<script type="module" th:src="@{/assets/js/pages/imprimelibros/facturas/add.js}"></script>
|
||||||
|
|
||||||
|
</th:block>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@ -16,10 +16,14 @@
|
|||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
|
|
||||||
<!-- Número (solo lectura siempre, normalmente) -->
|
<!-- Número (solo lectura siempre, normalmente) -->
|
||||||
<div class="col-md-3">
|
<div th:if="${factura.numeroFactura != null}" class="col-md-3">
|
||||||
<label class="form-label" th:text="#{facturas.form.numero-factura}">Número de factura</label>
|
<label class="form-label" th:text="#{facturas.form.numero-factura}">Número de factura</label>
|
||||||
<input id="facturaNumero" type="text" class="form-control" th:value="${factura.numeroFactura}" readonly>
|
<input id="facturaNumero" type="text" class="form-control" th:value="${factura.numeroFactura}" readonly>
|
||||||
</div>
|
</div>
|
||||||
|
<div th:if="${factura.numeroFactura == null}" class="col-md-3">
|
||||||
|
<label class="form-label" th:text="#{facturas.form.id}">ID de la factura</label>
|
||||||
|
<input id="facturaId" type="text" class="form-control" th:value="${factura.id}" readonly>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Serie -->
|
<!-- Serie -->
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
@ -43,6 +47,15 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Factura rectificada -->
|
||||||
|
<div th:if="${factura.facturaRectificada != null}" class="w-100 d-md-block"></div>
|
||||||
|
<div th:if="${factura.facturaRectificada != null}" class="col-md-3">
|
||||||
|
<label class="form-label" th:text="#{facturas.form.factura-rectificada}">Factura rectificada</label>
|
||||||
|
<input readonly id="facturaRectificadaId" class="form-control"
|
||||||
|
th:value="${factura.facturaRectificada.numeroFactura}" />
|
||||||
|
</div>
|
||||||
|
<div th:if="${factura.facturaRectificada != null}" class="w-100 d-md-block"></div>
|
||||||
|
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<label class="form-label" th:text="#{facturas.form.fecha-emision}">Fecha</label>
|
<label class="form-label" th:text="#{facturas.form.fecha-emision}">Fecha</label>
|
||||||
|
|
||||||
|
|||||||
@ -73,24 +73,24 @@
|
|||||||
Aceptar ferro
|
Aceptar ferro
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button th:if="${item.estado.priority >= 7 && item.estado.priority < 11 && item.buttons.ferro}"
|
<button
|
||||||
|
th:if="${item.estado.priority >= 7 and item.estado.priority <= 11 and item['buttons'] != null and item['buttons']['ferro'] == true}"
|
||||||
type="button" class="btn btn-light w-100 btn-download-ferro"
|
type="button" class="btn btn-light w-100 btn-download-ferro"
|
||||||
th:text="#{pedido.view.ferro-download}"
|
th:text="#{pedido.view.ferro-download}" th:attr="data-linea-id=${item.lineaId}">
|
||||||
th:attr="data-linea-id=${item.lineaId}">
|
|
||||||
Descargar ferro
|
Descargar ferro
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button th:if="${item.estado.priority >= 7 && item.estado.priority < 11 && item.buttons.cub}"
|
<button
|
||||||
|
th:if="${item.estado.priority >= 7 and item.estado.priority <= 11 and item['buttons'] != null and item['buttons']['cub'] == true}"
|
||||||
type="button" class="btn btn-light w-100 btn-download-cub"
|
type="button" class="btn btn-light w-100 btn-download-cub"
|
||||||
th:text="#{pedido.view.cub-download}"
|
th:text="#{pedido.view.cub-download}" th:attr="data-linea-id=${item.lineaId}">
|
||||||
th:attr="data-linea-id=${item.lineaId}">
|
|
||||||
Descargar cubierta
|
Descargar cubierta
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button th:if="${item.estado.priority >= 7 && item.estado.priority < 11 && item.buttons.tapa}"
|
<button
|
||||||
|
th:if="${item.estado.priority >= 7 and item.estado.priority <= 11 and item['buttons'] != null and item['buttons']['tapa'] == true}"
|
||||||
type="button" class="btn btn-light w-100 btn-download-tapa"
|
type="button" class="btn btn-light w-100 btn-download-tapa"
|
||||||
th:text="#{pedido.view.tapa-download}"
|
th:text="#{pedido.view.tapa-download}" th:attr="data-linea-id=${item.lineaId}">
|
||||||
th:attr="data-linea-id=${item.lineaId}">
|
|
||||||
Descargar tapa
|
Descargar tapa
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -136,7 +136,7 @@
|
|||||||
<div class="d-flex flex-wrap my-n1">
|
<div class="d-flex flex-wrap my-n1">
|
||||||
<!-- Actualizar estado-->
|
<!-- Actualizar estado-->
|
||||||
<div class="update-estado-button"
|
<div class="update-estado-button"
|
||||||
th:if="${item.estado.name != 'cancelado' && item.estado.name != 'maquetacion' && item.estado.name != 'terminado'}">
|
th:if="${item.estado.name != 'cancelado' && item.estado.name != 'maquetacion' && item.estado.name != 'enviado'}">
|
||||||
<a href="javascript:void(0);" class="d-block text-body p-1 px-2 update-status-item"
|
<a href="javascript:void(0);" class="d-block text-body p-1 px-2 update-status-item"
|
||||||
th:attr="data-linea-id=${item.lineaId}">
|
th:attr="data-linea-id=${item.lineaId}">
|
||||||
<i class="ri-refresh-line text-muted align-bottom me-1"><span
|
<i class="ri-refresh-line text-muted align-bottom me-1"><span
|
||||||
|
|||||||
@ -43,6 +43,7 @@
|
|||||||
pais=${direccionFacturacion != null ? direccionFacturacion.paisNombre : ''}
|
pais=${direccionFacturacion != null ? direccionFacturacion.paisNombre : ''}
|
||||||
)}">
|
)}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<th:block th:if="${isAdmin and showCancel}">
|
<th:block th:if="${isAdmin and showCancel}">
|
||||||
<div sec:authorize="isAuthenticated() and hasAnyRole('SUPERADMIN','ADMIN')"
|
<div sec:authorize="isAuthenticated() and hasAnyRole('SUPERADMIN','ADMIN')"
|
||||||
@ -60,11 +61,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</th:block>
|
</th:block>
|
||||||
|
<th:block th:if="${showDownloadFactura}">
|
||||||
|
<div class="col-12 col-md-auto">
|
||||||
|
<div class="card card border mb-3">
|
||||||
|
<div class="card-header bg-light">
|
||||||
|
<span class="fs-16" th:text="#{'pedido.view.actions'}"></span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<button type="button" th:attr="data-factura-id=${facturaId}" class="btn btn-secondary w-100 btn-download-factura"
|
||||||
|
th:text="#{pedido.view.descargar-factura}">
|
||||||
|
Descargar factura
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th:block>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<th:block th:each="linea: ${lineas}">
|
<th:block th:each="linea: ${lineas}">
|
||||||
<div
|
<div
|
||||||
th:insert="~{imprimelibros/pedidos/pedidos-linea :: pedido-linea (item=${linea}, isAdmin=${isAdmin})}">
|
th:insert="~{imprimelibros/pedidos/pedidos-linea :: pedido-linea (item=${linea}, isAdmin=${isAdmin})}">
|
||||||
|
|||||||
Reference in New Issue
Block a user