package internal import ( "encoding/json" "fmt" "log" "time" "VerifactuMidAPI/internal/formats" "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 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(raw json.RawMessage) (*AltaOutput, error) { defer func() { if r := recover(); r != nil { log.Printf("PANIC: %v", r) } }() result, formatName, err := formats.TransformAuto(raw) if err != nil { return &AltaOutput{ Success: false, Error: "transform_error: " + err.Error(), }, nil } log.Printf("detected format: %s", formatName) return s.processInvoiceData(result) } func (s *FacturaService) processInvoiceData(result *formats.TransformResult) (*AltaOutput, error) { fecha, _ := time.Parse("02-01-2006", result.FechaExpedicion) ivaList := make([]IVARegularizacion, len(result.IVA)) for i, v := range result.IVA { ivaList[i] = IVARegularizacion{ Base: v.Base, Cuota: v.Cuota, Tipo: v.Tipo, ClaveRegimen: "01", Calificacion: "S1", } } cuotaTotal := 0.0 for _, v := range result.IVA { cuotaTotal += v.Cuota } var dest *Destinatario if result.Destinatario != nil { dest = &Destinatario{ Nombre: result.Destinatario.Nombre, NIF: result.Destinatario.NIF, } } data := &InvoiceData{ Tipo: "alta", EmisorNIF: result.EmisorNIF, NumSerie: result.NumSerie, Fecha: fecha, TipoFactura: result.TipoFactura, Descripcion: result.Descripcion, Destinatario: dest, IVA: ivaList, CuotaTotal: cuotaTotal, ImporteTotal: result.ImporteTotal, Sistema: Sistema{ Nombre: result.Sistema.Nombre, NIFProveedor: result.Sistema.NIFProveedor, NombreSistema: result.Sistema.Nombre, IDSistema: "01", Version: result.Sistema.Version, NumeroInstalacion: "1", TipoUsoVerifactu: "S", TipoUsoMultiOT: "N", IndicadorMultiOT: "N", }, FechaGen: time.Now(), } var prevHash, prevNumSerie string var prevFecha time.Time if s.hashStorage != nil { record, err := s.hashStorage.GetLastRecord(result.EmisorNIF) if err != nil { return &AltaOutput{ Success: false, Error: "hash_storage_error", }, nil } if record != nil { prevHash = record.Huella prevNumSerie = record.NumSerie prevFecha = record.Fecha } } 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 := verifactu.AltaData{ EmisorNombre: result.EmisorNombre, EmisorNIF: data.EmisorNIF, NumSerie: data.NumSerie, FechaExpedicion: data.Fecha, TipoFactura: data.TipoFactura, Descripcion: data.Descripcion, DestinatarioNombre: destNombre(data.Destinatario), DestinatarioNIF: destNIF(data.Destinatario), IVA: toIVAData(data.IVA), 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: currentHash, PrevHash: prevHash, PrevNumSerie: prevNumSerie, PrevFecha: prevFecha, FechaGen: data.FechaGen, } if s.verifactu != nil { log.Printf("Sending to AEAT...") resp, err := s.verifactu.SendAlta(altaData) if err != nil { log.Printf("AEAT error: %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.Respuesta != nil { estado := resp.Body.Respuesta.EstadoEnvio csv := resp.Body.Respuesta.CSV log.Printf("AEAT EstadoEnvio: %s CSV: %s", estado, csv) if 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: csv, Estado: "Correcto", }, nil } errMsg := estado if len(resp.Body.Respuesta.RespuestaLineas) > 0 { l := resp.Body.Respuesta.RespuestaLineas[0] errMsg = fmt.Sprintf("%s [%s] %s", estado, l.CodigoError, l.DescripcionError) } log.Printf("AEAT error: %s", errMsg) return &AltaOutput{ Success: false, Error: "aeat_error: " + errMsg, }, 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 } func destNombre(d *Destinatario) string { if d == nil { return "" } return d.Nombre } func destNIF(d *Destinatario) string { if d == nil { return "" } return d.NIF } func toIVAData(list []IVARegularizacion) []verifactu.IVARegularizacionData { out := make([]verifactu.IVARegularizacionData, len(list)) for i, v := range list { out[i] = verifactu.IVARegularizacionData{ Base: v.Base, Cuota: v.Cuota, Tipo: v.Tipo, ClaveRegimen: v.ClaveRegimen, Calificacion: v.Calificacion, } } return out } 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 }