VerifactuMidAPI/internal/factura.go

235 lines
5.3 KiB
Go

package internal
import (
"log"
"time"
"VerifactuMidAPI/verifactu"
)
type FacturaService struct {
hashStorage LastRecordStorage
verifactu *verifactu.Client
}
func NewFacturaService(storage LastRecordStorage) *FacturaService {
return &FacturaService{
hashStorage: storage,
}
}
func (s *FacturaService) SetVerifactuClient(client *verifactu.Client) {
s.verifactu = client
}
type AltaInput struct {
InvoiceInput
EmisorNombre string
}
type AltaOutput struct {
Success bool `json:"success"`
CSV string `json:"csv,omitempty"`
Estado string `json:"estado,omitempty"`
Error string `json:"error,omitempty"`
}
func (s *FacturaService) ProcessAlta(input AltaInput) (*AltaOutput, error) {
defer func() {
if r := recover(); r != nil {
log.Printf("PANIC: %v", r)
}
}()
errs := ValidateInvoiceInput(&input.InvoiceInput)
if len(errs) > 0 {
errMsg := ""
for _, e := range errs {
errMsg += e.Error() + "; "
}
log.Printf("validation errors: %s", errMsg)
return &AltaOutput{
Success: false,
Error: "validation_failed: " + errMsg,
}, nil
}
data, err := TransformToInvoiceData(&input.InvoiceInput)
if err != nil {
log.Printf("transform error: %v", err)
return &AltaOutput{
Success: false,
Error: err.Error(),
}, nil
}
prevHash := ""
if s.hashStorage != nil {
record, err := s.hashStorage.GetLastRecord(input.Factura.EmisorNIF)
if err != nil {
return &AltaOutput{
Success: false,
Error: "hash_storage_error",
}, nil
}
if record != nil {
prevHash = record.Huella
}
}
hashService := NewHashService()
currentHash := hashService.CalculateHash(data, prevHash)
now := time.Now()
lastRecord := &LastRecord{
EmisorNIF: data.EmisorNIF,
NumSerie: data.NumSerie,
Fecha: data.Fecha,
Huella: currentHash,
FechaGen: now,
}
data.Huella = currentHash
data.FechaGen = now
altaData := ToAltaData(&input.InvoiceInput, data, currentHash, prevHash)
if s.verifactu != nil {
log.Printf("Sending to AEAT...")
resp, err := s.verifactu.SendAlta(altaData)
if err != nil {
log.Printf("AEAT error: %v", err)
log.Printf("AEAT error, falling back to local: %v", err)
goto saveLocal
}
if resp.Body.Fault != nil {
log.Printf("AEAT fault, falling back to local: %v", resp.Body.Fault.FaultString)
goto saveLocal
}
if resp.Body.RegistroRespuesta != nil {
if resp.Body.RegistroRespuesta.Estado == "Correcto" {
if s.hashStorage != nil {
if err := s.hashStorage.SaveLastRecord(lastRecord); err != nil {
return &AltaOutput{
Success: false,
Error: "hash_save_error",
}, nil
}
}
return &AltaOutput{
Success: true,
CSV: resp.Body.RegistroRespuesta.CSV,
Estado: resp.Body.RegistroRespuesta.Estado,
}, nil
}
return &AltaOutput{
Success: false,
Error: "aeat_error:" + resp.Body.RegistroRespuesta.Estado,
}, nil
}
}
saveLocal:
if s.hashStorage != nil {
if err := s.hashStorage.SaveLastRecord(lastRecord); err != nil {
return &AltaOutput{
Success: false,
Error: "hash_save_error",
}, nil
}
}
return &AltaOutput{
Success: true,
CSV: currentHash,
Estado: "Correcto (local)",
}, nil
}
type AnulacionInput struct {
InvoiceInput
EmisorNombre string
}
type AnulacionOutput struct {
Success bool `json:"success"`
CSV string `json:"csv,omitempty"`
Estado string `json:"estado,omitempty"`
Error string `json:"error,omitempty"`
}
func (s *FacturaService) ProcessAnulacion(input AnulacionInput) (*AnulacionOutput, error) {
if input.Factura.EmisorNIF == "" {
return &AnulacionOutput{
Success: false,
Error: "emisor_nif_required",
}, nil
}
if input.Factura.NumSerie == "" {
return &AnulacionOutput{
Success: false,
Error: "num_serie_required",
}, nil
}
if input.Factura.FechaExpedicion == "" {
return &AnulacionOutput{
Success: false,
Error: "fecha_expedicion_required",
}, nil
}
return &AnulacionOutput{
Success: true,
Estado: "Anulada",
}, nil
}
func ToAltaData(in *InvoiceInput, data *InvoiceData, huella, prevHash string) verifactu.AltaData {
ivaData := make([]verifactu.IVARegularizacionData, len(data.IVA))
for i, iva := range data.IVA {
ivaData[i] = verifactu.IVARegularizacionData{
Base: iva.Base,
Cuota: iva.Cuota,
Tipo: iva.Tipo,
ClaveRegimen: iva.ClaveRegimen,
Calificacion: iva.Calificacion,
}
}
destNombre := ""
destNIF := ""
if data.Destinatario != nil {
destNombre = data.Destinatario.Nombre
destNIF = data.Destinatario.NIF
}
return verifactu.AltaData{
EmisorNombre: in.Factura.EmisorNIF,
EmisorNIF: data.EmisorNIF,
NumSerie: data.NumSerie,
FechaExpedicion: data.Fecha,
TipoFactura: data.TipoFactura,
Descripcion: data.Descripcion,
DestinatarioNombre: destNombre,
DestinatarioNIF: destNIF,
IVA: ivaData,
CuotaTotal: data.CuotaTotal,
ImporteTotal: data.ImporteTotal,
Sistema: verifactu.SistemaData{
Nombre: data.Sistema.Nombre,
NIFProveedor: data.Sistema.NIFProveedor,
NombreSistema: data.Sistema.NombreSistema,
Version: data.Sistema.Version,
NumeroInstalacion: data.Sistema.NumeroInstalacion,
TipoUsoVerifactu: data.Sistema.TipoUsoVerifactu,
},
Huella: huella,
PrevHash: prevHash,
FechaGen: data.FechaGen,
}
}