diff --git a/app/Services/EDocument/Standards/Validation/VerifactuDocumentValidator.php b/app/Services/EDocument/Standards/Validation/VerifactuDocumentValidator.php
index 284767b459..8b6144adaf 100644
--- a/app/Services/EDocument/Standards/Validation/VerifactuDocumentValidator.php
+++ b/app/Services/EDocument/Standards/Validation/VerifactuDocumentValidator.php
@@ -104,16 +104,11 @@ class VerifactuDocumentValidator extends XsltDocumentValidator
$registroAlta = $xpath->query('//si:RegistroAlta | //sum1:RegistroAlta');
if ($registroAlta->length > 0) {
$tipoFactura = $xpath->query('.//si:TipoFactura | .//sum1:TipoFactura', $registroAlta->item(0));
- if ($tipoFactura->length > 0 && $tipoFactura->item(0)->textContent === 'R1') {
+ if ($tipoFactura->length > 0 && in_array($tipoFactura->item(0)->textContent,['R1','F3'])) {
return 'modification';
}
}
- // Check for RegistroModificacion structure (legacy)
- $registroModificacion = $xpath->query('//si:RegistroModificacion | //sum1:RegistroModificacion');
- if ($registroModificacion->length > 0) {
- return 'modification';
- }
// Check for cancellation structure
$registroAnulacion = $xpath->query('//si:RegistroAnulacion | //sum1:RegistroAnulacion');
@@ -192,7 +187,7 @@ class VerifactuDocumentValidator extends XsltDocumentValidator
if ($tipoFactura === false || $tipoFactura->length === 0) {
$tipoFactura = $xpath->query('.//sum1:TipoFactura', $registroAlta->item(0));
}
- if ($tipoFactura !== false && $tipoFactura->length > 0 && $tipoFactura->item(0)->textContent !== 'R1') {
+ if ($tipoFactura !== false && $tipoFactura->length > 0 && !in_array($tipoFactura->item(0)->textContent, ['R1','F3'])) {
$this->errors['structure'][] = "TipoFactura must be 'R1' for modifications, found: " . $tipoFactura->item(0)->textContent;
}
}
diff --git a/app/Services/EDocument/Standards/Verifactu.php b/app/Services/EDocument/Standards/Verifactu.php
index 7d4b9fe5fd..6f55a7561a 100644
--- a/app/Services/EDocument/Standards/Verifactu.php
+++ b/app/Services/EDocument/Standards/Verifactu.php
@@ -24,11 +24,9 @@ use App\Helpers\Invoice\InvoiceSumInclusive;
use App\Services\EDocument\Standards\Verifactu\AeatClient;
use App\Services\EDocument\Standards\Verifactu\RegistroAlta;
use App\Services\EDocument\Standards\Verifactu\Models\Desglose;
-use App\Services\EDocument\Standards\Verifactu\RegistroModificacion;
use App\Services\EDocument\Standards\Verifactu\Models\Encadenamiento;
use App\Services\EDocument\Standards\Verifactu\Models\RegistroAnterior;
use App\Services\EDocument\Standards\Verifactu\Models\SistemaInformatico;
-use App\Services\EDocument\Standards\Verifactu\Models\InvoiceModification;
use App\Services\EDocument\Standards\Verifactu\Models\PersonaFisicaJuridica;
use App\Services\EDocument\Standards\Verifactu\Models\Invoice as VerifactuInvoice;
@@ -62,8 +60,14 @@ class Verifactu extends AbstractService
$v_logs = $this->invoice->company->verifactu_logs;
- //determine the current status of the invoice.
- $document = (new RegistroAlta($this->invoice))->run()->getInvoice();
+ $i_logs = $this->invoice->verifactu_logs;
+
+ if($i_logs->count() >= 1){
+ $document = (new RegistroAlta($this->invoice))->run()->setRectification()->getInvoice();
+ }
+ else{
+ $document = (new RegistroAlta($this->invoice))->run()->getInvoice();
+ }
//keep this state for logging later on successful send
$this->_document = $document;
@@ -74,7 +78,6 @@ class Verifactu extends AbstractService
if($v_logs->count() >= 1){
$v_log = $v_logs->first();
$this->_previous_huella = $v_log->hash;
- // $document = InvoiceModification::createFromInvoice($document, $v_log->deserialize());
}
//3. cancelled => RegistroAnulacion
diff --git a/app/Services/EDocument/Standards/Verifactu/Models/Invoice.php b/app/Services/EDocument/Standards/Verifactu/Models/Invoice.php
index df110dff9f..2211997c09 100644
--- a/app/Services/EDocument/Standards/Verifactu/Models/Invoice.php
+++ b/app/Services/EDocument/Standards/Verifactu/Models/Invoice.php
@@ -21,6 +21,7 @@ class Invoice extends BaseXmlModel implements XmlModelInterface
// Constants for invoice types
public const TIPO_FACTURA_NORMAL = 'F1';
public const TIPO_FACTURA_RECTIFICATIVA = 'R1';
+ public const TIPO_FACTURA_SUSTITUIDA = 'F3';
// Constants for rectification types
public const TIPO_RECTIFICATIVA_COMPLETA = 'I'; // Rectificación por diferencias (Complete rectification)
@@ -36,7 +37,7 @@ class Invoice extends BaseXmlModel implements XmlModelInterface
protected ?string $tipoRectificativa = null;
protected ?array $facturasRectificadas = null;
protected ?array $facturasSustituidas = null;
- protected ?float $importeRectificacion = null;
+ protected ?array $importeRectificacion = null;
protected ?string $fechaOperacion = null;
protected string $descripcionOperacion;
protected ?string $facturaSimplificadaArt7273 = null;
@@ -233,21 +234,17 @@ class Invoice extends BaseXmlModel implements XmlModelInterface
return $this->importeRectificacion;
}
- public function setImporteRectificacion(?float $importeRectificacion): self
+ public function setImporteRectificacion(?array $importeRectificacion): self
{
- if ($importeRectificacion !== null) {
- // Validate that the amount is within reasonable bounds
- if (abs($importeRectificacion) > 999999999.99) {
- throw new \InvalidArgumentException('ImporteRectificacion must be between -999999999.99 and 999999999.99');
- }
- }
$this->importeRectificacion = $importeRectificacion;
+
return $this;
}
public function setRectificationAmounts(array $amounts): self
{
+
$this->importeRectificacion = $amounts;
return $this;
}
@@ -565,11 +562,11 @@ class Invoice extends BaseXmlModel implements XmlModelInterface
* Helper method to create a rectificative invoice with ImporteRectificacion
*
* @param string $tipoRectificativa The type of rectification ('I' for complete, 'S' for substitutive)
- * @param float $importeRectificacion The rectification amount
+ * @param array $importeRectificacion The rectification amount
* @param string $descripcionOperacion Description of the rectification operation
* @return self
*/
- public function makeRectificativeWithAmount(string $tipoRectificativa, float $importeRectificacion, string $descripcionOperacion = 'Rectificación de factura'): self
+ public function makeRectificativeWithAmount(string $tipoRectificativa, array $importeRectificacion, string $descripcionOperacion = 'Rectificación de factura'): self
{
$this->setTipoFactura(self::TIPO_FACTURA_RECTIFICATIVA)
->setTipoRectificativa($tipoRectificativa)
@@ -947,10 +944,10 @@ class Invoice extends BaseXmlModel implements XmlModelInterface
$root->appendChild($this->createElement($doc, 'TipoFactura', $this->tipoFactura));
// 5. TipoRectificativa (only for R1 invoices)
- if ($this->tipoFactura === self::TIPO_FACTURA_RECTIFICATIVA && $this->tipoRectificativa !== null) {
+ if (in_array($this->tipoFactura, [self::TIPO_FACTURA_SUSTITUIDA, self::TIPO_FACTURA_RECTIFICATIVA]) && $this->tipoRectificativa !== null) {
$root->appendChild($this->createElement($doc, 'TipoRectificativa', $this->tipoRectificativa));
}
-
+
// 6. FacturasRectificadas (only for R1 invoices)
if ($this->tipoFactura === self::TIPO_FACTURA_RECTIFICATIVA && $this->facturasRectificadas !== null) {
$facturasRectificadasElement = $this->createElement($doc, 'FacturasRectificadas');
@@ -973,8 +970,32 @@ class Invoice extends BaseXmlModel implements XmlModelInterface
$root->appendChild($facturasRectificadasElement);
}
+
+ if ($this->tipoFactura === self::TIPO_FACTURA_SUSTITUIDA && $this->facturasSustituidas !== null) {
+ $facturasSustituidasElement = $this->createElement($doc, 'FacturasSustituidas');
+
+ foreach ($this->facturasSustituidas as $facturaSustituidas) {
+ $idFacturaSustituidasElement = $this->createElement($doc, 'IDFacturaSustituida');
+
+ // Add IDEmisorFactura
+ $idFacturaSustituidasElement->appendChild($this->createElement($doc, 'IDEmisorFactura', $facturaSustituidas['IDEmisorFactura']));
+
+ // Add NumSerieFactura
+ $idFacturaSustituidasElement->appendChild($this->createElement($doc, 'NumSerieFactura', $facturaSustituidas['NumSerieFactura']));
+
+ // Add FechaExpedicionFactura
+ $idFacturaSustituidasElement->appendChild($this->createElement($doc, 'FechaExpedicionFactura', $facturaSustituidas['FechaExpedicionFactura']));
+
+ $facturasSustituidasElement->appendChild($idFacturaSustituidasElement);
+ }
+
+ $root->appendChild($facturasSustituidasElement);
+ }
+
+
+
// 7. ImporteRectificacion (only for R1 invoices with proper structure)
- if ($this->tipoFactura === self::TIPO_FACTURA_RECTIFICATIVA && $this->importeRectificacion !== null) {
+ if (in_array($this->tipoFactura, [self::TIPO_FACTURA_RECTIFICATIVA, self::TIPO_FACTURA_SUSTITUIDA]) && $this->importeRectificacion !== null) {
$importeRectificacionElement = $this->createElement($doc, 'ImporteRectificacion');
// Add BaseRectificada
@@ -1442,14 +1463,6 @@ class Invoice extends BaseXmlModel implements XmlModelInterface
return $node ? $node->nodeValue : null;
}
- /**
- * Create a modification from this invoice
- */
- public function createModification(Invoice $modifiedInvoice): InvoiceModification
- {
- return InvoiceModification::createFromInvoice($this, $modifiedInvoice);
- }
-
/**
* Create a cancellation record for this invoice
*/
@@ -1468,47 +1481,6 @@ class Invoice extends BaseXmlModel implements XmlModelInterface
return $cancellation;
}
- /**
- * Create a modification record from this invoice
- */
- public function createModificationRecord(): RegistroModificacion
- {
- $modificationRecord = new RegistroModificacion();
- $modificationRecord
- ->setIdVersion($this->getIdVersion())
- ->setIdFactura($this->getIdFactura())
- ->setRefExterna($this->getRefExterna())
- ->setNombreRazonEmisor($this->getNombreRazonEmisor())
- ->setSubsanacion($this->getSubsanacion())
- ->setRechazoPrevio($this->getRechazoPrevio())
- ->setTipoFactura($this->getTipoFactura())
- ->setTipoRectificativa($this->getTipoRectificativa())
- ->setFacturasRectificadas($this->getFacturasRectificadas())
- ->setFacturasSustituidas($this->getFacturasSustituidas())
- ->setImporteRectificacion($this->getImporteRectificacion())
- ->setFechaOperacion($this->getFechaOperacion())
- ->setDescripcionOperacion($this->getDescripcionOperacion())
- ->setFacturaSimplificadaArt7273($this->getFacturaSimplificadaArt7273())
- ->setFacturaSinIdentifDestinatarioArt61d($this->getFacturaSinIdentifDestinatarioArt61d())
- ->setMacrodato($this->getMacrodato())
- ->setEmitidaPorTerceroODestinatario($this->getEmitidaPorTerceroODestinatario())
- ->setTercero($this->getTercero())
- ->setDestinatarios($this->getDestinatarios())
- ->setCupon($this->getCupon())
- ->setDesglose($this->getDesglose())
- ->setCuotaTotal($this->getCuotaTotal())
- ->setImporteTotal($this->getImporteTotal())
- ->setEncadenamiento($this->getEncadenamiento())
- ->setSistemaInformatico($this->getSistemaInformatico())
- ->setFechaHoraHusoGenRegistro($this->getFechaHoraHusoGenRegistro())
- ->setNumRegistroAcuerdoFacturacion($this->getNumRegistroAcuerdoFacturacion())
- ->setIdAcuerdoSistemaInformatico($this->getIdAcuerdoSistemaInformatico())
- ->setTipoHuella($this->getTipoHuella())
- ->setHuella($this->getHuella());
-
- return $modificationRecord;
- }
-
public function serialize()
{
return serialize($this);
diff --git a/app/Services/EDocument/Standards/Verifactu/Models/InvoiceCancellation.php b/app/Services/EDocument/Standards/Verifactu/Models/InvoiceCancellation.php
deleted file mode 100644
index efa824ac16..0000000000
--- a/app/Services/EDocument/Standards/Verifactu/Models/InvoiceCancellation.php
+++ /dev/null
@@ -1,332 +0,0 @@
-setNumSerieFacturaEmisor($invoice->number);
- $cancellation->setFechaExpedicionFacturaEmisor(\Carbon\Carbon::parse($invoice->date)->format('d-m-Y'));
- $cancellation->setNifEmisor($invoice->company->settings->vat_number ?? 'B12345678');
- $cancellation->setHuellaFactura($huella);
-
- return $cancellation;
- }
-
- // Getters and Setters
- public function getIdVersion(): string
- {
- return $this->idVersion;
- }
-
- public function setIdVersion(string $idVersion): self
- {
- $this->idVersion = $idVersion;
- return $this;
- }
-
- public function getNumSerieFacturaEmisor(): string
- {
- return $this->numSerieFacturaEmisor;
- }
-
- public function setNumSerieFacturaEmisor(string $numSerieFacturaEmisor): self
- {
- $this->numSerieFacturaEmisor = $numSerieFacturaEmisor;
- return $this;
- }
-
- public function getFechaExpedicionFacturaEmisor(): string
- {
- return $this->fechaExpedicionFacturaEmisor;
- }
-
- public function setFechaExpedicionFacturaEmisor(string $fechaExpedicionFacturaEmisor): self
- {
- $this->fechaExpedicionFacturaEmisor = $fechaExpedicionFacturaEmisor;
- return $this;
- }
-
- public function getNifEmisor(): string
- {
- return $this->nifEmisor;
- }
-
- public function setNifEmisor(string $nifEmisor): self
- {
- $this->nifEmisor = $nifEmisor;
- return $this;
- }
-
- public function getHuellaFactura(): string
- {
- return $this->huellaFactura;
- }
-
- public function setHuellaFactura(string $huellaFactura): self
- {
- $this->huellaFactura = $huellaFactura;
- return $this;
- }
-
- public function getEstado(): string
- {
- return $this->estado;
- }
-
- public function setEstado(string $estado): self
- {
- $this->estado = $estado;
- return $this;
- }
-
- public function getDescripcionEstado(): string
- {
- return $this->descripcionEstado;
- }
-
- public function setDescripcionEstado(string $descripcionEstado): self
- {
- $this->descripcionEstado = $descripcionEstado;
- return $this;
- }
-
- /**
- * Generate the XML structure for the cancellation
- */
- public function toXml(\DOMDocument $doc): \DOMElement
- {
- // Create root element with proper namespaces
- $root = $doc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/suministro/FacturaLR.xsd', 'SuministroLRFacturas');
-
- // Add namespaces
- $root->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:ds', 'http://www.w3.org/2000/09/xmldsig#');
- $root->setAttribute('Version', $this->idVersion);
-
- // Create LRFacturaEntrada
- $lrFacturaEntrada = $doc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/suministro/FacturaLR.xsd', 'LRFacturaEntrada');
- $root->appendChild($lrFacturaEntrada);
-
- // Create IDFactura
- $idFactura = $doc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/suministro/FacturaLR.xsd', 'IDFactura');
- $lrFacturaEntrada->appendChild($idFactura);
-
- // Create IDEmisorFactura
- $idEmisorFactura = $doc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/suministro/FacturaLR.xsd', 'IDEmisorFactura');
- $idFactura->appendChild($idEmisorFactura);
-
- // Add NumSerieFacturaEmisor
- $idEmisorFactura->appendChild($doc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/suministro/FacturaLR.xsd', 'NumSerieFacturaEmisor', $this->numSerieFacturaEmisor));
-
- // Add FechaExpedicionFacturaEmisor
- $idEmisorFactura->appendChild($doc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/suministro/FacturaLR.xsd', 'FechaExpedicionFacturaEmisor', $this->fechaExpedicionFacturaEmisor));
-
- // Add NIFEmisor
- $idEmisorFactura->appendChild($doc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/suministro/FacturaLR.xsd', 'NIFEmisor', $this->nifEmisor));
-
- // Add HuellaFactura
- $idEmisorFactura->appendChild($doc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/suministro/FacturaLR.xsd', 'HuellaFactura', $this->huellaFactura));
-
- // Create EstadoFactura
- $estadoFactura = $doc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/suministro/FacturaLR.xsd', 'EstadoFactura');
- $lrFacturaEntrada->appendChild($estadoFactura);
-
- // Add Estado
- $estadoFactura->appendChild($doc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/suministro/FacturaLR.xsd', 'Estado', $this->estado));
-
- // Add DescripcionEstado
- $estadoFactura->appendChild($doc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/suministro/FacturaLR.xsd', 'DescripcionEstado', $this->descripcionEstado));
-
- return $root;
- }
-
- /**
- * Generate XML string
- */
- public function toXmlString(): string
- {
- $doc = new \DOMDocument('1.0', 'UTF-8');
- $doc->preserveWhiteSpace = false;
- $doc->formatOutput = true;
-
- $root = $this->toXml($doc);
- $doc->appendChild($root);
-
- return $doc->saveXML();
- }
-
- /**
- * Generate SOAP envelope for web service communication
- */
- public function toSoapEnvelope(): string
- {
- // Create the SOAP document
- $soapDoc = new \DOMDocument('1.0', 'UTF-8');
- $soapDoc->preserveWhiteSpace = false;
- $soapDoc->formatOutput = true;
-
- // Create SOAP envelope with namespaces
- $envelope = $soapDoc->createElementNS('http://schemas.xmlsoap.org/soap/envelope/', 'soapenv:Envelope');
- $envelope->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:soapenv', 'http://schemas.xmlsoap.org/soap/envelope/');
- $envelope->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:sum', 'https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd');
- $envelope->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:sum1', 'https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd');
-
- $soapDoc->appendChild($envelope);
-
- // Create Header
- $header = $soapDoc->createElementNS('http://schemas.xmlsoap.org/soap/envelope/', 'soapenv:Header');
- $envelope->appendChild($header);
-
- // Create Body
- $body = $soapDoc->createElementNS('http://schemas.xmlsoap.org/soap/envelope/', 'soapenv:Body');
- $envelope->appendChild($body);
-
- // Create RegFactuSistemaFacturacion
- $regFactu = $soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd', 'sum:RegFactuSistemaFacturacion');
- $body->appendChild($regFactu);
-
- // Create Cabecera
- $cabecera = $soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd', 'sum:Cabecera');
- $regFactu->appendChild($cabecera);
-
- // Create ObligadoEmision
- $obligadoEmision = $soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:ObligadoEmision');
- $cabecera->appendChild($obligadoEmision);
-
- // Add ObligadoEmision content (using default values for now)
- $obligadoEmision->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:NombreRazon', 'Test Company'));
- $obligadoEmision->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:NIF', $this->nifEmisor));
-
- // Create RegistroFactura
- $registroFactura = $soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd', 'sum:RegistroFactura');
- $regFactu->appendChild($registroFactura);
-
- // Import your existing XML into the RegistroFactura
- $yourXmlDoc = new \DOMDocument();
- $yourXmlDoc->loadXML($this->toXmlString());
-
- // Import the root element from your XML
- $importedNode = $soapDoc->importNode($yourXmlDoc->documentElement, true);
- $registroFactura->appendChild($importedNode);
-
- return $soapDoc->saveXML();
- }
-
- /**
- * Parse from DOM element
- */
- public static function fromDOMElement(\DOMElement $element): self
- {
- $cancellation = new self();
-
- // Parse IDVersion
- $idVersion = $element->getAttribute('Version');
- if ($idVersion) {
- $cancellation->setIdVersion($idVersion);
- }
-
- // Parse LRFacturaEntrada
- $lrFacturaEntrada = $element->getElementsByTagNameNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/suministro/FacturaLR.xsd', 'LRFacturaEntrada')->item(0);
- if ($lrFacturaEntrada) {
- // Parse IDFactura
- $idFactura = $lrFacturaEntrada->getElementsByTagNameNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/suministro/FacturaLR.xsd', 'IDFactura')->item(0);
- if ($idFactura) {
- $idEmisorFactura = $idFactura->getElementsByTagNameNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/suministro/FacturaLR.xsd', 'IDEmisorFactura')->item(0);
- if ($idEmisorFactura) {
- // Parse NumSerieFacturaEmisor
- $numSerie = $cancellation->getElementValue($idEmisorFactura, 'NumSerieFacturaEmisor', 'https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/suministro/FacturaLR.xsd');
- if ($numSerie) {
- $cancellation->setNumSerieFacturaEmisor($numSerie);
- }
-
- // Parse FechaExpedicionFacturaEmisor
- $fecha = $cancellation->getElementValue($idEmisorFactura, 'FechaExpedicionFacturaEmisor', 'https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/suministro/FacturaLR.xsd');
- if ($fecha) {
- $cancellation->setFechaExpedicionFacturaEmisor($fecha);
- }
-
- // Parse NIFEmisor
- $nif = $cancellation->getElementValue($idEmisorFactura, 'NIFEmisor', 'https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/suministro/FacturaLR.xsd');
- if ($nif) {
- $cancellation->setNifEmisor($nif);
- }
-
- // Parse HuellaFactura
- $huella = $cancellation->getElementValue($idEmisorFactura, 'HuellaFactura', 'https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/suministro/FacturaLR.xsd');
- if ($huella) {
- $cancellation->setHuellaFactura($huella);
- }
- }
- }
-
- // Parse EstadoFactura
- $estadoFactura = $lrFacturaEntrada->getElementsByTagNameNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/suministro/FacturaLR.xsd', 'EstadoFactura')->item(0);
- if ($estadoFactura) {
- // Parse Estado
- $estado = $cancellation->getElementValue($estadoFactura, 'Estado', 'https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/suministro/FacturaLR.xsd');
- if ($estado) {
- $cancellation->setEstado($estado);
- }
-
- // Parse DescripcionEstado
- $descripcion = $cancellation->getElementValue($estadoFactura, 'DescripcionEstado', 'https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/suministro/FacturaLR.xsd');
- if ($descripcion) {
- $cancellation->setDescripcionEstado($descripcion);
- }
- }
- }
-
- return $cancellation;
- }
-
-
-
- /**
- * Serialize for storage
- */
- public function serialize(): string
- {
- return serialize($this);
- }
-
- /**
- * Unserialize from storage
- */
- public static function unserialize(string $data): self
- {
- $object = unserialize($data);
-
- if (!$object instanceof self) {
- throw new \InvalidArgumentException('Invalid serialized data - not an InvoiceCancellation object');
- }
-
- return $object;
- }
-}
\ No newline at end of file
diff --git a/app/Services/EDocument/Standards/Verifactu/Models/InvoiceModification.php b/app/Services/EDocument/Standards/Verifactu/Models/InvoiceModification.php
deleted file mode 100644
index 43d0c9eac7..0000000000
--- a/app/Services/EDocument/Standards/Verifactu/Models/InvoiceModification.php
+++ /dev/null
@@ -1,376 +0,0 @@
- "Sustitución por otra factura", // Replacement by another invoice
- '2' => "Error en facturación", // Billing error
- '3' => "Anulación por devolución", // Cancellation due to return
- '4' => "Anulación por insolvencia" // Cancellation due to insolvency
- ];
-
- public function __construct()
- {
- $this->registroAnulacion = new RegistroAnulacion();
- $this->registroModificacion = new RegistroModificacion();
- $this->sistemaInformatico = new SistemaInformatico();
- }
-
- public function getRegistroAnulacion(): RegistroAnulacion
- {
- return $this->registroAnulacion;
- }
-
- public function setRegistroAnulacion(RegistroAnulacion $registroAnulacion): self
- {
- $this->registroAnulacion = $registroAnulacion;
- return $this;
- }
-
- public function getRegistroModificacion(): RegistroModificacion
- {
- return $this->registroModificacion;
- }
-
- public function setRegistroModificacion(RegistroModificacion $registroModificacion): self
- {
- $this->registroModificacion = $registroModificacion;
- return $this;
- }
-
- public function getSistemaInformatico(): SistemaInformatico
- {
- return $this->sistemaInformatico;
- }
-
- public function setSistemaInformatico(SistemaInformatico $sistemaInformatico): self
- {
- $this->sistemaInformatico = $sistemaInformatico;
- return $this;
- }
-
- public function setHuella(string $huella): self
- {
- $this->getRegistroModificacion()->setHuella($huella);
- return $this;
- }
-
- /**
- * Create a modification from an existing invoice
- */
- public static function createFromInvoice(Invoice $originalInvoice, Invoice $modifiedInvoice): self
- {
- $currentTimestamp = now()->format('Y-m-d\TH:i:sP');
-
- $modification = new self();
-
- // Set up cancellation record
- $cancellation = new RegistroAnulacion();
- $cancellation
- ->setIdEmisorFactura($originalInvoice->getTercero()?->getNif() ?? 'B12345678')
- ->setNumSerieFactura($originalInvoice->getIdFactura()->getNumSerieFactura())
- ->setFechaExpedicionFactura($originalInvoice->getFechaExpedicionFactura())
- ->setMotivoAnulacion('1'); // Sustitución por otra factura
-
- $modification->setRegistroAnulacion($cancellation);
-
- // Set up modification record
- $modificationRecord = new RegistroModificacion();
- $modificationRecord
- ->setIdVersion($modifiedInvoice->getIdVersion())
- ->setIdFactura($modifiedInvoice->getIdFactura())
- ->setRefExterna($modifiedInvoice->getRefExterna())
- ->setNombreRazonEmisor($modifiedInvoice->getNombreRazonEmisor())
- ->setSubsanacion($modifiedInvoice->getSubsanacion())
- ->setRechazoPrevio($modifiedInvoice->getRechazoPrevio())
- ->setTipoFactura('R1') // always R1 for rectification
- ->setTipoRectificativa('S') // always S for rectification
- ->setFacturasRectificadas($modifiedInvoice->getFacturasRectificadas())
- ->setFacturasSustituidas($modifiedInvoice->getFacturasSustituidas())
- ->setImporteRectificacion($modifiedInvoice->getImporteRectificacion())
- ->setFechaOperacion($modifiedInvoice->getFechaOperacion())
- ->setDescripcionOperacion($modifiedInvoice->getDescripcionOperacion())
- ->setFacturaSimplificadaArt7273($modifiedInvoice->getFacturaSimplificadaArt7273())
- ->setFacturaSinIdentifDestinatarioArt61d($modifiedInvoice->getFacturaSinIdentifDestinatarioArt61d())
- ->setMacrodato($modifiedInvoice->getMacrodato())
- ->setEmitidaPorTerceroODestinatario($modifiedInvoice->getEmitidaPorTerceroODestinatario())
- ->setTercero($modifiedInvoice->getTercero())
- ->setDestinatarios($modifiedInvoice->getDestinatarios())
- ->setCupon($modifiedInvoice->getCupon())
- ->setDesglose($modifiedInvoice->getDesglose())
- ->setCuotaTotal($modifiedInvoice->getCuotaTotal())
- ->setImporteTotal($modifiedInvoice->getImporteTotal())
- ->setEncadenamiento($modifiedInvoice->getEncadenamiento())
- ->setSistemaInformatico($modifiedInvoice->getSistemaInformatico())
- ->setFechaHoraHusoGenRegistro($currentTimestamp)
- ->setNumRegistroAcuerdoFacturacion($modifiedInvoice->getNumRegistroAcuerdoFacturacion())
- ->setIdAcuerdoSistemaInformatico($modifiedInvoice->getIdAcuerdoSistemaInformatico())
- ->setTipoHuella($modifiedInvoice->getTipoHuella())
- ->setHuella('PLACEHOLDER_HUELLA');
-
- $modification->setRegistroModificacion($modificationRecord);
-
- // Set up sistema informatico for the modification (only if not null)
- if ($modifiedInvoice->getSistemaInformatico()) {
- $modification->setSistemaInformatico($modifiedInvoice->getSistemaInformatico());
- }
-
- return $modification;
- }
-
- public function toSoapEnvelope(): string
- {
- // Create the SOAP document
- $soapDoc = new \DOMDocument('1.0', 'UTF-8');
- $soapDoc->preserveWhiteSpace = false;
- $soapDoc->formatOutput = true;
-
- // Create SOAP envelope with namespaces
- $envelope = $soapDoc->createElementNS('http://schemas.xmlsoap.org/soap/envelope/', 'soapenv:Envelope');
- $envelope->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:soapenv', 'http://schemas.xmlsoap.org/soap/envelope/');
- $envelope->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:sum', 'https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd');
- $envelope->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:sum1', 'https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd');
-
- $soapDoc->appendChild($envelope);
-
- // Create Header
- $header = $soapDoc->createElementNS('http://schemas.xmlsoap.org/soap/envelope/', 'soapenv:Header');
- $envelope->appendChild($header);
-
- // Create Body
- $body = $soapDoc->createElementNS('http://schemas.xmlsoap.org/soap/envelope/', 'soapenv:Body');
- $envelope->appendChild($body);
-
- // Create RegFactuSistemaFacturacion
- $regFactu = $soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd', 'sum:RegFactuSistemaFacturacion');
- $body->appendChild($regFactu);
-
- // Create Cabecera
- $cabecera = $soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd', 'sum:Cabecera');
- $regFactu->appendChild($cabecera);
-
- // Create ObligadoEmision
- $obligadoEmision = $soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:ObligadoEmision');
- $cabecera->appendChild($obligadoEmision);
-
- // Add ObligadoEmision content
- if ($this->sistemaInformatico) {
- $obligadoEmision->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:NombreRazon', $this->sistemaInformatico->getNombreRazon()));
- $obligadoEmision->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:NIF', $this->sistemaInformatico->getNif()));
- } else {
- // Default values if no sistema informatico is available
- $obligadoEmision->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:NombreRazon', 'CERTIFICADO FISICA PRUEBAS'));
- $obligadoEmision->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:NIF', 'A39200019'));
- }
-
- // Create RegistroFactura
- $registroFactura = $soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd', 'sum:RegistroFactura');
- $regFactu->appendChild($registroFactura);
-
- // Create RegistroAlta
- $registroAlta = $soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:RegistroAlta');
- $registroFactura->appendChild($registroAlta);
-
- // Add IDVersion inside RegistroAlta
- $registroAlta->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:IDVersion', '1.0'));
-
- // Create IDFactura
- $idFactura = $soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:IDFactura');
- $registroAlta->appendChild($idFactura);
-
- // Add IDFactura child elements
- $idFactura->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:IDEmisorFactura', $this->registroModificacion->getIdFactura()->getIdEmisorFactura()));
- $idFactura->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:NumSerieFactura', $this->registroModificacion->getIdFactura()->getNumSerieFactura()));
- $idFactura->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:FechaExpedicionFactura', $this->registroModificacion->getIdFactura()->getFechaExpedicionFactura()));
-
- // Add NombreRazonEmisor
- $registroAlta->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:NombreRazonEmisor', $this->registroModificacion->getNombreRazonEmisor()));
-
- // Add TipoFactura (R1 for rectificativa)
- $registroAlta->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:TipoFactura', 'R1'));
-
- // Add TipoRectificativa for R1 invoices (S for sustitutiva)
- if ($this->registroModificacion->getTipoFactura() === 'R1') {
- $registroAlta->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:TipoRectificativa', 'S'));
- }
-
- // Add DescripcionOperacion
- $registroAlta->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:DescripcionOperacion', $this->registroModificacion->getDescripcionOperacion()));
-
- // Create ModificacionFactura with correct namespace
- $modificacionFactura = $soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:ModificacionFactura');
- $registroAlta->appendChild($modificacionFactura);
-
- // Add TipoRectificativa (S for sustitutiva)
- $modificacionFactura->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:TipoRectificativa', 'S'));
-
- // Create FacturasRectificadas
- $facturasRectificadas = $soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:FacturasRectificadas');
- $modificacionFactura->appendChild($facturasRectificadas);
-
- // Add Factura (the original invoice being rectified)
- $factura = $soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:Factura');
- $facturasRectificadas->appendChild($factura);
-
- // Add original invoice details
- $factura->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:NumSerieFacturaEmisor', $this->registroAnulacion->getNumSerieFactura()));
- $factura->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:FechaExpedicionFacturaEmisor', $this->registroAnulacion->getFechaExpedicionFactura()));
-
- // Create Desglose
- $desglose = $soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:Desglose');
- $registroAlta->appendChild($desglose);
-
- // Create DetalleDesglose
- $detalleDesglose = $soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:DetalleDesglose');
- $desglose->appendChild($detalleDesglose);
-
- // Add DetalleDesglose child elements
- $detalleDesglose->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:ClaveRegimen', '01'));
- $detalleDesglose->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:CalificacionOperacion', 'S1'));
- $detalleDesglose->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:TipoImpositivo', '21'));
- $detalleDesglose->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:BaseImponibleOimporteNoSujeto', '200.00'));
- $detalleDesglose->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:CuotaRepercutida', $this->registroModificacion->getCuotaTotal()));
-
- // Add ImporteTotal
- $registroAlta->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:ImporteTotal', $this->registroModificacion->getImporteTotal()));
-
- return $soapDoc->saveXML();
- }
-
- public function toXmlString(): string
- {
- $doc = new \DOMDocument('1.0', 'UTF-8');
- $doc->preserveWhiteSpace = false;
- $doc->formatOutput = true;
-
- // Create ModificacionFactura root
- $root = $doc->createElementNS(self::XML_NAMESPACE, self::XML_NAMESPACE_PREFIX . ':ModificacionFactura');
- $doc->appendChild($root);
-
- // Add RegistroAnulacion
- $registroAnulacionElement = $this->registroAnulacion->toXml($doc);
- $root->appendChild($registroAnulacionElement);
-
- // Add RegistroModificacion
- $registroModificacionElement = $this->registroModificacion->toXml($doc);
- $root->appendChild($registroModificacionElement);
-
- return $doc->saveXML();
- }
-
- public function toXml(\DOMDocument $doc): \DOMElement
- {
- // Create ModificacionFactura root
- $root = $doc->createElementNS(self::XML_NAMESPACE, self::XML_NAMESPACE_PREFIX . ':ModificacionFactura');
-
- // Add RegistroAnulacion
- $registroAnulacionElement = $this->registroAnulacion->toXml($doc);
- $root->appendChild($registroAnulacionElement);
-
- // Add RegistroModificacion
- $registroModificacionElement = $this->registroModificacion->toXml($doc);
- $root->appendChild($registroModificacionElement);
-
- return $root;
- }
-
- public static function fromDOMElement(\DOMElement $element): self
- {
- $modification = new self();
-
- // Handle RegistroAnulacion
- $registroAnulacionElement = $element->getElementsByTagNameNS(self::XML_NAMESPACE, 'RegistroAnulacion')->item(0);
- if ($registroAnulacionElement) {
- $registroAnulacion = RegistroAnulacion::fromDOMElement($registroAnulacionElement);
- $modification->setRegistroAnulacion($registroAnulacion);
- }
-
- // Handle RegistroModificacion
- $registroModificacionElement = $element->getElementsByTagNameNS(self::XML_NAMESPACE, 'RegistroModificacion')->item(0);
- if ($registroModificacionElement) {
- $registroModificacion = RegistroModificacion::fromDOMElement($registroModificacionElement);
- $modification->setRegistroModificacion($registroModificacion);
- }
-
- return $modification;
- }
-
- /**
- * Create a proper RegistroAlta structure from the RegistroModificacion data
- */
- // private function createRegistroAltaFromModificacion(\DOMDocument $doc): \DOMElement
- // {
- // $registroAlta = $doc->createElementNS(self::XML_NAMESPACE, self::XML_NAMESPACE_PREFIX . ':RegistroAlta');
-
- // // Add IDVersion
- // $registroAlta->appendChild($this->createElement($doc, 'IDVersion', $this->registroModificacion->getIdVersion()));
-
- // // Create IDFactura structure
- // $idFactura = $doc->createElementNS(self::XML_NAMESPACE, self::XML_NAMESPACE_PREFIX . ':IDFactura');
- // $idFactura->appendChild($this->createElement($doc, 'IDEmisorFactura', $this->registroModificacion->getTercero()?->getNif() ?? 'B12345678'));
- // $idFactura->appendChild($this->createElement($doc, 'NumSerieFactura', $this->registroModificacion->getIdFactura()));
- // $idFactura->appendChild($this->createElement($doc, 'FechaExpedicionFactura', '2025-01-01'));
- // $registroAlta->appendChild($idFactura);
-
- // // Add other required elements
- // if ($this->registroModificacion->getRefExterna()) {
- // $registroAlta->appendChild($this->createElement($doc, 'RefExterna', $this->registroModificacion->getRefExterna()));
- // }
-
- // $registroAlta->appendChild($this->createElement($doc, 'NombreRazonEmisor', $this->registroModificacion->getNombreRazonEmisor()));
- // $registroAlta->appendChild($this->createElement($doc, 'TipoFactura', $this->registroModificacion->getTipoFactura()));
- // $registroAlta->appendChild($this->createElement($doc, 'DescripcionOperacion', $this->registroModificacion->getDescripcionOperacion()));
-
- // // Add Desglose
- // $desglose = $doc->createElementNS(self::XML_NAMESPACE, self::XML_NAMESPACE_PREFIX . ':Desglose');
- // $desgloseFactura = $doc->createElementNS(self::XML_NAMESPACE, self::XML_NAMESPACE_PREFIX . ':DesgloseFactura');
- // $desgloseFactura->appendChild($this->createElement($doc, 'Impuesto', '01'));
- // $desgloseFactura->appendChild($this->createElement($doc, 'ClaveRegimen', '01'));
- // $desgloseFactura->appendChild($this->createElement($doc, 'CalificacionOperacion', 'S1'));
- // $desgloseFactura->appendChild($this->createElement($doc, 'TipoImpositivo', '21'));
- // $desgloseFactura->appendChild($this->createElement($doc, 'BaseImponibleOimporteNoSujeto', '100.00'));
- // $desgloseFactura->appendChild($this->createElement($doc, 'CuotaRepercutida', '21.00'));
- // $desglose->appendChild($desgloseFactura);
- // $registroAlta->appendChild($desglose);
-
- // $registroAlta->appendChild($this->createElement($doc, 'CuotaTotal', $this->registroModificacion->getCuotaTotal()));
- // $registroAlta->appendChild($this->createElement($doc, 'ImporteTotal', $this->registroModificacion->getImporteTotal()));
-
- // // Add Encadenamiento
- // $encadenamiento = $doc->createElementNS(self::XML_NAMESPACE, self::XML_NAMESPACE_PREFIX . ':Encadenamiento');
- // $encadenamiento->appendChild($this->createElement($doc, 'PrimerRegistro', 'S'));
- // $registroAlta->appendChild($encadenamiento);
-
- // // Add SistemaInformatico
- // $sistemaInformatico = $doc->createElementNS(self::XML_NAMESPACE, self::XML_NAMESPACE_PREFIX . ':SistemaInformatico');
- // $sistemaInformatico->appendChild($this->createElement($doc, 'NombreRazon', 'Test System'));
- // $sistemaInformatico->appendChild($this->createElement($doc, 'NIF', 'B12345678'));
- // $sistemaInformatico->appendChild($this->createElement($doc, 'NombreSistemaInformatico', 'Test Software'));
- // $sistemaInformatico->appendChild($this->createElement($doc, 'IdSistemaInformatico', '01'));
- // $sistemaInformatico->appendChild($this->createElement($doc, 'Version', '1.0'));
- // $sistemaInformatico->appendChild($this->createElement($doc, 'NumeroInstalacion', '001'));
- // $sistemaInformatico->appendChild($this->createElement($doc, 'TipoUsoPosibleSoloVerifactu', 'S'));
- // $sistemaInformatico->appendChild($this->createElement($doc, 'TipoUsoPosibleMultiOT', 'S'));
- // $sistemaInformatico->appendChild($this->createElement($doc, 'IndicadorMultiplesOT', 'S'));
- // $registroAlta->appendChild($sistemaInformatico);
-
- // $registroAlta->appendChild($this->createElement($doc, 'FechaHoraHusoGenRegistro', $this->registroModificacion->getFechaHoraHusoGenRegistro()));
- // $registroAlta->appendChild($this->createElement($doc, 'TipoHuella', $this->registroModificacion->getTipoHuella()));
- // $registroAlta->appendChild($this->createElement($doc, 'Huella', $this->registroModificacion->getHuella()));
-
- // return $registroAlta;
- // }
-}
diff --git a/app/Services/EDocument/Standards/Verifactu/Models/RegistroModificacion.php b/app/Services/EDocument/Standards/Verifactu/Models/RegistroModificacion.php
deleted file mode 100644
index 384a63b77a..0000000000
--- a/app/Services/EDocument/Standards/Verifactu/Models/RegistroModificacion.php
+++ /dev/null
@@ -1,679 +0,0 @@
-desglose = new Desglose();
- $this->encadenamiento = new Encadenamiento();
- $this->sistemaInformatico = new SistemaInformatico();
- $this->tipoFactura = 'F1'; // Default to normal invoice
- }
-
- // Getters and setters - same as Invoice model
- public function getIdVersion(): string
- {
- return $this->idVersion;
- }
-
- public function setIdVersion(string $idVersion): self
- {
- $this->idVersion = $idVersion;
- return $this;
- }
-
- public function getFechaExpedicionFactura(): string
- {
- return $this->fechaExpedicionFactura ?? now()->format('d-m-Y');
- }
-
- public function setFechaExpedicionFactura(string $fechaExpedicionFactura): self
- {
- $this->fechaExpedicionFactura = $fechaExpedicionFactura;
- return $this;
- }
-
- public function getIdFactura(): IDFactura
- {
- return $this->idFactura;
- }
-
- public function setIdFactura(IDFactura $idFactura): self
- {
- $this->idFactura = $idFactura;
- return $this;
- }
-
- public function getRefExterna(): ?string
- {
- return $this->refExterna;
- }
-
- public function setRefExterna(?string $refExterna): self
- {
- $this->refExterna = $refExterna;
- return $this;
- }
-
- public function getNombreRazonEmisor(): string
- {
- return $this->nombreRazonEmisor;
- }
-
- public function setNombreRazonEmisor(string $nombreRazonEmisor): self
- {
- $this->nombreRazonEmisor = $nombreRazonEmisor;
- return $this;
- }
-
- public function getSubsanacion(): ?string
- {
- return $this->subsanacion;
- }
-
- public function setSubsanacion(?string $subsanacion): self
- {
- $this->subsanacion = $subsanacion;
- return $this;
- }
-
- public function getRechazoPrevio(): ?string
- {
- return $this->rechazoPrevio;
- }
-
- public function setRechazoPrevio(?string $rechazoPrevio): self
- {
- $this->rechazoPrevio = $rechazoPrevio;
- return $this;
- }
-
- public function getTipoFactura(): string
- {
- return $this->tipoFactura;
- }
-
- public function setTipoFactura(string $tipoFactura): self
- {
- $this->tipoFactura = $tipoFactura;
- return $this;
- }
-
- public function getTipoRectificativa(): ?string
- {
- return $this->tipoRectificativa;
- }
-
- public function setTipoRectificativa(?string $tipoRectificativa): self
- {
- $this->tipoRectificativa = $tipoRectificativa;
- return $this;
- }
-
- public function getFacturasRectificadas(): ?array
- {
- return $this->facturasRectificadas;
- }
-
- public function setFacturasRectificadas(?array $facturasRectificadas): self
- {
- $this->facturasRectificadas = $facturasRectificadas;
- return $this;
- }
-
- public function getFacturasSustituidas(): ?array
- {
- return $this->facturasSustituidas;
- }
-
- public function setFacturasSustituidas(?array $facturasSustituidas): self
- {
- $this->facturasSustituidas = $facturasSustituidas;
- return $this;
- }
-
- public function getImporteRectificacion(): ?float
- {
- return $this->importeRectificacion;
- }
-
- public function setImporteRectificacion(?float $importeRectificacion): self
- {
- $this->importeRectificacion = $importeRectificacion;
- return $this;
- }
-
- public function getFechaOperacion(): ?string
- {
- return $this->fechaOperacion;
- }
-
- public function setFechaOperacion(?string $fechaOperacion): self
- {
- $this->fechaOperacion = $fechaOperacion;
- return $this;
- }
-
- public function getDescripcionOperacion(): string
- {
- return $this->descripcionOperacion;
- }
-
- public function setDescripcionOperacion(string $descripcionOperacion): self
- {
- $this->descripcionOperacion = $descripcionOperacion;
- return $this;
- }
-
- public function getFacturaSimplificadaArt7273(): ?string
- {
- return $this->facturaSimplificadaArt7273;
- }
-
- public function setFacturaSimplificadaArt7273(?string $facturaSimplificadaArt7273): self
- {
- $this->facturaSimplificadaArt7273 = $facturaSimplificadaArt7273;
- return $this;
- }
-
- public function getFacturaSinIdentifDestinatarioArt61d(): ?string
- {
- return $this->facturaSinIdentifDestinatarioArt61d;
- }
-
- public function setFacturaSinIdentifDestinatarioArt61d(?string $facturaSinIdentifDestinatarioArt61d): self
- {
- $this->facturaSinIdentifDestinatarioArt61d = $facturaSinIdentifDestinatarioArt61d;
- return $this;
- }
-
- public function getMacrodato(): ?string
- {
- return $this->macrodato;
- }
-
- public function setMacrodato(?string $macrodato): self
- {
- $this->macrodato = $macrodato;
- return $this;
- }
-
- public function getEmitidaPorTerceroODestinatario(): ?string
- {
- return $this->emitidaPorTerceroODestinatario;
- }
-
- public function setEmitidaPorTerceroODestinatario(?string $emitidaPorTerceroODestinatario): self
- {
- $this->emitidaPorTerceroODestinatario = $emitidaPorTerceroODestinatario;
- return $this;
- }
-
- public function getTercero(): ?PersonaFisicaJuridica
- {
- return $this->tercero;
- }
-
- public function setTercero(?PersonaFisicaJuridica $tercero): self
- {
- $this->tercero = $tercero;
- return $this;
- }
-
- public function getDestinatarios(): ?array
- {
- return $this->destinatarios;
- }
-
- public function setDestinatarios(?array $destinatarios): self
- {
- $this->destinatarios = $destinatarios;
- return $this;
- }
-
- public function getCupon(): ?string
- {
- return $this->cupon;
- }
-
- public function setCupon(?string $cupon): self
- {
- $this->cupon = $cupon;
- return $this;
- }
-
- public function getDesglose(): Desglose
- {
- return $this->desglose;
- }
-
- public function setDesglose(Desglose $desglose): self
- {
- $this->desglose = $desglose;
- return $this;
- }
-
- public function getCuotaTotal(): float
- {
- return $this->cuotaTotal;
- }
-
- public function setCuotaTotal(float $cuotaTotal): self
- {
- $this->cuotaTotal = $cuotaTotal;
- return $this;
- }
-
- public function getImporteTotal(): float
- {
- return $this->importeTotal;
- }
-
- public function setImporteTotal($importeTotal): self
- {
- if (!is_numeric($importeTotal)) {
- throw new \InvalidArgumentException('ImporteTotal must be a numeric value');
- }
-
- $formatted = number_format((float)$importeTotal, 2, '.', '');
- if (!preg_match('/^(\+|-)?\d{1,12}(\.\d{0,2})?$/', $formatted)) {
- throw new \InvalidArgumentException('ImporteTotal must be a number with up to 12 digits and 2 decimal places');
- }
-
- $this->importeTotal = (float)$importeTotal;
- return $this;
- }
-
- public function getEncadenamiento(): Encadenamiento
- {
- return $this->encadenamiento;
- }
-
- public function setEncadenamiento(Encadenamiento $encadenamiento): self
- {
- $this->encadenamiento = $encadenamiento;
- return $this;
- }
-
- public function getSistemaInformatico(): SistemaInformatico
- {
- return $this->sistemaInformatico;
- }
-
- public function setSistemaInformatico(SistemaInformatico $sistemaInformatico): self
- {
- $this->sistemaInformatico = $sistemaInformatico;
- return $this;
- }
-
- public function getFechaHoraHusoGenRegistro(): string
- {
- return $this->fechaHoraHusoGenRegistro;
- }
-
- public function setFechaHoraHusoGenRegistro(string $fechaHoraHusoGenRegistro): self
- {
- $this->fechaHoraHusoGenRegistro = $fechaHoraHusoGenRegistro;
- return $this;
- }
-
- public function getNumRegistroAcuerdoFacturacion(): ?string
- {
- return $this->numRegistroAcuerdoFacturacion;
- }
-
- public function setNumRegistroAcuerdoFacturacion(?string $numRegistroAcuerdoFacturacion): self
- {
- $this->numRegistroAcuerdoFacturacion = $numRegistroAcuerdoFacturacion;
- return $this;
- }
-
- public function getIdAcuerdoSistemaInformatico(): ?string
- {
- return $this->idAcuerdoSistemaInformatico;
- }
-
- public function setIdAcuerdoSistemaInformatico(?string $idAcuerdoSistemaInformatico): self
- {
- $this->idAcuerdoSistemaInformatico = $idAcuerdoSistemaInformatico;
- return $this;
- }
-
- public function getTipoHuella(): string
- {
- return $this->tipoHuella;
- }
-
- public function setTipoHuella(string $tipoHuella): self
- {
- $this->tipoHuella = $tipoHuella;
- return $this;
- }
-
- public function getHuella(): string
- {
- return $this->huella;
- }
-
- public function setHuella(string $huella): self
- {
- $this->huella = $huella;
- return $this;
- }
-
- public function getSignature(): ?string
- {
- return $this->signature;
- }
-
- public function setSignature(?string $signature): self
- {
- $this->signature = $signature;
- return $this;
- }
-
- public function getFacturaRectificativa(): ?FacturaRectificativa
- {
- return $this->facturaRectificativa;
- }
-
- public function setFacturaRectificativa(FacturaRectificativa $facturaRectificativa): void
- {
- $this->facturaRectificativa = $facturaRectificativa;
- }
-
- public function setPrivateKeyPath(string $path): self
- {
- $this->privateKeyPath = $path;
- return $this;
- }
-
- public function setPublicKeyPath(string $path): self
- {
- $this->publicKeyPath = $path;
- return $this;
- }
-
- public function setCertificatePath(string $path): self
- {
- $this->certificatePath = $path;
- return $this;
- }
-
- public function toXml(\DOMDocument $doc): \DOMElement
- {
- // Create root element with proper namespaces
- $root = $doc->createElementNS(self::XML_NAMESPACE, self::XML_NAMESPACE_PREFIX . ':RegistroModificacion');
-
- // Add namespaces
- $root->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:' . self::XML_NAMESPACE_PREFIX, self::XML_NAMESPACE);
- $root->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:' . self::XML_DS_NAMESPACE_PREFIX, self::XML_DS_NAMESPACE);
-
- // Add required elements in exact order according to schema
- $root->appendChild($this->createElement($doc, 'IDVersion', $this->idVersion));
-
- // Create IDFactura structure
- $idFactura = $this->createElement($doc, 'IDFactura');
- $idFactura->appendChild($this->createElement($doc, 'IDEmisorFactura', $this->tercero?->getNif() ?? 'B12345678'));
- $idFactura->appendChild($this->createElement($doc, 'NumSerieFactura', $this->idFactura->getNumSerieFactura()));
- $idFactura->appendChild($this->createElement($doc, 'FechaExpedicionFactura', $this->getFechaExpedicionFactura()));
- $root->appendChild($idFactura);
-
- if ($this->refExterna !== null) {
- $root->appendChild($this->createElement($doc, 'RefExterna', $this->refExterna));
- }
-
- $root->appendChild($this->createElement($doc, 'NombreRazonEmisor', $this->nombreRazonEmisor));
-
- if ($this->subsanacion !== null) {
- $root->appendChild($this->createElement($doc, 'Subsanacion', $this->subsanacion));
- }
-
- if ($this->rechazoPrevio !== null) {
- $root->appendChild($this->createElement($doc, 'RechazoPrevio', $this->rechazoPrevio));
- }
-
- // Create DatosFactura element
- $datosFactura = $this->createElement($doc, 'DatosFactura');
-
- // Add TipoFactura to DatosFactura
- $datosFactura->appendChild($this->createElement($doc, 'TipoFactura', $this->tipoFactura));
-
- if ($this->tipoFactura === 'R1' && $this->facturaRectificativa !== null) {
- $datosFactura->appendChild($this->createElement($doc, 'TipoRectificativa', $this->facturaRectificativa->getTipoRectificativa()));
- $facturasRectificadas = $this->createElement($doc, 'FacturasRectificadas');
- $facturasRectificadas->appendChild($this->facturaRectificativa->toXml($doc));
- $datosFactura->appendChild($facturasRectificadas);
- if ($this->importeRectificacion !== null) {
- $datosFactura->appendChild($this->createElement($doc, 'ImporteRectificacion', (string)$this->importeRectificacion));
- }
- }
-
- if ($this->fechaOperacion) {
- $datosFactura->appendChild($this->createElement($doc, 'FechaOperacion', date('d-m-Y', strtotime($this->fechaOperacion))));
- }
-
- $datosFactura->appendChild($this->createElement($doc, 'DescripcionOperacion', $this->descripcionOperacion));
-
- if ($this->cupon !== null) {
- $datosFactura->appendChild($this->createElement($doc, 'Cupon', $this->cupon));
- }
-
- if ($this->facturaSimplificadaArt7273 !== null) {
- $datosFactura->appendChild($this->createElement($doc, 'FacturaSimplificadaArt7273', $this->facturaSimplificadaArt7273));
- }
-
- if ($this->facturaSinIdentifDestinatarioArt61d !== null) {
- $datosFactura->appendChild($this->createElement($doc, 'FacturaSinIdentifDestinatarioArt61d', $this->facturaSinIdentifDestinatarioArt61d));
- }
-
- if ($this->macrodato !== null) {
- $datosFactura->appendChild($this->createElement($doc, 'Macrodato', $this->macrodato));
- }
-
- if ($this->emitidaPorTerceroODestinatario !== null) {
- $datosFactura->appendChild($this->createElement($doc, 'EmitidaPorTerceroODestinatario', $this->emitidaPorTerceroODestinatario));
- }
-
- if ($this->tercero !== null) {
- $datosFactura->appendChild($this->tercero->toXml($doc));
- }
-
- if ($this->destinatarios !== null && count($this->destinatarios) > 0) {
- $destinatariosElement = $this->createElement($doc, 'Destinatarios');
- foreach ($this->destinatarios as $destinatario) {
- $idDestinatarioElement = $this->createElement($doc, 'IDDestinatario');
-
- // Add NombreRazon
- $idDestinatarioElement->appendChild($this->createElement($doc, 'NombreRazon', $destinatario->getNombreRazon()));
-
- // Add either NIF or IDOtro
- if ($destinatario->getNif() !== null) {
- $idDestinatarioElement->appendChild($this->createElement($doc, 'NIF', $destinatario->getNif()));
- } else {
- $idOtroElement = $this->createElement($doc, 'IDOtro');
- $idOtroElement->appendChild($this->createElement($doc, 'CodigoPais', $destinatario->getPais()));
- $idOtroElement->appendChild($this->createElement($doc, 'IDType', $destinatario->getTipoIdentificacion()));
- $idOtroElement->appendChild($this->createElement($doc, 'ID', $destinatario->getIdOtro()));
- $idDestinatarioElement->appendChild($idOtroElement);
- }
-
- $destinatariosElement->appendChild($idDestinatarioElement);
- }
- $datosFactura->appendChild($destinatariosElement);
- }
-
- // Add Desglose to DatosFactura
- if ($this->desglose) {
- $desgloseElement = $this->desglose->toXml($doc);
- $datosFactura->appendChild($desgloseElement);
- }
-
- // Add CuotaTotal to DatosFactura
- $datosFactura->appendChild($this->createElement($doc, 'CuotaTotal', (string)$this->cuotaTotal));
-
- // Add ImporteTotal to DatosFactura
- $datosFactura->appendChild($this->createElement($doc, 'ImporteTotal', (string)$this->importeTotal));
-
- // Add Encadenamiento to DatosFactura
- if ($this->encadenamiento) {
- $encadenamientoElement = $this->encadenamiento->toXml($doc);
- $datosFactura->appendChild($encadenamientoElement);
- }
-
- // Add SistemaInformatico to DatosFactura
- if ($this->sistemaInformatico) {
- $sistemaInformaticoElement = $this->sistemaInformatico->toXml($doc);
- $datosFactura->appendChild($sistemaInformaticoElement);
- }
-
- // Add FechaHoraHusoGenRegistro to DatosFactura
- $datosFactura->appendChild($this->createElement($doc, 'FechaHoraHusoGenRegistro', $this->fechaHoraHusoGenRegistro));
-
- // Add TipoHuella and Huella to DatosFactura
- $datosFactura->appendChild($this->createElement($doc, 'TipoHuella', $this->tipoHuella));
- $datosFactura->appendChild($this->createElement($doc, 'Huella', $this->huella));
-
- // Add DatosFactura to root
- $root->appendChild($datosFactura);
-
- // Add optional Signature
- if ($this->signature !== null) {
- $root->appendChild($this->createDsElement($doc, 'Signature', $this->signature));
- }
-
- return $root;
- }
-
- public function toXmlString(): string
- {
- // Validate required fields first, outside of try-catch
- $requiredFields = [
- 'idVersion' => 'IDVersion',
- 'idFactura' => 'NumSerieFactura',
- 'nombreRazonEmisor' => 'NombreRazonEmisor',
- 'tipoFactura' => 'TipoFactura',
- 'descripcionOperacion' => 'DescripcionOperacion',
- 'cuotaTotal' => 'CuotaTotal',
- 'importeTotal' => 'ImporteTotal',
- 'fechaHoraHusoGenRegistro' => 'FechaHoraHusoGenRegistro',
- 'tipoHuella' => 'TipoHuella',
- 'huella' => 'Huella'
- ];
-
- foreach ($requiredFields as $property => $fieldName) {
- if (!isset($this->$property)) {
- throw new \InvalidArgumentException("Missing required field: $fieldName");
- }
- }
-
- // Enable user error handling for XML operations
- $previousErrorSetting = libxml_use_internal_errors(true);
- libxml_clear_errors();
-
- try {
- $doc = new \DOMDocument('1.0', 'UTF-8');
- $doc->preserveWhiteSpace = false;
- $doc->formatOutput = true;
-
- // Create root element using toXml method
- $root = $this->toXml($doc);
- $doc->appendChild($root);
-
- $xml = $doc->saveXML();
- if ($xml === false) {
- throw new \DOMException('Failed to generate XML');
- }
-
- return $xml;
- } catch (\ErrorException $e) {
- // Convert any libxml errors to DOMException
- $errors = libxml_get_errors();
- libxml_clear_errors();
- if (!empty($errors)) {
- throw new \DOMException($errors[0]->message);
- }
- throw new \DOMException($e->getMessage());
- } finally {
- // Restore previous error handling setting
- libxml_use_internal_errors($previousErrorSetting);
- libxml_clear_errors();
- }
- }
-
- public static function fromDOMElement(\DOMElement $element): self
- {
- $registroModificacion = new self();
-
- // Handle IDVersion
- $idVersion = $element->getElementsByTagNameNS(self::XML_NAMESPACE, 'IDVersion')->item(0);
- if ($idVersion) {
- $registroModificacion->setIdVersion($idVersion->nodeValue);
- }
-
- // Handle IDFactura
- $idFactura = $element->getElementsByTagNameNS(self::XML_NAMESPACE, 'IDFactura')->item(0);
- if ($idFactura) {
- $numSerieFactura = $idFactura->getElementsByTagNameNS(self::XML_NAMESPACE, 'NumSerieFactura')->item(0);
- if ($numSerieFactura) {
- $registroModificacion->setIdFactura($numSerieFactura->nodeValue);
- }
-
- $fechaExpedicionFactura = $idFactura->getElementsByTagNameNS(self::XML_NAMESPACE, 'FechaExpedicionFactura')->item(0);
- if ($fechaExpedicionFactura) {
- $registroModificacion->setFechaExpedicionFactura($fechaExpedicionFactura->nodeValue);
- }
- }
-
- // Handle other fields similar to Invoice model
- // ... (implement other field parsing as needed)
-
- return $registroModificacion;
- }
-}
\ No newline at end of file
diff --git a/app/Services/EDocument/Standards/Verifactu/RegistroAlta.php b/app/Services/EDocument/Standards/Verifactu/RegistroAlta.php
index 55c1421136..7df104dd8a 100644
--- a/app/Services/EDocument/Standards/Verifactu/RegistroAlta.php
+++ b/app/Services/EDocument/Standards/Verifactu/RegistroAlta.php
@@ -108,9 +108,6 @@ class RegistroAlta
$this->current_timestamp = now()->format('Y-m-d\TH:i:sP');
- // Determine if this is a rectification invoice
- $isRectification = $this->invoice->status_id === 5; // Assuming status_id 5 is for rectification
-
$this->v_invoice
->setIdVersion('1.0')
->setIdFactura((new IDFactura())
@@ -118,37 +115,14 @@ class RegistroAlta
->setNumSerieFactura($this->invoice->number)
->setFechaExpedicionFactura(\Carbon\Carbon::parse($this->invoice->date)->format('d-m-Y')))
->setNombreRazonEmisor($this->company->present()->name()) //company name
- ->setTipoFactura($isRectification ? 'R1' : 'F1') //invoice type
- ->setDescripcionOperacion($isRectification ? 'Rectificación por error en factura anterior' : 'Alta')// It IS! manadatory - max chars 500
+ ->setTipoFactura('F1') //invoice type
+ ->setDescripcionOperacion('Alta')// It IS! manadatory - max chars 500
->setCuotaTotal($this->invoice->total_taxes) //total taxes
->setImporteTotal($this->invoice->amount) //total invoice amount
->setFechaHoraHusoGenRegistro($this->current_timestamp) //creation/submission timestamp
->setTipoHuella('01') //sha256
->setHuella('PLACEHOLDER_HUELLA');
- // Set up rectification details if this is a rectification invoice
- if ($isRectification) {
- $this->v_invoice->setTipoRectificativa('S'); // S for substitutive rectification
-
- // Set up rectified invoice information
- $facturasRectificadas = [
- [
- 'IDEmisorFactura' => $this->company->settings->vat_number,
- 'NumSerieFactura' => $this->invoice->number,
- 'FechaExpedicionFactura' => \Carbon\Carbon::parse($this->invoice->date)->format('d-m-Y')
- ]
- ];
- $this->v_invoice->setFacturasRectificadas($facturasRectificadas);
-
- // Set up rectification amounts
- $importeRectificacion = [
- 'BaseRectificada' => $this->calc->getNetSubtotal(),
- 'CuotaRectificada' => $this->invoice->total_taxes,
- 'CuotaRecargoRectificado' => 0.00
- ];
- $this->v_invoice->setRectificationAmounts($importeRectificacion);
- }
-
/** The business entity that is issuing the invoice */
$emisor = new PersonaFisicaJuridica();
$emisor->setNif($this->company->settings->vat_number)
@@ -235,6 +209,34 @@ class RegistroAlta
return $this;
}
+ public function setRectification(): self
+ {
+
+ $this->v_invoice->setTipoFactura('R1');
+ $this->v_invoice->setTipoRectificativa('S'); // S for substitutive rectification
+
+ // Set up rectified invoice information
+ $facturasRectificadas = [
+ [
+ 'IDEmisorFactura' => $this->company->settings->vat_number,
+ 'NumSerieFactura' => $this->invoice->number,
+ 'FechaExpedicionFactura' => \Carbon\Carbon::parse($this->invoice->date)->format('d-m-Y')
+ ]
+ ];
+
+ $this->v_invoice->setFacturasRectificadas($facturasRectificadas);
+
+ // Set up rectification amounts
+ $importeRectificacion = [
+ 'BaseRectificada' => $this->calc->getNetSubtotal(),
+ 'CuotaRectificada' => $this->invoice->total_taxes,
+ 'CuotaRecargoRectificado' => 0.00
+ ];
+ $this->v_invoice->setRectificationAmounts($importeRectificacion);
+
+ return $this;
+ }
+
public function getInvoice(): VerifactuInvoice
{
return $this->v_invoice;
diff --git a/tests/Feature/EInvoice/Verifactu/InvoiceCancellationTest.php b/tests/Feature/EInvoice/Verifactu/InvoiceCancellationTest.php
deleted file mode 100644
index 3b6bb2a5ab..0000000000
--- a/tests/Feature/EInvoice/Verifactu/InvoiceCancellationTest.php
+++ /dev/null
@@ -1,443 +0,0 @@
-faker = Faker::create();
- }
-
- private function buildTestInvoice(): Invoice
- {
- $account = Account::factory()->create([
- 'hosted_client_count' => 1000,
- 'created_at' => now(),
- 'updated_at' => now(),
- ]);
-
- $company = Company::factory()->create([
- 'account_id' => $account->id,
- 'created_at' => now(),
- 'updated_at' => now(),
- ]);
-
- $company_settings = CompanySettings::defaults();
- $company_settings->currency_id = '3';
- $company_settings->country_id = '724';
- $company_settings->vat_number = $this->test_company_nif;
-
- $company->settings = $company_settings;
- $company->save();
-
- $this->company = $company;
-
- $user = User::factory()->create([
- 'account_id' => $account->id,
- 'email' => $this->faker->unique()->safeEmail(),
- 'confirmation_code' => $this->faker->unique()->uuid(),
- ]);
-
- $this->user = $user;
-
- $user->companies()->attach($company->id, [
- 'account_id' => $account->id,
- 'is_owner' => 1,
- 'is_admin' => 1,
- 'is_locked' => 0,
- 'notifications' => CompanySettings::notificationDefaults(),
- 'settings' => null,
- ]);
-
-
- $company_token = new CompanyToken();
- $company_token->user_id = $user->id;
- $company_token->company_id = $company->id;
- $company_token->account_id = $account->id;
- $company_token->token = $this->faker->unique()->sha1();
- $company_token->name = $this->faker->word();
- $company_token->is_system = 0;
-
- $company_token->save();
-
- $client_settings = ClientSettings::defaults();
- $client_settings->currency_id = '3';
-
- $this->client = Client::factory()->create([
- 'user_id' => $this->user->id,
- 'company_id' => $this->company->id,
- 'name' => 'Test Client',
- 'address1' => 'Calle Mayor 123',
- 'city' => 'Madrid',
- 'state' => 'Madrid',
- 'postal_code' => '28001',
- 'country_id' => 724,
- 'vat_number' => $this->test_client_nif,
- 'balance' => 0,
- 'paid_to_date' => 0,
- 'settings' => $client_settings,
- ]);
-
- $line_items = [];
-
- $item = new InvoiceItem();
- $item->product_key = '1234567890';
- $item->qty = 1;
- $item->cost = 100;
- $item->notes = 'Test item';
- $item->tax_name1 = 'IVA';
- $item->tax_rate1 = 21;
-
- $line_items[] = $item;
-
- $invoice = Invoice::factory()->create([
- 'user_id' => $this->user->id,
- 'company_id' => $this->company->id,
- 'client_id' => $this->client->id,
- 'number' => 'INV-2024-001',
- 'date' => '2024-01-15',
- 'due_date' => now()->addDays(5)->format('Y-m-d'),
- 'status_id' => Invoice::STATUS_DRAFT,
- 'amount' => 121.00,
- 'balance' => 121.00,
- 'line_items' => $line_items,
- ]);
-
- $invoice = $invoice->calc()
- ->getInvoice()
- ->service()
- ->markSent()
- ->save();
-
- return $invoice;
- }
-
- public function testInvoiceCancellationCreation()
- {
- $invoice = $this->buildTestInvoice();
-
- $huella = 'ABCD1234EF5678901234567890ABCDEF1234567890ABCDEF1234567890ABCDEF12';
-
- $cancellation = InvoiceCancellation::fromInvoice($invoice, $huella);
-
- $this->assertInstanceOf(InvoiceCancellation::class, $cancellation);
- $this->assertEquals('INV-2024-001', $cancellation->getNumSerieFacturaEmisor());
- $this->assertEquals('15-01-2024', $cancellation->getFechaExpedicionFacturaEmisor());
- $this->assertEquals($this->test_company_nif, $cancellation->getNifEmisor());
- $this->assertEquals($huella, $cancellation->getHuellaFactura());
- $this->assertEquals('02', $cancellation->getEstado());
- $this->assertEquals('Factura anulada por error', $cancellation->getDescripcionEstado());
-
- }
-
- public function testInvoiceCancellationXmlGeneration()
- {
- $invoice = $this->buildTestInvoice();
-
- $huella = 'ABCD1234EF5678901234567890ABCDEF1234567890ABCDEF1234567890ABCDEF12';
-
- $cancellation = InvoiceCancellation::fromInvoice($invoice, $huella);
-
- $xmlString = $cancellation->toXmlString();
-
- // Verify XML structure
- $this->assertNotEmpty($xmlString);
- $this->assertStringContainsString('', $xmlString);
- $this->assertStringContainsString('SuministroLRFacturas', $xmlString);
- $this->assertStringContainsString('xmlns:ds="http://www.w3.org/2000/09/xmldsig#"', $xmlString);
- $this->assertStringContainsString('Version="1.1"', $xmlString);
-
- // Verify required elements
- $this->assertStringContainsString('LRFacturaEntrada', $xmlString);
- $this->assertStringContainsString('IDFactura', $xmlString);
- $this->assertStringContainsString('IDEmisorFactura', $xmlString);
- $this->assertStringContainsString('NumSerieFacturaEmisor', $xmlString);
- $this->assertStringContainsString('FechaExpedicionFacturaEmisor', $xmlString);
- $this->assertStringContainsString('NIFEmisor', $xmlString);
- $this->assertStringContainsString('HuellaFactura', $xmlString);
- $this->assertStringContainsString('EstadoFactura', $xmlString);
- $this->assertStringContainsString('Estado', $xmlString);
- $this->assertStringContainsString('DescripcionEstado', $xmlString);
-
- // Verify specific values
- $this->assertStringContainsString('INV-2024-001', $xmlString);
- $this->assertStringContainsString('15-01-2024', $xmlString);
- $this->assertStringContainsString($this->test_company_nif, $xmlString);
- $this->assertStringContainsString($huella, $xmlString);
- $this->assertStringContainsString('02', $xmlString);
- $this->assertStringContainsString('Factura anulada por error', $xmlString);
- }
-
-
- public function testInvoiceCancellationSoapEnvelope()
- {
- $invoice = $this->buildTestInvoice();
-
- $huella = 'ABCD1234EF5678901234567890ABCDEF1234567890ABCDEF1234567890ABCDEF12';
-
- $cancellation = InvoiceCancellation::fromInvoice($invoice, $huella);
-
- $soapEnvelope = $cancellation->toSoapEnvelope();
-
- // Verify SOAP structure
- $this->assertNotEmpty($soapEnvelope);
- $this->assertStringContainsString('soapenv:Envelope', $soapEnvelope);
- $this->assertStringContainsString('xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"', $soapEnvelope);
- $this->assertStringContainsString('xmlns:sum="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd"', $soapEnvelope);
- $this->assertStringContainsString('xmlns:sum1="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd"', $soapEnvelope);
-
- // Verify SOAP body structure
- $this->assertStringContainsString('soapenv:Header', $soapEnvelope);
- $this->assertStringContainsString('soapenv:Body', $soapEnvelope);
- $this->assertStringContainsString('sum:RegFactuSistemaFacturacion', $soapEnvelope);
- $this->assertStringContainsString('sum:Cabecera', $soapEnvelope);
- $this->assertStringContainsString('sum1:ObligadoEmision', $soapEnvelope);
- $this->assertStringContainsString('sum:RegistroFactura', $soapEnvelope);
-
- // Verify the cancellation XML is embedded in SOAP
- $this->assertStringContainsString('SuministroLRFacturas', $soapEnvelope);
- $this->assertStringContainsString('LRFacturaEntrada', $soapEnvelope);
- }
-
-
- public function testInvoiceCancellationCustomValues()
- {
- $cancellation = new InvoiceCancellation();
-
- $cancellation->setNumSerieFacturaEmisor('CUSTOM-INV-001')
- ->setFechaExpedicionFacturaEmisor('2025-01-20')
- ->setNifEmisor('B87654321')
- ->setHuellaFactura('CUSTOM_HASH_1234567890ABCDEF')
- ->setEstado('01') // Different status
- ->setDescripcionEstado('Factura anulada por solicitud del cliente');
-
- $xmlString = $cancellation->toXmlString();
-
- // Verify custom values are in XML
- $this->assertStringContainsString('CUSTOM-INV-001', $xmlString);
- $this->assertStringContainsString('2025-01-20', $xmlString);
- $this->assertStringContainsString('B87654321', $xmlString);
- $this->assertStringContainsString('CUSTOM_HASH_1234567890ABCDEF', $xmlString);
- $this->assertStringContainsString('01', $xmlString);
- $this->assertStringContainsString('Factura anulada por solicitud del cliente', $xmlString);
-
- }
-
- public function testInvoiceCancellationSerialization()
- {
- $invoice = $this->buildTestInvoice();
-
- $cancellation = InvoiceCancellation::fromInvoice($invoice, 'TEST_HASH');
-
- // Serialize
- $serialized = $cancellation->serialize();
- $this->assertNotEmpty($serialized);
- $this->assertIsString($serialized);
-
- // Deserialize
- $deserialized = InvoiceCancellation::unserialize($serialized);
- $this->assertInstanceOf(InvoiceCancellation::class, $deserialized);
-
- // Verify all properties are preserved
- $this->assertEquals($cancellation->getNumSerieFacturaEmisor(), $deserialized->getNumSerieFacturaEmisor());
- $this->assertEquals($cancellation->getFechaExpedicionFacturaEmisor(), $deserialized->getFechaExpedicionFacturaEmisor());
- $this->assertEquals($cancellation->getNifEmisor(), $deserialized->getNifEmisor());
- $this->assertEquals($cancellation->getHuellaFactura(), $deserialized->getHuellaFactura());
- $this->assertEquals($cancellation->getEstado(), $deserialized->getEstado());
- $this->assertEquals($cancellation->getDescripcionEstado(), $deserialized->getDescripcionEstado());
-
- }
-
- public function testInvoiceCancellationFromXml()
- {
- $invoice = $this->buildTestInvoice();
-
- $originalCancellation = InvoiceCancellation::fromInvoice($invoice, 'ORIGINAL_HASH');
- $originalCancellation->setEstado('03')
- ->setDescripcionEstado('Factura anulada por duplicado');
-
- $xmlString = $originalCancellation->toXmlString();
-
- // Parse from XML
- $parsedCancellation = InvoiceCancellation::fromXml($xmlString);
-
- // Verify all properties are correctly parsed
- $this->assertEquals($originalCancellation->getNumSerieFacturaEmisor(), $parsedCancellation->getNumSerieFacturaEmisor());
- $this->assertEquals($originalCancellation->getFechaExpedicionFacturaEmisor(), $parsedCancellation->getFechaExpedicionFacturaEmisor());
- $this->assertEquals($originalCancellation->getNifEmisor(), $parsedCancellation->getNifEmisor());
- $this->assertEquals($originalCancellation->getHuellaFactura(), $parsedCancellation->getHuellaFactura());
- $this->assertEquals($originalCancellation->getEstado(), $parsedCancellation->getEstado());
- $this->assertEquals($originalCancellation->getDescripcionEstado(), $parsedCancellation->getDescripcionEstado());
-
- }
-
- public function testInvoiceCancellationXmlValidation()
- {
- $invoice = $this->buildTestInvoice();
-
- $cancellation = InvoiceCancellation::fromInvoice($invoice, 'VALIDATION_HASH');
-
- $xmlString = $cancellation->toXmlString();
-
- // Verify XML is well-formed
- $doc = new \DOMDocument();
- $this->assertTrue($doc->loadXML($xmlString), 'Generated XML should be well-formed');
-
- // Verify required namespaces
- $doc->loadXML($xmlString);
- $root = $doc->documentElement;
-
- $this->assertEquals('SuministroLRFacturas', $root->nodeName);
- $this->assertEquals('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/suministro/FacturaLR.xsd', $root->getAttribute('xmlns'));
- $this->assertEquals('http://www.w3.org/2000/09/xmldsig#', $root->getAttribute('xmlns:ds'));
- $this->assertEquals('1.1', $root->getAttribute('Version'));
-
- }
-
- public function testInvoiceCancellationDifferentStatusCodes()
- {
- $invoice = $this->buildTestInvoice();
-
- $statusCodes = [
- '01' => 'Factura anulada por solicitud del cliente',
- '02' => 'Factura anulada por error',
- '03' => 'Factura anulada por duplicado',
- '04' => 'Factura anulada por otros motivos'
- ];
-
- foreach ($statusCodes as $code => $description) {
- $cancellation = InvoiceCancellation::fromInvoice($invoice, 'STATUS_HASH_' . $code);
- $cancellation->setEstado($code)
- ->setDescripcionEstado($description);
-
- $xmlString = $cancellation->toXmlString();
-
- $this->assertStringContainsString($code, $xmlString);
- $this->assertStringContainsString($description, $xmlString);
- }
-
- }
-
- public function testInvoiceCancellationWithNullValues()
- {
- $cancellation = new InvoiceCancellation();
-
- // Test with minimal required values
- $cancellation->setNumSerieFacturaEmisor('MINIMAL-INV')
- ->setFechaExpedicionFacturaEmisor('2025-01-01')
- ->setNifEmisor('B12345678')
- ->setHuellaFactura('MINIMAL_HASH');
-
- $xmlString = $cancellation->toXmlString();
-
- // Should still generate valid XML with default values
- $this->assertNotEmpty($xmlString);
- $this->assertStringContainsString('MINIMAL-INV', $xmlString);
- $this->assertStringContainsString('2025-01-01', $xmlString);
- $this->assertStringContainsString('B12345678', $xmlString);
- $this->assertStringContainsString('MINIMAL_HASH', $xmlString);
- $this->assertStringContainsString('02', $xmlString); // Default estado
- $this->assertStringContainsString('Factura anulada por error', $xmlString); // Default description
-
- }
-
- public function testInvoiceCancellationIntegrationWithVerifactu()
- {
- $invoice = $this->buildTestInvoice();
-
- // Simulate the integration with the main Verifactu class
- $cancellation = InvoiceCancellation::fromInvoice($invoice, 'INTEGRATION_HASH');
-
- // Test XML generation
- $xmlString = $cancellation->toXmlString();
- $this->assertNotEmpty($xmlString);
-
- // Test SOAP envelope generation
- $soapEnvelope = $cancellation->toSoapEnvelope();
- $this->assertNotEmpty($soapEnvelope);
-
- // Test serialization for storage
- $serialized = $cancellation->serialize();
- $this->assertNotEmpty($serialized);
-
- // Test that the cancellation can be stored and retrieved
- $deserialized = InvoiceCancellation::unserialize($serialized);
- $this->assertInstanceOf(InvoiceCancellation::class, $deserialized);
-
- // Verify the deserialized object can still generate XML
- $newXmlString = $deserialized->toXmlString();
- $this->assertNotEmpty($newXmlString);
- $this->assertEquals($xmlString, $newXmlString);
-
- }
-
- public function testInvoiceCancellationExactXmlFormat()
- {
- $invoice = $this->buildTestInvoice();
-
- $cancellation = InvoiceCancellation::fromInvoice($invoice, 'ABCD1234EF5678901234567890ABCDEF1234567890ABCDEF1234567890ABCDEF12');
-
- $xmlString = $cancellation->toXmlString();
-
- // Verify the exact XML structure matches the required format
- $expectedElements = [
- '',
- '',
- '',
- '',
- 'INV-2024-001',
- '15-01-2024',
- 'A39200019',
- 'ABCD1234EF5678901234567890ABCDEF1234567890ABCDEF1234567890ABCDEF12',
- '',
- '',
- '',
- '02',
- 'Factura anulada por error',
- '',
- '',
- ''
- ];
-
- foreach ($expectedElements as $element) {
- $this->assertStringContainsString($element, $xmlString, "XML should contain: $element");
- }
-
- // Verify XML is properly formatted and indented
- $this->assertStringContainsString(' ', $xmlString);
- $this->assertStringContainsString(' ', $xmlString);
- $this->assertStringContainsString(' ', $xmlString);
- $this->assertStringContainsString(' ', $xmlString);
- }
-}
\ No newline at end of file
diff --git a/tests/Feature/EInvoice/Verifactu/VerifactuFeatureTest.php b/tests/Feature/EInvoice/Verifactu/VerifactuFeatureTest.php
index 9fe746a196..762fde4614 100644
--- a/tests/Feature/EInvoice/Verifactu/VerifactuFeatureTest.php
+++ b/tests/Feature/EInvoice/Verifactu/VerifactuFeatureTest.php
@@ -24,7 +24,6 @@ use App\Services\EDocument\Standards\Verifactu\Models\IDFactura;
use App\Services\EDocument\Standards\Verifactu\ResponseProcessor;
use App\Services\EDocument\Standards\Verifactu\Models\Encadenamiento;
use App\Services\EDocument\Standards\Verifactu\Models\RegistroAnterior;
-use App\Services\EDocument\Standards\Verifactu\Models\InvoiceModification;
use App\Services\EDocument\Standards\Validation\VerifactuDocumentValidator;
use App\Services\EDocument\Standards\Verifactu\Models\FacturaRectificativa;
use App\Services\EDocument\Standards\Verifactu\Models\Invoice as VerifactuInvoice;
@@ -195,7 +194,7 @@ class VerifactuFeatureTest extends TestCase
$invoice = $this->buildData();
- $invoice->number = 'TEST0033343459';
+ $invoice->number = 'TEST0033343460';
$invoice->save();
$this->assertNotNull($invoice);
@@ -212,17 +211,17 @@ class VerifactuFeatureTest extends TestCase
$xx = VerifactuLog::create([
'invoice_id' => $_inv->id,
'company_id' => $invoice->company_id,
- 'invoice_number' => 'TEST0033343458',
+ 'invoice_number' => 'TEST0033343459',
'date' => '2025-08-10',
- 'hash' => '71E0DB528B7D83CE44A1D9055FE814371D77A9291EB24B74043ACE639175CC3C',
+ 'hash' => 'E5A23515881D696FCD1CA8EE4902632BFC6D892BA8EB79CB656A5F84963079D3',
'nif' => 'A39200019',
- 'previous_hash' => '71E0DB528B7D83CE44A1D9055FE814371D77A9291EB24B74043ACE639175CC3C',
+ 'previous_hash' => 'E5A23515881D696FCD1CA8EE4902632BFC6D892BA8EB79CB656A5F84963079D3',
]);
$verifactu = new Verifactu($invoice);
$verifactu->run();
$verifactu->setTestMode()
- ->setPreviousHash('71E0DB528B7D83CE44A1D9055FE814371D77A9291EB24B74043ACE639175CC3C');
+ ->setPreviousHash('E5A23515881D696FCD1CA8EE4902632BFC6D892BA8EB79CB656A5F84963079D3');
$validator = new \App\Services\EDocument\Standards\Validation\VerifactuDocumentValidator($verifactu->getEnvelope());
$validator->validate();
@@ -262,291 +261,245 @@ class VerifactuFeatureTest extends TestCase
$xx->forceDelete();
}
+ public function testBuildInvoiceCancellation()
+ {
+ $invoice = $this->buildData();
+
+ $invoice->number = 'TEST0033343459';
+ $invoice->save();
+
+ $_inv = Invoice::factory()->create([
+ 'user_id' => $invoice->user_id,
+ 'company_id' => $invoice->company_id,
+ 'client_id' => $invoice->client_id,
+ 'date' => '2025-08-10',
+ 'status_id' => Invoice::STATUS_SENT,
+ 'uses_inclusive_taxes' => false,
+ ]);
+
+ $xx = VerifactuLog::create([
+ 'invoice_id' => $_inv->id,
+ 'company_id' => $invoice->company_id,
+ 'invoice_number' => 'TEST0033343459',
+ 'date' => '2025-08-10',
+ 'hash' => 'CEF610A3C24D4106ABE4A836C48B0F5251600F44EEE05A90EBD7185FA753553F',
+ 'nif' => 'A39200019',
+ 'previous_hash' => 'CEF610A3C24D4106ABE4A836C48B0F5251600F44EEE05A90EBD7185FA753553F',
+ ]);
+
+ $verifactu = new Verifactu($invoice);
+ $document = (new RegistroAlta($invoice))->run()->getInvoice();
+ $huella = $this->cancellationHash($document, $xx->hash);
+
+ $cancellation = $document->createCancellation();
+ // $cancellation->setFechaHoraHusoGenRegistro('2025-08-09T23:57:25+00:00');
+ $cancellation->setHuella($huella);
+
+ $soapXml = $cancellation->toSoapEnvelope();
+
+ $response = Http::withHeaders([
+ 'Content-Type' => 'text/xml; charset=utf-8',
+ 'SOAPAction' => '',
+ ])
+ ->withOptions([
+ 'cert' => storage_path('aeat-cert5.pem'),
+ 'ssl_key' => storage_path('aeat-key5.pem'),
+ 'verify' => false,
+ 'timeout' => 30,
+ ])
+ ->withBody($soapXml, 'text/xml')
+ ->post('https://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP');
+
+ nlog('Request with AEAT official test data:');
+ nlog($soapXml);
+ nlog('Response with AEAT official test data:');
+ nlog('Response Status: ' . $response->status());
+ nlog('Response Headers: ' . json_encode($response->headers()));
+ nlog('Response Body: ' . $response->body());
+
+ $r = new ResponseProcessor();
+ $rx = $r->processResponse($response->body());
+ $this->assertTrue($rx['success']);
+
+ $xx->forceDelete();
+
+
+ }
+
+ private function cancellationHash($document, $huella)
+ {
+
+ $idEmisorFacturaAnulada = $document->getIdFactura()->getIdEmisorFactura();
+ $numSerieFacturaAnulada = $document->getIdFactura()->getNumSerieFactura();
+ $fechaExpedicionFacturaAnulada = $document->getIdFactura()->getFechaExpedicionFactura();
+ $fechaHoraHusoGenRegistro = $document->getFechaHoraHusoGenRegistro();
+
+ $hashInput = "IDEmisorFacturaAnulada={$idEmisorFacturaAnulada}&" .
+ "NumSerieFacturaAnulada={$numSerieFacturaAnulada}&" .
+ "FechaExpedicionFacturaAnulada={$fechaExpedicionFacturaAnulada}&" .
+ "Huella={$huella}&" .
+ "FechaHoraHusoGenRegistro={$fechaHoraHusoGenRegistro}";
+
+ nlog("Cancellation Huella: " . $hashInput);
+
+ return strtoupper(hash('sha256', $hashInput));
-public function testBuildInvoiceCancellation()
-{
- $invoice = $this->buildData();
-
- $invoice->number = 'TEST0033343459';
- $invoice->save();
-
- $_inv = Invoice::factory()->create([
- 'user_id' => $invoice->user_id,
- 'company_id' => $invoice->company_id,
- 'client_id' => $invoice->client_id,
- 'date' => '2025-08-10',
- 'status_id' => Invoice::STATUS_SENT,
- 'uses_inclusive_taxes' => false,
- ]);
-
- $xx = VerifactuLog::create([
- 'invoice_id' => $_inv->id,
- 'company_id' => $invoice->company_id,
- 'invoice_number' => 'TEST0033343459',
- 'date' => '2025-08-10',
- 'hash' => 'CEF610A3C24D4106ABE4A836C48B0F5251600F44EEE05A90EBD7185FA753553F',
- 'nif' => 'A39200019',
- 'previous_hash' => 'CEF610A3C24D4106ABE4A836C48B0F5251600F44EEE05A90EBD7185FA753553F',
- ]);
-
- $verifactu = new Verifactu($invoice);
- $document = (new RegistroAlta($invoice))->run()->getInvoice();
- $huella = $this->cancellationHash($document, $xx->hash);
-
- $cancellation = $document->createCancellation();
- // $cancellation->setFechaHoraHusoGenRegistro('2025-08-09T23:57:25+00:00');
- $cancellation->setHuella($huella);
-
- $soapXml = $cancellation->toSoapEnvelope();
-
- $response = Http::withHeaders([
- 'Content-Type' => 'text/xml; charset=utf-8',
- 'SOAPAction' => '',
- ])
- ->withOptions([
- 'cert' => storage_path('aeat-cert5.pem'),
- 'ssl_key' => storage_path('aeat-key5.pem'),
- 'verify' => false,
- 'timeout' => 30,
- ])
- ->withBody($soapXml, 'text/xml')
- ->post('https://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP');
-
- nlog('Request with AEAT official test data:');
- nlog($soapXml);
- nlog('Response with AEAT official test data:');
- nlog('Response Status: ' . $response->status());
- nlog('Response Headers: ' . json_encode($response->headers()));
- nlog('Response Body: ' . $response->body());
-
- $r = new ResponseProcessor();
- $rx = $r->processResponse($response->body());
- $this->assertTrue($rx['success']);
-
- $xx->forceDelete();
-
-
-}
-
-private function cancellationHash($document, $huella)
-{
-
-$idEmisorFacturaAnulada = $document->getIdFactura()->getIdEmisorFactura();
-$numSerieFacturaAnulada = $document->getIdFactura()->getNumSerieFactura();
-$fechaExpedicionFacturaAnulada = $document->getIdFactura()->getFechaExpedicionFactura();
-$fechaHoraHusoGenRegistro = $document->getFechaHoraHusoGenRegistro();
-
-$hashInput = "IDEmisorFacturaAnulada={$idEmisorFacturaAnulada}&" .
- "NumSerieFacturaAnulada={$numSerieFacturaAnulada}&" .
- "FechaExpedicionFacturaAnulada={$fechaExpedicionFacturaAnulada}&" .
- "Huella={$huella}&" .
- "FechaHoraHusoGenRegistro={$fechaHoraHusoGenRegistro}";
-
- nlog("Cancellation Huella: " . $hashInput);
-
-return strtoupper(hash('sha256', $hashInput));
-
-
-// $hashInput = "IDEmisorFacturaAnulada={$document->getIdFactura()->getIdEmisorFactura()}&" .
-// "NumSerieFacturaAnulada={$document->getIdFactura()->getNumSerieFactura()}&" .
-// "FechaExpedicionFacturaAnulada={$document->getIdFactura()->getFechaExpedicionFactura()}&" .
-// "Huella={$blank_huella}&" .
-// "FechaHoraHusoGenRegistro={$document->getFechaHoraHusoGenRegistro()}";
-
-// return strtoupper(hash('sha256', $hashInput));
-
-}
+ }
public function test_invoice_invoice_modification()
{
$invoice = $this->buildData();
- $invoice->number = 'TEST0033343444';
+ $invoice->number = 'TEST0033343460-R2';
$invoice->save();
- $previous_huella = 'C9D10B1EE60CEE114B67CDF07F23487239B2A04A697BE2C4F67AC934B0553CF5';
+ $previous_huella = '1FB6B4EF72DD2A07CC23B3F9D74EE5749C8E86B34B9B1DFFFC8C3E46ACA87E21';
+
+ $xx = VerifactuLog::create([
+ 'invoice_id' => $invoice->id,
+ 'company_id' => $invoice->company_id,
+ 'invoice_number' => 'TEST0033343459',
+ 'date' => '2025-08-10',
+ 'hash' => $previous_huella,
+ 'nif' => 'A39200019',
+ 'previous_hash' => $previous_huella,
+ ]);
$verifactu = new Verifactu($invoice);
- $old_document = $verifactu->setTestMode()
+ $document = $verifactu->setTestMode()
->setPreviousHash($previous_huella)
->run()
->getInvoice();
+ nlog($document->toSoapEnvelope());
- $_verifactu = (new Verifactu($invoice))->setTestMode()->run();
- $new_document = $_verifactu->getInvoice();
- $new_document->setTipoFactura('R1');
- $new_document->setTipoRectificativa('S');
- // For substitutive rectifications (S), ImporteRectificacion is mandatory
- $new_document->setImporteRectificacion(100.00);
- // Set a proper description for the rectification operation
- $new_document->setDescripcionOperacion('Rectificación por error en factura anterior');
-
- // Debug: Log the description to ensure it's set
- \Log::info('DescripcionOperacion set to: ' . $new_document->getDescripcionOperacion());
-
- // Set up the rectified invoice information with proper amounts
- // For R1 invoices, we need BaseRectificada and CuotaRectificada
- $new_document->setRectifiedInvoice(
- 'A39200019', // NIF of rectified invoice
- 'TEST0033343443', // Series number of rectified invoice
- '09-08-2025' // Date of rectified invoice
- );
-
- // Set the rectified amounts (BaseRectificada and CuotaRectificada)
- // These represent the amounts from the original invoice that are being rectified
- $new_document->setRectifiedAmounts(
- 200.00, // BaseRectificada - base amount from original invoice
- 42.00 // CuotaRectificada - tax amount from original invoice
- );
-
- $response = $_verifactu->send($new_document->toSoapEnvelope());
- // $_document = InvoiceModification::createFromInvoice($old_document, $new_document);
-
- // $response = $_verifactu->send($_document->toSoapEnvelope());
-
- // Debug: Log the XML being sent
- $xmlString = $new_document->toXmlString();
- \Log::info('Generated XML for R1 invoice:', ['xml' => $xmlString]);
-
- // Debug: Log the SOAP envelope being sent
- $soapEnvelope = $new_document->toSoapEnvelope();
- \Log::info('Generated SOAP envelope for R1 invoice:', ['soap' => $soapEnvelope]);
-
- // Debug: Log the response to see what's happening
- \Log::info('Verifactu R1 invoice test response:', $response);
-
+ $response = $verifactu->send($document->toSoapEnvelope());
+
$this->assertNotNull($response);
$this->assertArrayHasKey('success', $response);
$this->assertTrue($response['success']);
}
-public function test_raw()
-{
-$soapXml = <<
-
-
-
-
-
-
- CERTIFICADO FISICA PRUEBAS
- A39200019
-
-
-
-
- 1.0
-
- A39200019
- TEST0033343444
- 09-08-2025
-
- CERTIFICADO FISICA PRUEBAS
- R1
- S
-
-
- A39200019
- TEST0033343443
- 09-08-2025
-
-
-
- 100.00
- 21.00
- 0.00
-
- Rectificación por error en factura anterior
-
-
- Test Recipient Company
- A39200019
-
-
-
-
- 01
- 01
- S1
- 21.00
- 97.00
- 20.37
-
-
- 47.05
- 144.05
-
- S
-
-
- CERTIFICADO FISICA PRUEBAS
- A39200019
- InvoiceNinja
- 77
- 1.0.03
- 383
- N
- S
- S
-
- 2025-08-09T23:18:44+02:00
- 01
- E7558C33FE3496551F38FEB582F4879B1D9F6C073489628A8DC275E12298941F
-
-
-
-
-
-XML;
+ public function test_rectification_invoice()
+ {
+ $soapXml = <<
+
+
+
+
+
+ CERTIFICADO FISICA PRUEBAS
+ A39200019
+
+
+
+
+ 1.0
+
+ A39200019
+ TEST0033343460-R1
+ 10-08-2025
+
+ CERTIFICADO FISICA PRUEBAS
+ F3
+
+
+ A39200019
+ TEST0033343460
+ 10-08-2025
+
+
+ Alta
+
+
+ CERTIFICADO FISICA PRUEBAS
+ A39200019
+
+
+
+
+ 01
+ 01
+ S1
+ 21.00
+ 100.00
+ 21.00
+
+
+ 21
+ 121
+
+
+ A39200019
+ TEST0033343459
+ 10-08-2025
+ 1FB6B4EF72DD2A07CC23B3F9D74EE5749C8E86B34B9B1DFFFC8C3E46ACA87E21
+
+
+
+ CERTIFICADO FISICA PRUEBAS
+ A39200019
+ InvoiceNinja
+ 77
+ 1.0.03
+ 383
+ N
+ S
+ S
+
+ 2025-08-10T05:02:18+00:00
+ 01
+ BC61C7CB7CB09917C076CAE7D066B3E2CF521A3B8B501D0C83250B5EB4A4B40D
+
+
+
+
+
+
+ XML;
-$xslt = new VerifactuDocumentValidator($soapXml);
-$xslt->validate();
-$errors = $xslt->getVerifactuErrors();
+ $xslt = new VerifactuDocumentValidator($soapXml);
+ $xslt->validate();
+ $errors = $xslt->getVerifactuErrors();
-if(count($errors) > 0) {
- nlog('Errors:');
- nlog($errors);
- nlog('Errors:');
-}
+ if(count($errors) > 0) {
+ nlog('Errors:');
+ nlog($errors);
+ nlog('Errors:');
+ }
-$this->assertCount(0, $errors);
+ $this->assertCount(0, $errors);
-$response = Http::withHeaders([
- 'Content-Type' => 'text/xml; charset=utf-8',
- 'SOAPAction' => '',
- ])
- ->withOptions([
- 'cert' => storage_path('aeat-cert5.pem'),
- 'ssl_key' => storage_path('aeat-key5.pem'),
- 'verify' => false,
- 'timeout' => 30,
- ])
- ->withBody($soapXml, 'text/xml')
- ->post('https://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP');
+ $response = Http::withHeaders([
+ 'Content-Type' => 'text/xml; charset=utf-8',
+ 'SOAPAction' => '',
+ ])
+ ->withOptions([
+ 'cert' => storage_path('aeat-cert5.pem'),
+ 'ssl_key' => storage_path('aeat-key5.pem'),
+ 'verify' => false,
+ 'timeout' => 30,
+ ])
+ ->withBody($soapXml, 'text/xml')
+ ->post('https://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP');
-nlog('Request with AEAT official test data:');
-nlog($soapXml);
-nlog('Response with AEAT official test data:');
-nlog('Response Status: ' . $response->status());
-nlog('Response Headers: ' . json_encode($response->headers()));
-nlog('Response Body: ' . $response->body());
+ nlog('Request with AEAT official test data:');
+ nlog($soapXml);
+ nlog('Response with AEAT official test data:');
+ nlog('Response Status: ' . $response->status());
+ nlog('Response Headers: ' . json_encode($response->headers()));
+ nlog('Response Body: ' . $response->body());
-$r = new ResponseProcessor();
-$rx = $r->processResponse($response->body());
+ $r = new ResponseProcessor();
+ $rx = $r->processResponse($response->body());
-nlog($rx);
+ nlog($rx);
-
-
-}
+ }
public function testInvoiceCancellation()
@@ -654,7 +607,15 @@ nlog($rx);
// Set up rectification details exactly as in the expected XML
$invoice->setRectifiedInvoice('A39200019', 'TEST0033343443', '09-08-2025');
- $invoice->setRectificationAmounts(100.00, 21.00, 0.00);
+
+
+ $importeRectificacion = [
+ 'BaseRectificada' => 100.00,
+ 'CuotaRectificada' => 21.00,
+ 'CuotaRecargoRectificado' => 0.00
+ ];
+
+ $invoice->setRectificationAmounts($importeRectificacion);
// Set up desglose exactly as in the expected XML
$desglose = new Desglose();
diff --git a/tests/Feature/EInvoice/Verifactu/VerifactuModificationTest.php b/tests/Feature/EInvoice/Verifactu/VerifactuModificationTest.php
deleted file mode 100644
index 0582dd1f9f..0000000000
--- a/tests/Feature/EInvoice/Verifactu/VerifactuModificationTest.php
+++ /dev/null
@@ -1,671 +0,0 @@
-setIdEmisorFactura('99999910G')
- ->setNumSerieFactura('TEST0033343436')
- ->setFechaExpedicionFactura('02-07-2025')
- ->setMotivoAnulacion('1');
-
- $this->assertEquals('99999910G', $cancellation->getIdEmisorFactura());
- $this->assertEquals('TEST0033343436', $cancellation->getNumSerieFactura());
- $this->assertEquals('02-07-2025', $cancellation->getFechaExpedicionFactura());
- $this->assertEquals('1', $cancellation->getMotivoAnulacion());
-
- $xml = $cancellation->toXmlString();
- $this->assertStringContainsString('RegistroAnulacion', $xml);
- $this->assertStringContainsString('99999910G', $xml);
- $this->assertStringContainsString('TEST0033343436', $xml);
- $this->assertStringContainsString('02-07-2025', $xml);
- $this->assertStringContainsString('1', $xml);
-
- $validXml = $cancellation->toXmlString();
-
- // Use the new VerifactuDocumentValidator
- $validator = new \App\Services\EDocument\Standards\Validation\VerifactuDocumentValidator($validXml);
- $validator->validate();
- $errors = $validator->getVerifactuErrors();
-
- if (!empty($errors)) {
-
- nlog('Verifactu Validation Errors:');
- nlog($validXml);
- nlog($errors);
- }
-
- }
-
- public function test_can_create_registro_modificacion()
- {
- $modification = new RegistroModificacion();
- $modification
- ->setIdVersion('1.0')
- ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura())
- ->setIdEmisorFactura('99999910G')
- ->setNumSerieFactura('TEST0033343436')
- ->setFechaExpedicionFactura('01-01-2025'))
- ->setNombreRazonEmisor('CERTIFICADO FISICA PRUEBAS')
- ->setTipoFactura('F1')
- ->setDescripcionOperacion('Test invoice modification')
- ->setCuotaTotal(21.00)
- ->setImporteTotal(121.00)
- ->setFechaHoraHusoGenRegistro('2025-01-02T12:00:00')
- ->setTipoHuella('01')
- ->setHuella('TEST_HASH');
-
- // Add sistema informatico
- $sistema = new SistemaInformatico();
- $sistema
- ->setNombreRazon('Sistema de Facturación')
- ->setNif('A39200019')
- ->setNombreSistemaInformatico('InvoiceNinja')
- ->setIdSistemaInformatico('77')
- ->setVersion('1.0.03')
- ->setNumeroInstalacion('383');
- $modification->setSistemaInformatico($sistema);
-
- // Add desglose
- $desglose = new Desglose();
- $desglose->setDesgloseFactura([
- 'Impuesto' => '01',
- 'ClaveRegimen' => '01',
- 'CalificacionOperacion' => 'S1',
- 'TipoImpositivo' => '21',
- 'BaseImponibleOimporteNoSujeto' => '100.00',
- 'CuotaRepercutida' => '21.00'
- ]);
-
- $modification->setDesglose($desglose);
-
- // Add encadenamiento
- $encadenamiento = new Encadenamiento();
- $encadenamiento->setPrimerRegistro('S');
- $modification->setEncadenamiento($encadenamiento);
-
- $this->assertEquals('1.0', $modification->getIdVersion());
- $this->assertEquals('TEST0033343436', $modification->getIdFactura()->getNumSerieFactura());
- $this->assertEquals('CERTIFICADO FISICA PRUEBAS', $modification->getNombreRazonEmisor());
- $this->assertEquals('F1', $modification->getTipoFactura());
- $this->assertEquals(21.00, $modification->getCuotaTotal());
- $this->assertEquals(121.00, $modification->getImporteTotal());
-
- $xml = $modification->toXmlString();
-
- $this->assertStringContainsString('RegistroModificacion', $xml);
- $this->assertStringContainsString('TEST0033343436', $xml);
- $this->assertStringContainsString('CERTIFICADO FISICA PRUEBAS', $xml);
- $this->assertStringContainsString('21', $xml);
- $this->assertStringContainsString('121', $xml);
-
- // Use the new VerifactuDocumentValidator
- $validator = new \App\Services\EDocument\Standards\Validation\VerifactuDocumentValidator($xml);
- $validator->validate();
- $errors = $validator->getVerifactuErrors();
-
- if (!empty($errors)) {
-
- nlog('Verifactu Validation Errors:');
- nlog($xml);
- nlog($errors);
- }
-
- }
-
- public function test_can_create_invoice_modification_from_invoices()
- {
- // Create original invoice
- $originalInvoice = new Invoice();
- $originalInvoice
- ->setIdVersion('1.0')
- ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura())
- ->setIdEmisorFactura('99999910G')
- ->setNumSerieFactura('TEST0033343436')
- ->setFechaExpedicionFactura('01-01-2025'))
- ->setNombreRazonEmisor('Original Company')
- ->setTipoFactura('F1')
- ->setDescripcionOperacion('Original invoice')
- ->setCuotaTotal(21.00)
- ->setImporteTotal(121.00)
- ->setFechaHoraHusoGenRegistro('2025-01-01T12:00:00')
- ->setTipoHuella('01')
- ->setHuella('ORIGINAL_HASH');
-
- // Add emitter to original invoice
- $emisor = new PersonaFisicaJuridica();
- $emisor
- ->setNif('99999910G')
- ->setRazonSocial('Original Company');
- $originalInvoice->setTercero($emisor);
-
- // Add sistema informatico to original invoice
- $sistema = new SistemaInformatico();
- $sistema
- ->setNombreRazon('Sistema de Facturación')
- ->setNif('A39200019')
- ->setNombreSistemaInformatico('InvoiceNinja')
- ->setIdSistemaInformatico('77')
- ->setVersion('1.0.03')
- ->setNumeroInstalacion('383');
- $originalInvoice->setSistemaInformatico($sistema);
-
- // Create modified invoice
- $modifiedInvoice = new Invoice();
- $modifiedInvoice
- ->setIdVersion('1.0')
- ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura())
- ->setIdEmisorFactura('99999910G')
- ->setNumSerieFactura('TEST0033343436')
- ->setFechaExpedicionFactura('02-01-2025'))
- ->setNombreRazonEmisor('Modified Company')
- ->setTipoFactura('F1')
- ->setDescripcionOperacion('Modified invoice')
- ->setCuotaTotal(42.00)
- ->setImporteTotal(242.00)
- ->setFechaHoraHusoGenRegistro('2025-01-02T12:00:00')
- ->setTipoHuella('01')
- ->setHuella('MODIFIED_HASH');
-
- // Add emitter to modified invoice
- $emisorModificado = new PersonaFisicaJuridica();
- $emisorModificado
- ->setNif('99999910G')
- ->setRazonSocial('Modified Company');
- $modifiedInvoice->setTercero($emisorModificado);
-
- // Add sistema informatico to modified invoice
- $modifiedInvoice->setSistemaInformatico($sistema);
-
- // Create modification
- $modification = InvoiceModification::createFromInvoice($originalInvoice, $modifiedInvoice);
-
- $this->assertInstanceOf(InvoiceModification::class, $modification);
- $this->assertInstanceOf(RegistroAnulacion::class, $modification->getRegistroAnulacion());
- $this->assertInstanceOf(RegistroModificacion::class, $modification->getRegistroModificacion());
-
- // Test cancellation record
- $cancellation = $modification->getRegistroAnulacion();
- $this->assertEquals('99999910G', $cancellation->getIdEmisorFactura());
- $this->assertEquals('TEST0033343436', $cancellation->getNumSerieFactura());
- $this->assertEquals('1', $cancellation->getMotivoAnulacion());
-
- // Test modification record
- $modificationRecord = $modification->getRegistroModificacion();
- $this->assertEquals('Modified Company', $modificationRecord->getNombreRazonEmisor());
- $this->assertEquals(42.00, $modificationRecord->getCuotaTotal());
- $this->assertEquals(242.00, $modificationRecord->getImporteTotal());
-
- $validXml = $modification->toSoapEnvelope();
-
- // Use the new VerifactuDocumentValidator
- $validator = new \App\Services\EDocument\Standards\Validation\VerifactuDocumentValidator($validXml);
- $validator->validate();
- $errors = $validator->getVerifactuErrors();
-
- if (!empty($errors)) {
-
- nlog('Verifactu Validation Errors:');
- nlog($validXml);
- nlog($errors);
- }
-
- // Now that validation is working correctly, we can assert no errors
- $this->assertCount(0, $errors);
- }
-
- public function test_can_generate_modification_soap_envelope()
- {
- // Create original invoice
- $originalInvoice = new Invoice();
- $originalInvoice
- ->setIdVersion('1.0')
- ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura())
- ->setIdEmisorFactura('99999910G')
- ->setNumSerieFactura('TEST0033343436')
- ->setFechaExpedicionFactura('01-01-2025'))
- ->setNombreRazonEmisor('Original Company')
- ->setTipoFactura('F1')
- ->setDescripcionOperacion('Original invoice')
- ->setCuotaTotal(21.00)
- ->setImporteTotal(121.00)
- ->setFechaHoraHusoGenRegistro('2025-01-01T12:00:00')
- ->setTipoHuella('01')
- ->setHuella('ORIGINAL_HASH');
-
- // Add emitter to original invoice
- $emisor = new PersonaFisicaJuridica();
- $emisor
- ->setNif('99999910G')
- ->setRazonSocial('Original Company');
- $originalInvoice->setTercero($emisor);
-
- // Add sistema informatico to original invoice
- $sistema = new SistemaInformatico();
- $sistema
- ->setNombreRazon('Sistema de Facturación')
- ->setNif('A39200019')
- ->setNombreSistemaInformatico('InvoiceNinja')
- ->setIdSistemaInformatico('77')
- ->setVersion('1.0.03')
- ->setNumeroInstalacion('383');
- $originalInvoice->setSistemaInformatico($sistema);
-
- // Create modified invoice
- $modifiedInvoice = new Invoice();
- $modifiedInvoice
- ->setIdVersion('1.0')
- ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura())
- ->setIdEmisorFactura('99999910G')
- ->setNumSerieFactura('TEST0033343436')
- ->setFechaExpedicionFactura('02-01-2025'))
- ->setNombreRazonEmisor('Modified Company')
- ->setTipoFactura('F1')
- ->setDescripcionOperacion('Modified invoice')
- ->setCuotaTotal(42.00)
- ->setImporteTotal(242.00)
- ->setFechaHoraHusoGenRegistro('2025-01-02T12:00:00')
- ->setTipoHuella('01')
- ->setHuella('MODIFIED_HASH');
-
- // Add emitter to modified invoice
- $emisorModificado = new PersonaFisicaJuridica();
- $emisorModificado
- ->setNif('99999910G')
- ->setRazonSocial('Modified Company');
- $modifiedInvoice->setTercero($emisorModificado);
-
- // Add sistema informatico to modified invoice
- $modifiedInvoice->setSistemaInformatico($sistema);
-
- // Create modification
- $modification = InvoiceModification::createFromInvoice($originalInvoice, $modifiedInvoice);
-
- // Generate SOAP envelope
- $soapXml = $modification->toSoapEnvelope();
-
- $this->assertStringContainsString('soapenv:Envelope', $soapXml);
- $this->assertStringContainsString('sum:RegFactuSistemaFacturacion', $soapXml);
- $this->assertStringContainsString('sum1:RegistroAlta', $soapXml);
- $this->assertStringContainsString('sum1:TipoFactura>R1', $soapXml);
- $this->assertStringContainsString('sum1:TipoRectificativa>S', $soapXml);
- $this->assertStringContainsString('TEST0033343436', $soapXml);
- $this->assertStringContainsString('Modified invoice', $soapXml);
- $this->assertStringContainsString('42', $soapXml);
- $this->assertStringContainsString('242', $soapXml);
-
- // Verify that TipoRectificativa is present for R1 invoices
- $this->assertStringContainsString('sum1:TipoRectificativa>S', $soapXml);
-
- $validXml = $modification->toSoapEnvelope();
-
- // Use the new VerifactuDocumentValidator
- $validator = new \App\Services\EDocument\Standards\Validation\VerifactuDocumentValidator($validXml);
- $validator->validate();
- $errors = $validator->getVerifactuErrors();
-
- if (!empty($errors)) {
-
- nlog('Verifactu Validation Errors:');
- nlog($validXml);
- nlog($errors);
- }
-
- }
-
- public function test_tipo_rectificativa_only_added_for_r1_invoices()
- {
- // Create original invoice
- $originalInvoice = new Invoice();
- $originalInvoice
- ->setIdVersion('1.0')
- ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura())
- ->setIdEmisorFactura('99999910G')
- ->setNumSerieFactura('TEST0033343436')
- ->setFechaExpedicionFactura('01-01-2025'))
- ->setNombreRazonEmisor('Original Company')
- ->setTipoFactura('F1')
- ->setDescripcionOperacion('Original invoice')
- ->setCuotaTotal(21.00)
- ->setImporteTotal(121.00)
- ->setFechaHoraHusoGenRegistro('2025-01-01T12:00:00')
- ->setTipoHuella('01')
- ->setHuella('ORIGINAL_HASH');
-
- // Add emitter to original invoice
- $emisor = new PersonaFisicaJuridica();
- $emisor
- ->setNif('99999910G')
- ->setRazonSocial('Original Company');
- $originalInvoice->setTercero($emisor);
-
- // Create modified invoice with F1 type (not R1)
- $modifiedInvoice = new Invoice();
- $modifiedInvoice
- ->setIdVersion('1.0')
- ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura())
- ->setIdEmisorFactura('99999910G')
- ->setNumSerieFactura('TEST0033343436')
- ->setFechaExpedicionFactura('02-01-2025'))
- ->setNombreRazonEmisor('Modified Company')
- ->setTipoFactura('F1') // F1 instead of R1
- ->setDescripcionOperacion('Modified invoice')
- ->setCuotaTotal(42.00)
- ->setImporteTotal(242.00)
- ->setFechaHoraHusoGenRegistro('2025-01-02T12:00:00')
- ->setTipoHuella('01')
- ->setHuella('MODIFIED_HASH');
-
- // Create modification
- $modification = InvoiceModification::createFromInvoice($originalInvoice, $modifiedInvoice);
-
- // Generate SOAP envelope
- $soapXml = $modification->toSoapEnvelope();
-
- // For InvoiceModification, TipoFactura is always R1 and TipoRectificativa is always S
- // This is because InvoiceModification is specifically for rectifying invoices
- $this->assertStringContainsString('sum1:TipoFactura>R1', $soapXml);
- $this->assertStringContainsString('sum1:TipoRectificativa>S', $soapXml);
-
- // Verify that the original F1 type from modifiedInvoice is not used in the SOAP envelope
- // because InvoiceModification always converts to R1
- $this->assertStringNotContainsString('sum1:TipoFactura>F1', $soapXml);
- }
-
- public function test_invoice_can_create_modification()
- {
- // Create original invoice
- $originalInvoice = new Invoice();
- $originalInvoice
- ->setIdVersion('1.0')
- ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura())
- ->setIdEmisorFactura('99999910G')
- ->setNumSerieFactura('TEST0033343436')
- ->setFechaExpedicionFactura('01-01-2025'))
- ->setNombreRazonEmisor('Original Company')
- ->setTipoFactura('F1')
- ->setDescripcionOperacion('Original invoice')
- ->setCuotaTotal(21.00)
- ->setImporteTotal(121.00)
- ->setFechaHoraHusoGenRegistro('2025-01-01T12:00:00')
- ->setTipoHuella('01')
- ->setHuella('ORIGINAL_HASH');
-
- // Add emitter to original invoice
- $emisor = new PersonaFisicaJuridica();
- $emisor
- ->setNif('99999910G')
- ->setRazonSocial('Original Company');
- $originalInvoice->setTercero($emisor);
-
- // Add sistema informatico to original invoice
- $sistema = new SistemaInformatico();
- $sistema
- ->setNombreRazon('Sistema de Facturación')
- ->setNif('A39200019')
- ->setNombreSistemaInformatico('InvoiceNinja')
- ->setIdSistemaInformatico('77')
- ->setVersion('1.0.03')
- ->setNumeroInstalacion('383');
- $originalInvoice->setSistemaInformatico($sistema);
-
- // Create modified invoice
- $modifiedInvoice = new Invoice();
- $modifiedInvoice
- ->setIdVersion('1.0')
- ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura())
- ->setIdEmisorFactura('99999910G')
- ->setNumSerieFactura('TEST0033343436')
- ->setFechaExpedicionFactura('02-01-2025'))
- ->setNombreRazonEmisor('Modified Company')
- ->setTipoFactura('F1')
- ->setDescripcionOperacion('Modified invoice')
- ->setCuotaTotal(42.00)
- ->setImporteTotal(242.00)
- ->setFechaHoraHusoGenRegistro('2025-01-02T12:00:00')
- ->setTipoHuella('01')
- ->setHuella('MODIFIED_HASH');
-
- // Add emitter to modified invoice
- $emisorModificado = new PersonaFisicaJuridica();
- $emisorModificado
- ->setNif('99999910G')
- ->setRazonSocial('Modified Company');
- $modifiedInvoice->setTercero($emisorModificado);
-
- // Add sistema informatico to modified invoice
- $modifiedInvoice->setSistemaInformatico($sistema);
-
- // Create modification using the invoice method
- $modification = $originalInvoice->createModification($modifiedInvoice);
-
- $this->assertInstanceOf(InvoiceModification::class, $modification);
-
- // Test cancellation record
- $cancellation = $modification->getRegistroAnulacion();
- $this->assertEquals('99999910G', $cancellation->getIdEmisorFactura());
- $this->assertEquals('TEST0033343436', $cancellation->getNumSerieFactura());
- $this->assertEquals('1', $cancellation->getMotivoAnulacion());
-
- // Test modification record
- $modificationRecord = $modification->getRegistroModificacion();
- $this->assertEquals('Modified Company', $modificationRecord->getNombreRazonEmisor());
- $this->assertEquals(42.00, $modificationRecord->getCuotaTotal());
- $this->assertEquals(242.00, $modificationRecord->getImporteTotal());
-
-
-$validXml = $modification->toSoapEnvelope();
-
-// Use the new VerifactuDocumentValidator
-$validator = new \App\Services\EDocument\Standards\Validation\VerifactuDocumentValidator($validXml);
-$validator->validate();
-$errors = $validator->getVerifactuErrors();
-
-if (!empty($errors)) {
-
- nlog('Verifactu Validation Errors:');
- nlog($validXml);
- nlog($errors);
-}
-
- }
-
- public function test_invoice_can_create_cancellation()
- {
- $invoice = new Invoice();
- $invoice
- ->setIdVersion('1.0')
- ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura())
- ->setIdEmisorFactura('99999910G')
- ->setNumSerieFactura('TEST0033343436')
- ->setFechaExpedicionFactura('01-01-2025'))
- ->setNombreRazonEmisor('Test Company')
- ->setTipoFactura('F1')
- ->setDescripcionOperacion('Test invoice')
- ->setCuotaTotal(21.00)
- ->setImporteTotal(121.00)
- ->setFechaHoraHusoGenRegistro('2025-01-01T12:00:00')
- ->setTipoHuella('01')
- ->setHuella('TEST_HASH');
-
- // Add emitter
- $emisor = new PersonaFisicaJuridica();
- $emisor
- ->setNif('99999910G')
- ->setRazonSocial('Test Company');
- $invoice->setTercero($emisor);
-
- $cancellation = $invoice->createCancellation();
-
- $this->assertInstanceOf(RegistroAnulacion::class, $cancellation);
- $this->assertEquals('99999910G', $cancellation->getIdEmisorFactura());
- $this->assertEquals('TEST0033343436', $cancellation->getNumSerieFactura());
- $this->assertEquals('1', $cancellation->getMotivoAnulacion());
- }
-
- public function test_invoice_can_create_modification_record()
- {
- $invoice = new Invoice();
- $invoice
- ->setIdVersion('1.0')
- ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura())
- ->setIdEmisorFactura('99999910G')
- ->setNumSerieFactura('TEST0033343436')
- ->setFechaExpedicionFactura('01-01-2025'))
- ->setNombreRazonEmisor('Test Company')
- ->setTipoFactura('F1')
- ->setDescripcionOperacion('Test invoice')
- ->setCuotaTotal(21.00)
- ->setImporteTotal(121.00)
- ->setFechaHoraHusoGenRegistro('2025-01-01T12:00:00')
- ->setTipoHuella('01')
- ->setHuella('TEST_HASH');
-
- // Add emitter
- $emisor = new PersonaFisicaJuridica();
- $emisor
- ->setNif('99999910G')
- ->setRazonSocial('Test Company');
- $invoice->setTercero($emisor);
-
- // Add sistema informatico
- $sistema = new SistemaInformatico();
- $sistema
- ->setNombreRazon('Sistema de Facturación')
- ->setNif('A39200019')
- ->setNombreSistemaInformatico('InvoiceNinja')
- ->setIdSistemaInformatico('77')
- ->setVersion('1.0.03')
- ->setNumeroInstalacion('383');
- $invoice->setSistemaInformatico($sistema);
-
- $modificationRecord = $invoice->createModificationRecord();
-
- $this->assertInstanceOf(RegistroModificacion::class, $modificationRecord);
- $this->assertEquals('1.0', $modificationRecord->getIdVersion());
- $this->assertEquals('TEST0033343436', $modificationRecord->getIdFactura());
- $this->assertEquals('Test Company', $modificationRecord->getNombreRazonEmisor());
- $this->assertEquals('F1', $modificationRecord->getTipoFactura());
- $this->assertEquals(21.00, $modificationRecord->getCuotaTotal());
- $this->assertEquals(121.00, $modificationRecord->getImporteTotal());
- }
-
- public function test_modification_xml_structure_matches_aeat_requirements()
- {
- // Create original invoice
- $originalInvoice = new Invoice();
- $originalInvoice
- ->setIdVersion('1.0')
- ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura())
- ->setIdEmisorFactura('99999910G')
- ->setNumSerieFactura('TEST0033343436')
- ->setFechaExpedicionFactura('01-01-2025'))
- ->setNombreRazonEmisor('Original Company')
- ->setTipoFactura('F1')
- ->setDescripcionOperacion('Original invoice')
- ->setCuotaTotal(21.00)
- ->setImporteTotal(121.00)
- ->setFechaHoraHusoGenRegistro('2025-01-01T12:00:00')
- ->setTipoHuella('01')
- ->setHuella('ORIGINAL_HASH');
-
- // Add emitter to original invoice
- $emisor = new PersonaFisicaJuridica();
- $emisor
- ->setNif('99999910G')
- ->setRazonSocial('Original Company');
- $originalInvoice->setTercero($emisor);
-
- // Add sistema informatico to original invoice
- $sistema = new SistemaInformatico();
- $sistema
- ->setNombreRazon('Sistema de Facturación')
- ->setNif('A39200019')
- ->setNombreSistemaInformatico('InvoiceNinja')
- ->setIdSistemaInformatico('77')
- ->setVersion('1.0.03')
- ->setNumeroInstalacion('383');
- $originalInvoice->setSistemaInformatico($sistema);
-
- // Create modified invoice
- $modifiedInvoice = new Invoice();
- $modifiedInvoice
- ->setIdVersion('1.0')
- ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura())
- ->setIdEmisorFactura('99999910G')
- ->setNumSerieFactura('TEST0033343436')
- ->setFechaExpedicionFactura('02-01-2025'))
- ->setNombreRazonEmisor('Modified Company')
- ->setTipoFactura('F1')
- ->setDescripcionOperacion('Modified invoice')
- ->setCuotaTotal(42.00)
- ->setImporteTotal(242.00)
- ->setFechaHoraHusoGenRegistro('2025-01-02T12:00:00')
- ->setTipoHuella('01')
- ->setHuella('MODIFIED_HASH');
-
- // Add emitter to modified invoice
- $emisorModificado = new PersonaFisicaJuridica();
- $emisorModificado
- ->setNif('99999910G')
- ->setRazonSocial('Modified Company');
- $modifiedInvoice->setTercero($emisorModificado);
-
- // Add sistema informatico to modified invoice
- $modifiedInvoice->setSistemaInformatico($sistema);
-
- // Create modification
- $modification = InvoiceModification::createFromInvoice($originalInvoice, $modifiedInvoice);
-
- // Generate SOAP envelope
- $soapXml = $modification->toSoapEnvelope();
-nlog($soapXml);
- // Verify the XML structure matches AEAT requirements
- $this->assertStringContainsString('assertStringContainsString('assertStringContainsString('assertStringContainsString('assertStringContainsString('assertStringContainsString('R1', $soapXml);
-
- // Verify modification structure (no cancellation block needed)
- $this->assertStringContainsString('Modified invoice', $soapXml);
- $this->assertStringContainsString('242', $soapXml);
- $this->assertStringContainsString('42', $soapXml);
-
- $validXml = $modification->toSoapEnvelope();
-
- // Use the new VerifactuDocumentValidator
- $validator = new \App\Services\EDocument\Standards\Validation\VerifactuDocumentValidator($validXml);
- $validator->validate();
- $errors = $validator->getVerifactuErrors();
-
- if (!empty($errors)) {
-
- nlog('Verifactu Validation Errors:');
- nlog($validXml);
- nlog($errors);
- }
-
- }
-}
\ No newline at end of file
diff --git a/tests/Feature/EInvoice/Verifactu/WSTest.php b/tests/Feature/EInvoice/Verifactu/WSTest.php
index a97b4bde23..85f899fafe 100644
--- a/tests/Feature/EInvoice/Verifactu/WSTest.php
+++ b/tests/Feature/EInvoice/Verifactu/WSTest.php
@@ -12,7 +12,6 @@ use App\Services\EDocument\Standards\Verifactu\Models\Encadenamiento;
use App\Services\EDocument\Standards\Verifactu\Models\SistemaInformatico;
use App\Services\EDocument\Standards\Verifactu\ResponseProcessor;
use App\Services\EDocument\Standards\Verifactu\Models\PersonaFisicaJuridica;
-use App\Services\EDocument\Standards\Verifactu\Models\InvoiceModification;
class WSTest extends TestCase
@@ -392,166 +391,6 @@ $invoice->setDestinatarios($destinatarios);
}
- //@todo - need to test that cancelling an invoice works.
- public function test_cancel_existing_invoice()
- {
- //@todo - need to test that cancelling an invoice works.
- }
-
- //@todo - Need to test that modifying an invoice works.
- public function test_cancel_and_modify_existing_invoice()
- {
- $currentTimestamp = now()->setTimezone('Europe/Madrid')->format('Y-m-d\TH:i:s');
- $invoice_number = 'TEST0033343436';
- $invoice_date = '02-07-2025';
- $nif = '99999910G';
-
- // Create original invoice (the one to be cancelled)
- $originalInvoice = new Invoice();
- $originalInvoice
- ->setIdVersion('1.0')
- ->setIdFactura($invoice_number)
- ->setNombreRazonEmisor('Original Company')
- ->setTipoFactura('F1')
- ->setDescripcionOperacion('Original invoice')
- ->setCuotaTotal(21.00)
- ->setImporteTotal(121.00)
- ->setFechaHoraHusoGenRegistro($currentTimestamp)
- ->setTipoHuella('01')
- ->setHuella('ORIGINAL_HASH');
-
- // Add emitter to original invoice
- $emisor = new PersonaFisicaJuridica();
- $emisor
- ->setNif($nif)
- ->setRazonSocial('Original Company');
- $originalInvoice->setTercero($emisor);
-
- // Add sistema informatico to original invoice
- $sistema = new SistemaInformatico();
- $sistema
- ->setNombreRazon('Sistema de Facturación')
- ->setNif('A39200019')
- ->setNombreSistemaInformatico('InvoiceNinja')
- ->setIdSistemaInformatico('77')
- ->setVersion('1.0.03')
- ->setNumeroInstalacion('383');
- $originalInvoice->setSistemaInformatico($sistema);
-
- // Create modified invoice (the replacement)
- $modifiedInvoice = new Invoice();
- $modifiedInvoice
- ->setIdVersion('1.0')
- ->setIdFactura($invoice_number)
- ->setNombreRazonEmisor('CERTIFICADO FISICA PRUEBAS')
- ->setTipoFactura('F1')
- ->setDescripcionOperacion('Test invoice submitted by computer system on behalf of business')
- ->setCuotaTotal(21.00)
- ->setImporteTotal(121.00)
- ->setFechaHoraHusoGenRegistro($currentTimestamp)
- ->setTipoHuella('01')
- ->setHuella('PLACEHOLDER_HUELLA');
-
- // Add emitter to modified invoice
- $emisorModificado = new PersonaFisicaJuridica();
- $emisorModificado
- ->setNif($nif)
- ->setRazonSocial('CERTIFICADO FISICA PRUEBAS');
- $modifiedInvoice->setTercero($emisorModificado);
-
- // Add sistema informatico to modified invoice
- $modifiedInvoice->setSistemaInformatico($sistema);
-
- // Add destinatarios to modified invoice
- $destinatario = new PersonaFisicaJuridica();
- $destinatario
- ->setNombreRazon('Test Recipient Company')
- ->setNif('A39200019');
- $modifiedInvoice->setDestinatarios([$destinatario]);
-
- // Add desglose to modified invoice
- $desglose = new Desglose();
- $desglose->setDesgloseFactura([
- 'Impuesto' => '01',
- 'ClaveRegimen' => '01',
- 'CalificacionOperacion' => 'S1',
- 'TipoImpositivo' => '21',
- 'BaseImponibleOimporteNoSujeto' => '100.00',
- 'CuotaRepercutida' => '21.00'
- ]);
- $modifiedInvoice->setDesglose($desglose);
-
- // Add encadenamiento to modified invoice
- $encadenamiento = new Encadenamiento();
- $encadenamiento->setPrimerRegistro('S');
- $modifiedInvoice->setEncadenamiento($encadenamiento);
-
- // Create modification using the new models
- $modification = InvoiceModification::createFromInvoice($originalInvoice, $modifiedInvoice);
-
- // Calculate the correct hash using AEAT's specified format
- $correctHash = $this->calculateVerifactuHash(
- $nif, // IDEmisorFactura
- $invoice_number, // NumSerieFactura
- $invoice_date, // FechaExpedicionFactura
- 'F1', // TipoFactura
- '21.00', // CuotaTotal
- '121.00', // ImporteTotal
- '', // Huella (empty for first calculation)
- $currentTimestamp // FechaHoraHusoGenRegistro (current time)
- );
-
- // Update the modification record with the correct hash
- $modification->getRegistroModificacion()->setHuella($correctHash);
-
- nlog('Calculated hash for XML: ' . $correctHash);
-
- // Generate SOAP envelope
- $soapXml = $modification->toSoapEnvelope();
-
- // Sign the XML before sending
- $certPath = storage_path('aeat-cert5.pem');
- $keyPath = storage_path('aeat-key5.pem');
- $signingService = new \App\Services\EDocument\Standards\Verifactu\Signing\SigningService($soapXml, file_get_contents($keyPath), file_get_contents($certPath));
- $soapXml = $signingService->sign();
-
- // Try direct HTTP approach instead of SOAP client
- $response = Http::withHeaders([
- 'Content-Type' => 'text/xml; charset=utf-8',
- 'SOAPAction' => '',
- ])
- ->withOptions([
- 'cert' => storage_path('aeat-cert5.pem'),
- 'ssl_key' => storage_path('aeat-key5.pem'),
- 'verify' => false,
- 'timeout' => 30,
- ])
- ->withBody($soapXml, 'text/xml')
- ->post('https://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP');
-
- nlog('Request with AEAT official test data:');
- nlog($soapXml);
- nlog('Response with AEAT official test data:');
- nlog('Response Status: ' . $response->status());
- nlog('Response Headers: ' . json_encode($response->headers()));
- nlog('Response Body: ' . $response->body());
-
- if (!$response->successful()) {
- \Log::error('Request failed with status: ' . $response->status());
- \Log::error('Response body: ' . $response->body());
- }
-
- $this->assertTrue($response->successful());
-
- $responseProcessor = new ResponseProcessor();
- $responseProcessor->processResponse($response->body());
-
- nlog($responseProcessor->getSummary());
-
- $this->assertTrue($responseProcessor->getSummary()['success']);
- }
-
-
/**
* Calculate Verifactu hash using AEAT's specified format
* Based on AEAT response showing the exact format they use