setIdVersion('1.0') ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura()) ->setIdEmisorFactura('B12345678') ->setNumSerieFactura('FAC-2023-001') ->setFechaExpedicionFactura('01-01-2023')) ->setRefExterna('REF-123') ->setNombreRazonEmisor('Empresa Ejemplo SL') ->setTipoFactura('F1') ->setDescripcionOperacion('Venta de productos varios') ->setCuotaTotal(210.00) ->setImporteTotal(1000.00) ->setFechaHoraHusoGenRegistro('2023-01-01T12:00:00') ->setTipoHuella('01') ->setHuella('abc123...'); // Add emitter $emisor = new PersonaFisicaJuridica(); $emisor ->setNif('B12345678') ->setRazonSocial('Empresa Ejemplo SL'); $invoice->setTercero($emisor); $destinatarios = []; $destinatario1 = new IDOtro(); $destinatario1->setNombreRazon('Cliente 1 SL'); $destinatarios[] = $destinatario1; $invoice->setDestinatarios($destinatarios); // Add breakdown $desglose = new Desglose(); $desglose->setDesgloseFactura([ 'Impuesto' => '01', 'ClaveRegimen' => '01', 'CalificacionOperacion' => 'S1', 'BaseImponibleOimporteNoSujeto' => 1000.00, 'TipoImpositivo' => 21, 'CuotaRepercutida' => 210.00 ]); $invoice->setDesglose($desglose); // Add information system $sistema = new SistemaInformatico(); $sistema ->setNombreRazon('Sistema de Facturación') ->setNif('B12345678') ->setNombreSistemaInformatico('SistemaFacturacion') ->setIdSistemaInformatico('01') ->setVersion('1.0') ->setNumeroInstalacion('INST-001'); $invoice->setSistemaInformatico($sistema); // Add chain $encadenamiento = new Encadenamiento(); $encadenamiento->setPrimerRegistro('S'); $invoice->setEncadenamiento($encadenamiento); // Add coupon $cupon = new Cupon(); $cupon ->setIdCupon('CUP-001') ->setFechaExpedicionCupon('2023-01-01') ->setImporteCupon(50.00) ->setDescripcionCupon('Descuento promocional'); // $invoice->setCupon($cupon); $xml = $invoice->toXmlString(); $xslt = new VerifactuDocumentValidator($xml); $xslt->validate(); $errors = $xslt->getVerifactuErrors(); if(count($errors) > 0) { nlog($xml); nlog($errors); } $this->assertCount(0, $errors); // Test deserialization $deserialized = Invoice::fromXml($xml); nlog($deserialized->toXmlString()); $this->assertEquals($invoice->getIdVersion(), $deserialized->getIdVersion()); $this->assertEquals($invoice->getIdFactura(), $deserialized->getIdFactura()); $this->assertEquals($invoice->getNombreRazonEmisor(), $deserialized->getNombreRazonEmisor()); $this->assertEquals($invoice->getTipoFactura(), $deserialized->getTipoFactura()); $this->assertEquals($invoice->getDescripcionOperacion(), $deserialized->getDescripcionOperacion()); $this->assertEquals($invoice->getCuotaTotal(), $deserialized->getCuotaTotal()); $this->assertEquals($invoice->getImporteTotal(), $deserialized->getImporteTotal()); } public function testCreateAndSerializeCompleteInvoice(): void { $invoice = new Invoice(); $invoice ->setIdVersion('1.0') ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura()) ->setIdEmisorFactura('B12345678') ->setNumSerieFactura('FAC-2023-001') ->setFechaExpedicionFactura('01-01-2023')) ->setRefExterna('REF-123') ->setNombreRazonEmisor('Empresa Ejemplo SL') ->setTipoFactura('F1') ->setDescripcionOperacion('Venta de productos varios') ->setCuotaTotal(210.00) ->setImporteTotal(1000.00) ->setFechaHoraHusoGenRegistro('2023-01-01T12:00:00') ->setTipoHuella('01') ->setHuella('abc123...'); // Add emitter $emisor = new PersonaFisicaJuridica(); $emisor ->setNif('B12345678') ->setRazonSocial('Empresa Ejemplo SL'); $invoice->setTercero($emisor); // Add breakdown $desglose = new Desglose(); $desglose->setDesgloseFactura([ 'Impuesto' => '01', 'ClaveRegimen' => '01', 'CalificacionOperacion' => 'S1', 'BaseImponibleOimporteNoSujeto' => 1000.00, 'TipoImpositivo' => 21, 'CuotaRepercutida' => 210.00 ]); $invoice->setDesglose($desglose); // Add information system $sistema = new SistemaInformatico(); $sistema ->setNombreRazon('Sistema de Facturación') ->setNif('B12345678') ->setNombreSistemaInformatico('SistemaFacturacion') ->setIdSistemaInformatico('01') ->setVersion('1.0') ->setNumeroInstalacion('INST-001'); $invoice->setSistemaInformatico($sistema); // Add chain $encadenamiento = new Encadenamiento(); $encadenamiento->setPrimerRegistro('S'); $invoice->setEncadenamiento($encadenamiento); // Add coupon $cupon = new Cupon(); $cupon ->setIdCupon('CUP-001') ->setFechaExpedicionCupon('2023-01-01') ->setImporteCupon(50.00) ->setDescripcionCupon('Descuento promocional'); // $invoice->setCupon($cupon); $xml = $invoice->toXmlString(); $xslt = new VerifactuDocumentValidator($xml); $xslt->validate(); $errors = $xslt->getVerifactuErrors(); if(count($errors) > 0) { nlog($xml); nlog($errors); } $this->assertCount(0, $errors); // Test deserialization $deserialized = Invoice::fromXml($xml); $this->assertEquals($invoice->getIdVersion(), $deserialized->getIdVersion()); $this->assertEquals($invoice->getIdFactura(), $deserialized->getIdFactura()); $this->assertEquals($invoice->getNombreRazonEmisor(), $deserialized->getNombreRazonEmisor()); $this->assertEquals($invoice->getTipoFactura(), $deserialized->getTipoFactura()); $this->assertEquals($invoice->getDescripcionOperacion(), $deserialized->getDescripcionOperacion()); $this->assertEquals($invoice->getCuotaTotal(), $deserialized->getCuotaTotal()); $this->assertEquals($invoice->getImporteTotal(), $deserialized->getImporteTotal()); } public function testCreateAndSerializeSimplifiedInvoice(): void { $invoice = new Invoice(); $invoice ->setIdVersion('1.0') ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura()) ->setIdEmisorFactura('B12345678') ->setNumSerieFactura('FAC-2023-002') ->setFechaExpedicionFactura('01-01-2023')) ->setNombreRazonEmisor('Empresa Ejemplo SL') ->setTipoFactura('F2') ->setFacturaSimplificadaArt7273('S') ->setDescripcionOperacion('Venta de productos varios') ->setCuotaTotal(21.00) ->setImporteTotal(100.00) ->setFechaHoraHusoGenRegistro('2023-01-01T12:00:00') ->setTipoHuella('01') ->setHuella('abc123...'); // Add breakdown $desglose = new Desglose(); $desglose->setDesgloseIVA([ 'Impuesto' => '01', 'ClaveRegimen' => '02', 'CalificacionOperacion' => 'S2', 'BaseImponibleOimporteNoSujeto' => 100.00, 'TipoImpositivo' => 21, 'CuotaRepercutida' => 21.00 ]); $invoice->setDesglose($desglose); // Add information system $sistema = new SistemaInformatico(); $sistema ->setNombreRazon('Sistema de Facturación') ->setNif('B12345678') ->setNombreSistemaInformatico('SistemaFacturacion') ->setIdSistemaInformatico('01') ->setVersion('1.0') ->setNumeroInstalacion('INST-001') ->setTipoUsoPosibleSoloVerifactu('S') ->setTipoUsoPosibleMultiOT('S') ->setIndicadorMultiplesOT('S'); $invoice->setSistemaInformatico($sistema); // Add encadenamiento $encadenamiento = new Encadenamiento(); $encadenamiento->setPrimerRegistro('S'); $invoice->setEncadenamiento($encadenamiento); $xml = $invoice->toXmlString(); $xslt = new VerifactuDocumentValidator($xml); $xslt->validate(); $errors = $xslt->getVerifactuErrors(); if (count($errors) > 0) { nlog($xml); nlog($errors); } $this->assertCount(0, $errors); // Test deserialization $deserialized = Invoice::fromXml($xml); $this->assertEquals($invoice->getIdVersion(), $deserialized->getIdVersion()); $this->assertEquals($invoice->getIdFactura(), $deserialized->getIdFactura()); $this->assertEquals($invoice->getNombreRazonEmisor(), $deserialized->getNombreRazonEmisor()); $this->assertEquals($invoice->getTipoFactura(), $deserialized->getTipoFactura()); } public function testCreateAndSerializeInvoiceWithoutRecipient(): void { $invoice = new Invoice(); $invoice ->setIdVersion('1.0') ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura()) ->setIdEmisorFactura('B12345678') ->setNumSerieFactura('FAC-2023-004') ->setFechaExpedicionFactura('01-01-2023')) ->setNombreRazonEmisor('Empresa Ejemplo SL') ->setTipoFactura('F1') ->setDescripcionOperacion('Venta de productos varios') ->setCuotaTotal(21.00) ->setImporteTotal(100.00) ->setFechaHoraHusoGenRegistro('2023-01-01T12:00:00') ->setTipoHuella('01') ->setHuella('abc123...'); // Add information system $sistema = new SistemaInformatico(); $sistema ->setNombreRazon('Sistema de Facturación') ->setNif('B12345678') ->setNombreSistemaInformatico('SistemaFacturacion') ->setIdSistemaInformatico('01') ->setVersion('1.0') ->setNumeroInstalacion('INST-001'); $invoice->setSistemaInformatico($sistema); // Add desglose with correct key names $desglose = new Desglose(); $desglose->setDesgloseIVA([ 'Impuesto' => '01', 'ClaveRegimen' => '02', 'CalificacionOperacion' => 'S2', 'BaseImponibleOimporteNoSujeto' => 100.00, 'TipoImpositivo' => 21.00, 'CuotaRepercutida' => 21.00 ]); $invoice->setDesglose($desglose); // Add encadenamiento $encadenamiento = new Encadenamiento(); $encadenamiento->setPrimerRegistro('S'); $invoice->setEncadenamiento($encadenamiento); $xml = $invoice->toXmlString(); $xslt = new VerifactuDocumentValidator($xml); $xslt->validate(); $errors = $xslt->getVerifactuErrors(); if (count($errors) > 0) { nlog($xml); nlog($errors); } $this->assertCount(0, $errors); // Test deserialization $deserialized = Invoice::fromXml($xml); $this->assertEquals($invoice->getIdVersion(), $deserialized->getIdVersion()); $this->assertEquals($invoice->getIdFactura(), $deserialized->getIdFactura()); $this->assertEquals($invoice->getNombreRazonEmisor(), $deserialized->getNombreRazonEmisor()); $this->assertEquals($invoice->getTipoFactura(), $deserialized->getTipoFactura()); $this->assertEquals($invoice->getCuotaTotal(), $deserialized->getCuotaTotal()); $this->assertEquals($invoice->getImporteTotal(), $deserialized->getImporteTotal()); } public function testInvalidXmlThrowsException(): void { $this->expectException(\DOMException::class); $invalidXml = ''; Invoice::fromXml($invalidXml); } public function testMissingRequiredFieldsThrowsException(): void { $invoice = new Invoice(); $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Missing required field: IDVersion'); $invoice->toXmlString(); } public function testCreateAndSerializeInvoiceWithMultipleRecipients(): void { $invoice = new Invoice(); $invoice ->setIdVersion('1.0') ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura()) ->setIdEmisorFactura('B12345678') ->setNumSerieFactura('FAC-2023-005') ->setFechaExpedicionFactura('01-01-2023')) ->setNombreRazonEmisor('Empresa Ejemplo SL') ->setTipoFactura('F1') ->setDescripcionOperacion('Venta a múltiples destinatarios') ->setCuotaTotal(42.00) ->setImporteTotal(200.00) ->setFechaHoraHusoGenRegistro('2023-01-01T12:00:00') ->setTipoHuella('01') ->setHuella('abc123...'); // Add multiple recipients $destinatarios = []; $destinatario1 = new PersonaFisicaJuridica(); $destinatario1 ->setNif('B87654321') ->setNombreRazon('Cliente 1 SL'); $destinatarios[] = $destinatario1; $destinatario2 = new PersonaFisicaJuridica(); $destinatario2 ->setPais('FR') ->setTipoIdentificacion('02') ->setNif('FR1235678') ->setNombreRazon('Client 2 SARL'); $destinatarios[] = $destinatario2; $invoice->setDestinatarios($destinatarios); // Add desglose with proper structure and correct key names $desglose = new Desglose(); $desglose->setDesgloseIVA([ 'Impuesto' => '01', 'ClaveRegimen' => '01', 'CalificacionOperacion' => 'S1', 'BaseImponibleOimporteNoSujeto' => 200.00, 'TipoImpositivo' => 21.00, 'CuotaRepercutida' => 42.00 ]); $invoice->setDesglose($desglose); // Add encadenamiento (required) $encadenamiento = new Encadenamiento(); $encadenamiento->setPrimerRegistro('S'); $invoice->setEncadenamiento($encadenamiento); // Add sistema informatico $sistema = new SistemaInformatico(); $sistema ->setNombreRazon('Sistema de Facturación') ->setNif('B12345678') ->setNombreSistemaInformatico('SistemaFacturacion') ->setIdSistemaInformatico('01') ->setVersion('1.0') ->setNumeroInstalacion('INST-001') ->setTipoUsoPosibleSoloVerifactu('S') ->setTipoUsoPosibleMultiOT('S') ->setIndicadorMultiplesOT('S'); $invoice->setSistemaInformatico($sistema); // Generate XML string $xml = $invoice->toXmlString(); $xslt = new VerifactuDocumentValidator($xml); $xslt->validate(); $errors = $xslt->getVerifactuErrors(); if (count($errors) > 0) { nlog($xml); nlog($errors); } $this->assertCount(0, $errors); // Test deserialization $deserialized = Invoice::fromXml($xml); $this->assertEquals(2, count($deserialized->getDestinatarios())); // Verify first recipient (with NIF) $this->assertEquals('Cliente 1 SL', $deserialized->getDestinatarios()[0]->getNombreRazon()); $this->assertEquals('B87654321', $deserialized->getDestinatarios()[0]->getNif()); // Verify second recipient (with IDOtro) $this->assertEquals('Client 2 SARL', $deserialized->getDestinatarios()[1]->getNombreRazon()); } public function testCreateAndSerializeInvoiceWithExemptOperation(): void { $invoice = new Invoice(); $invoice ->setIdVersion('1.0') ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura()) ->setIdEmisorFactura('B12345678') ->setNumSerieFactura('FAC-2023-006') ->setFechaExpedicionFactura('01-01-2023')) ->setNombreRazonEmisor('Empresa Ejemplo SL') ->setTipoFactura('F1') ->setDescripcionOperacion('Operación exenta de IVA') ->setCuotaTotal(0.00) ->setImporteTotal(100.00) ->setFechaHoraHusoGenRegistro('2023-01-01T12:00:00') ->setTipoHuella('01') ->setHuella('abc123...'); // Add desglose with exempt operation $desglose = new Desglose(); $desglose->setDesgloseIVA([ 'Impuesto' => '01', 'ClaveRegimen' => '01', 'CalificacionOperacion' => 'N1', 'BaseImponibleOimporteNoSujeto' => 100.00, 'TipoImpositivo' => 0, 'CuotaRepercutida' => 0.00 ]); $invoice->setDesglose($desglose); // Add encadenamiento (required) $encadenamiento = new Encadenamiento(); $encadenamiento->setPrimerRegistro('S'); $invoice->setEncadenamiento($encadenamiento); // Add sistema informatico $sistema = new SistemaInformatico(); $sistema ->setNombreRazon('Sistema de Facturación') ->setNif('B12345678') ->setNombreSistemaInformatico('SistemaFacturacion') ->setIdSistemaInformatico('01') ->setVersion('1.0') ->setNumeroInstalacion('INST-001') ->setTipoUsoPosibleSoloVerifactu('S') ->setTipoUsoPosibleMultiOT('S') ->setIndicadorMultiplesOT('S'); $invoice->setSistemaInformatico($sistema); // Generate XML string $xml = $invoice->toXmlString(); // Debug output // echo "\nGenerated XML:\n"; // echo $xml; // echo "\n\n"; $xslt = new VerifactuDocumentValidator($xml); $xslt->validate(); $errors = $xslt->getVerifactuErrors(); if(count($errors) > 0) { nlog($xml); nlog($errors); } $this->assertCount(0, $errors); // Test deserialization $deserialized = Invoice::fromXml($xml); $this->assertEquals(0.00, $deserialized->getCuotaTotal()); $this->assertEquals(100.00, $deserialized->getImporteTotal()); } public function testCreateAndSerializeInvoiceWithDifferentTaxRates(): void { $invoice = new Invoice(); $invoice ->setIdVersion('1.0') ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura()) ->setIdEmisorFactura('B12345678') ->setNumSerieFactura('FAC-2023-007') ->setFechaExpedicionFactura('01-01-2023')) ->setNombreRazonEmisor('Empresa Ejemplo SL') ->setTipoFactura('F1') ->setDescripcionOperacion('Venta con diferentes tipos impositivos') ->setCuotaTotal(31.50) ->setImporteTotal(250.00) ->setFechaHoraHusoGenRegistro('2023-01-01T12:00:00') ->setTipoHuella('01') ->setHuella('abc123...'); // Add desglose with multiple tax rates $desglose = new Desglose(); $desglose->setDesgloseIVA([ [ 'Impuesto' => '01', 'ClaveRegimen' => '01', 'CalificacionOperacion' => 'S1', 'BaseImponibleOimporteNoSujeto' => 100.00, 'TipoImpositivo' => 21.00, 'CuotaRepercutida' => 21.00 ], [ 'Impuesto' => '01', 'ClaveRegimen' => '01', 'CalificacionOperacion' => 'S1', 'BaseImponibleOimporteNoSujeto' => 150.00, 'TipoImpositivo' => 7.00, 'CuotaRepercutida' => 10.50 ] ]); $invoice->setDesglose($desglose); // Add encadenamiento (required) $encadenamiento = new Encadenamiento(); $encadenamiento->setPrimerRegistro('S'); $invoice->setEncadenamiento($encadenamiento); // Add sistema informatico $sistema = new SistemaInformatico(); $sistema ->setNombreRazon('Sistema de Facturación') ->setNif('B12345678') ->setNombreSistemaInformatico('SistemaFacturacion') ->setIdSistemaInformatico('01') ->setVersion('1.0') ->setNumeroInstalacion('INST-001') ->setTipoUsoPosibleSoloVerifactu('S') ->setTipoUsoPosibleMultiOT('S') ->setIndicadorMultiplesOT('S'); $invoice->setSistemaInformatico($sistema); // Generate XML string $xml = $invoice->toXmlString(); // Debug output // echo "\nGenerated XML:\n"; // echo $xml; // echo "\n\n"; $xslt = new VerifactuDocumentValidator($xml); $xslt->validate(); $errors = $xslt->getVerifactuErrors(); if(count($errors) > 0) { nlog($xml); nlog($errors); } $this->assertCount(0, $errors); // Test deserialization $deserialized = Invoice::fromXml($xml); $this->assertEquals(31.50, $deserialized->getCuotaTotal()); $this->assertEquals(250.00, $deserialized->getImporteTotal()); } public function testCreateAndSerializeInvoiceWithSubsequentChain(): void { $invoice = new Invoice(); $invoice ->setIdVersion('1.0') ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura()) ->setIdEmisorFactura('B12345678') ->setNumSerieFactura('FAC-2023-008') ->setFechaExpedicionFactura('01-01-2023')) ->setNombreRazonEmisor('Empresa Ejemplo SL') ->setTipoFactura('F1') ->setDescripcionOperacion('Factura con encadenamiento posterior') ->setCuotaTotal(21.00) ->setImporteTotal(100.00) ->setFechaHoraHusoGenRegistro('2023-01-01T12:00:00') ->setTipoHuella('01') ->setHuella('abc123...'); // Add desglose with proper structure $desglose = new Desglose(); $desglose->setDesgloseIVA([ 'Impuesto' => '01', 'ClaveRegimen' => '01', 'CalificacionOperacion' => 'S1', 'BaseImponible' => 100.00, 'TipoImpositivo' => 21, 'Cuota' => 21.00 ]); $invoice->setDesglose($desglose); // Add encadenamiento with subsequent chain $encadenamiento = new Encadenamiento(); $encadenamiento->setPrimerRegistro('S'); $invoice->setEncadenamiento($encadenamiento); // Add sistema informatico with all required fields $sistema = new SistemaInformatico(); $sistema ->setNombreRazon('Sistema de Facturación') ->setNif('B12345678') ->setNombreSistemaInformatico('SistemaFacturacion') ->setIdSistemaInformatico('01') ->setVersion('1.0') ->setNumeroInstalacion('INST-001') ->setTipoUsoPosibleSoloVerifactu('S') ->setTipoUsoPosibleMultiOT('S') ->setIndicadorMultiplesOT('S'); $invoice->setSistemaInformatico($sistema); // Generate XML string $xml = $invoice->toXmlString(); // Debug output // echo "\nGenerated XML:\n"; // echo $xml; // echo "\n\n"; $xslt = new VerifactuDocumentValidator($xml); $xslt->validate(); $errors = $xslt->getVerifactuErrors(); if(count($errors) > 0) { nlog($xml); nlog($errors); } $this->assertCount(0, $errors); // Test deserialization $deserialized = Invoice::fromXml($xml); $this->assertEquals('S', $deserialized->getEncadenamiento()->getPrimerRegistro()); } public function testCreateAndSerializeInvoiceWithThirdPartyIssuer(): void { $invoice = new Invoice(); $invoice ->setIdVersion('1.0') ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura()) ->setIdEmisorFactura('B12345678') ->setNumSerieFactura('FAC-2023-009') ->setFechaExpedicionFactura('01-01-2023')) ->setNombreRazonEmisor('Empresa Ejemplo SL') ->setTipoFactura('F1') ->setDescripcionOperacion('Factura emitida por tercero') ->setEmitidaPorTerceroODestinatario('T') ->setCuotaTotal(21.00) ->setImporteTotal(100.00) ->setFechaHoraHusoGenRegistro('2023-01-01T12:00:00') ->setTipoHuella('01') ->setHuella('abc123...'); // Set up the third party issuer $tercero = new PersonaFisicaJuridica(); $tercero ->setNif('B98765432') ->setRazonSocial('Tercero Emisor SL'); $invoice->setTercero($tercero); // Add desglose $desglose = new Desglose(); $desglose->setDesgloseIVA([ 'Impuesto' => '01', 'ClaveRegimen' => '02', 'CalificacionOperacion' => 'S2', 'BaseImponible' => 100.00, 'TipoImpositivo' => 21, 'Cuota' => 21.00 ]); $invoice->setDesglose($desglose); // Add encadenamiento (required) $encadenamiento = new Encadenamiento(); $encadenamiento->setPrimerRegistro('S'); $invoice->setEncadenamiento($encadenamiento); // Add sistema informatico with all required fields $sistema = new SistemaInformatico(); $sistema ->setNombreRazon('Sistema de Facturación') ->setNif('B12345678') ->setNombreSistemaInformatico('SistemaFacturacion') ->setIdSistemaInformatico('01') ->setVersion('1.0') ->setNumeroInstalacion('INST-001') ->setTipoUsoPosibleSoloVerifactu('S') ->setTipoUsoPosibleMultiOT('S') ->setIndicadorMultiplesOT('S'); $invoice->setSistemaInformatico($sistema); // Generate XML string $xml = $invoice->toXmlString(); // Debug output // echo "\nGenerated XML:\n"; // echo $xml; // echo "\n\n"; $xslt = new VerifactuDocumentValidator($xml); $xslt->validate(); $errors = $xslt->getVerifactuErrors(); if(count($errors) > 0) { nlog($xml); nlog($errors); } $this->assertCount(0, $errors); // Test deserialization $deserialized = Invoice::fromXml($xml); $this->assertEquals('T', $deserialized->getEmitidaPorTerceroODestinatario()); $this->assertEquals('B98765432', $deserialized->getTercero()->getNif()); $this->assertEquals('Tercero Emisor SL', $deserialized->getTercero()->getRazonSocial()); } public function testCreateAndSerializeInvoiceWithMacroData(): void { $invoice = new Invoice(); $invoice ->setIdVersion('1.0') ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura()) ->setIdEmisorFactura('B12345678') ->setNumSerieFactura('FAC-2023-010') ->setFechaExpedicionFactura('01-01-2023')) ->setNombreRazonEmisor('Empresa Ejemplo SL') ->setTipoFactura('F1') ->setMacrodato('S') ->setDescripcionOperacion('Factura con macrodato') ->setCuotaTotal(21.00) ->setImporteTotal(100.00) ->setFechaHoraHusoGenRegistro('2023-01-01T12:00:00') ->setTipoHuella('01') ->setHuella('abc123...'); // Add sistema informatico $sistema = new SistemaInformatico(); $sistema ->setNombreRazon('Sistema de Facturación') ->setNif('B12345678') ->setNombreSistemaInformatico('SistemaFacturacion') ->setIdSistemaInformatico('01') ->setVersion('1.0') ->setNumeroInstalacion('INST-001') ->setTipoUsoPosibleSoloVerifactu('S') ->setTipoUsoPosibleMultiOT('S') ->setIndicadorMultiplesOT('S'); $invoice->setSistemaInformatico($sistema); // Add Desglose $desglose = new Desglose(); $desglose->setDesgloseIVA([ 'Impuesto' => '01', 'ClaveRegimen' => '01', 'CalificacionOperacion' => 'S1', 'BaseImponible' => 100.00, 'TipoImpositivo' => 21.00, 'Cuota' => 21.00 ]); $invoice->setDesglose($desglose); // Add Encadenamiento $encadenamiento = new Encadenamiento(); $encadenamiento->setPrimerRegistro('S'); $invoice->setEncadenamiento($encadenamiento); // Generate XML string $xml = $invoice->toXmlString(); // Debug output // echo "\nGenerated XML:\n"; // echo $xml; // echo "\n\n"; $xslt = new VerifactuDocumentValidator($xml); $xslt->validate(); $errors = $xslt->getVerifactuErrors(); if(count($errors) > 0) { nlog($xml); nlog($errors); } $this->assertCount(0, $errors); // Test deserialization $deserialized = Invoice::fromXml($xml); $this->assertEquals('S', $deserialized->getMacrodato()); } public function testCreateAndSerializeInvoiceWithAgreementData(): void { $invoice = new Invoice(); $invoice->setIdVersion('1.0') ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura()) ->setIdEmisorFactura('B12345678') ->setNumSerieFactura('FAC-2023-012') ->setFechaExpedicionFactura('01-01-2023')) ->setNombreRazonEmisor('Empresa Ejemplo SL') ->setTipoFactura('F1') ->setDescripcionOperacion('Factura con datos de acuerdo') ->setCuotaTotal(21) ->setImporteTotal(100) ->setFechaHoraHusoGenRegistro('2023-01-01T12:00:00') ->setTipoHuella('01') ->setHuella('abc123...') ->setNumRegistroAcuerdoFacturacion('REG-001') ->setIdAcuerdoSistemaInformatico('AGR-001'); // Set up Desglose $desglose = new Desglose(); $desglose->setDesgloseIVA([ 'Impuesto' => '01', 'ClaveRegimen' => '02', 'CalificacionOperacion' => 'S2', 'BaseImponible' => 100.00, 'TipoImpositivo' => 21.00, 'Cuota' => 21.00 ]); $invoice->setDesglose($desglose); // Set up Encadenamiento $encadenamiento = new Encadenamiento(); $encadenamiento->setPrimerRegistro('S'); $invoice->setEncadenamiento($encadenamiento); // Set up SistemaInformatico $sistema = new SistemaInformatico(); $sistema->setNombreRazon('Sistema de Facturación') ->setNIF('B12345678') ->setNombreSistemaInformatico('SistemaFacturacion') ->setIdSistemaInformatico('01') ->setVersion('1.0') ->setNumeroInstalacion('INST-001') ->setTipoUsoPosibleSoloVerifactu('S') ->setTipoUsoPosibleMultiOT('S') ->setIndicadorMultiplesOT('S'); $invoice->setSistemaInformatico($sistema); $xml = $invoice->toXmlString(); $xslt = new VerifactuDocumentValidator($xml); $xslt->validate(); $errors = $xslt->getVerifactuErrors(); if(count($errors) > 0) { nlog($xml); nlog($errors); } $this->assertCount(0, $errors); // Deserialize and verify $deserializedInvoice = Invoice::fromXml($xml); $this->assertEquals('REG-001', $deserializedInvoice->getNumRegistroAcuerdoFacturacion()); $this->assertEquals('AGR-001', $deserializedInvoice->getIdAcuerdoSistemaInformatico()); } public function testCreateAndSerializeInvoiceWithRejectionAndCorrection() { $invoice = new Invoice(); $invoice->setIdVersion('1.0') ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura()) ->setIdEmisorFactura('B12345678') ->setNumSerieFactura('FAC-2023-013') ->setFechaExpedicionFactura('01-01-2023')) ->setNombreRazonEmisor('Empresa Ejemplo SL') ->setSubsanacion('S') ->setRechazoPrevio('S') ->setTipoFactura('F1') ->setDescripcionOperacion('Factura con rechazo y subsanación') ->setCuotaTotal(21) ->setImporteTotal(100) ->setFechaHoraHusoGenRegistro('2023-01-01T12:00:00') ->setTipoHuella('01') ->setHuella('abc123...'); // Add proper Desglose $desglose = new Desglose(); $desglose->setDesgloseFactura([ 'Impuesto' => '01', 'ClaveRegimen' => '02', 'CalificacionOperacion' => 'S2', 'TipoImpositivo' => 21.00, 'BaseImponible' => 100.00, 'Cuota' => 21.00 ]); $invoice->setDesglose($desglose); // Add proper Encadenamiento $encadenamiento = new Encadenamiento(); $encadenamiento->setPrimerRegistro('S'); $invoice->setEncadenamiento($encadenamiento); // Add SistemaInformatico $sistemaInformatico = new SistemaInformatico(); $sistemaInformatico->setNombreRazon('Sistema de Facturación') ->setNif('B12345678') ->setNombreSistemaInformatico('SistemaFacturacion') ->setIdSistemaInformatico('01') ->setVersion('1.0') ->setNumeroInstalacion('INST-001') ->setTipoUsoPosibleSoloVerifactu('S') ->setTipoUsoPosibleMultiOT('S') ->setIndicadorMultiplesOT('S'); $invoice->setSistemaInformatico($sistemaInformatico); $xml = $invoice->toXmlString(); $this->assertNotEmpty($xml); $xslt = new VerifactuDocumentValidator($xml); $xslt->validate(); $errors = $xslt->getVerifactuErrors(); if(count($errors) > 0) { nlog($xml); nlog($errors); } $this->assertCount(0, $errors); // Test deserialization $deserializedInvoice = Invoice::fromXml($xml); $this->assertEquals('S', $deserializedInvoice->getSubsanacion()); $this->assertEquals('S', $deserializedInvoice->getRechazoPrevio()); } public function testCreateAndSerializeInvoiceWithOperationDate(): void { $invoice = new Invoice(); $invoice ->setIdVersion('1.0') ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura()) ->setIdEmisorFactura('B12345678') ->setNumSerieFactura('FAC-2023-014') ->setFechaExpedicionFactura('01-01-2023')) ->setNombreRazonEmisor('Empresa Ejemplo SL') ->setTipoFactura('F1') ->setDescripcionOperacion('Factura con fecha de operación') ->setFechaOperacion('2023-01-01') ->setCuotaTotal(21.00) ->setImporteTotal(100.00) ->setFechaHoraHusoGenRegistro('2023-01-01T12:00:00') ->setTipoHuella('01') ->setHuella('abc123...'); // Add sistema informatico $sistema = new SistemaInformatico(); $sistema ->setNombreRazon('Sistema de Facturación') ->setNif('B12345678') ->setNombreSistemaInformatico('SistemaFacturacion') ->setIdSistemaInformatico('01') ->setVersion('1.0') ->setNumeroInstalacion('INST-001'); $invoice->setSistemaInformatico($sistema); // Add Desglose $desglose = new Desglose(); $desglose->setDesgloseFactura([ 'Impuesto' => '01', 'ClaveRegimen' => '02', 'CalificacionOperacion' => 'S2', 'TipoImpositivo' => '21.00', 'BaseImponibleOimporteNoSujeto' => '100.00', 'CuotaRepercutida' => '21.00' ]); $invoice->setDesglose($desglose); // Add Encadenamiento $encadenamiento = new Encadenamiento(); $encadenamiento->setPrimerRegistro('S'); $invoice->setEncadenamiento($encadenamiento); // Generate XML string $xml = $invoice->toXmlString(); // // Debug output // echo "\nGenerated XML:\n"; // echo $xml; // echo "\n\n"; $xslt = new VerifactuDocumentValidator($xml); $xslt->validate(); $errors = $xslt->getVerifactuErrors(); if(count($errors) > 0) { nlog($xml); nlog($errors); } $this->assertCount(0, $errors); // Test deserialization $deserialized = Invoice::fromXml($xml); $this->assertEquals('01-01-2023', $deserialized->getFechaOperacion()); } public function testCreateAndSerializeInvoiceWithCoupon(): void { $invoice = new Invoice(); $invoice ->setIdVersion('1.0') ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura()) ->setIdEmisorFactura('B12345678') ->setNumSerieFactura('FAC-2023-015') ->setFechaExpedicionFactura('01-01-2023')) ->setNombreRazonEmisor('Empresa Ejemplo SL') ->setTipoFactura('F1') ->setDescripcionOperacion('Factura con cupón') ->setCupon('S') // Set cupon to 'S' to indicate it has a coupon ->setCuotaTotal(21.00) ->setImporteTotal(100.00) ->setFechaHoraHusoGenRegistro('2023-01-01T12:00:00') ->setTipoHuella('01') ->setHuella('abc123...'); // Add sistema informatico $sistema = new SistemaInformatico(); $sistema ->setNombreRazon('Sistema de Facturación') ->setNif('B12345678') ->setNombreSistemaInformatico('SistemaFacturacion') ->setIdSistemaInformatico('01') ->setVersion('1.0') ->setNumeroInstalacion('INST-001'); $invoice->setSistemaInformatico($sistema); // Add Desglose $desglose = new Desglose(); $desglose->setDesgloseFactura([ 'Impuesto' => '01', 'ClaveRegimen' => '01', 'CalificacionOperacion' => 'S1', 'TipoImpositivo' => '21.00', 'BaseImponibleOimporteNoSujeto' => '100.00', 'CuotaRepercutida' => '21.00' ]); $invoice->setDesglose($desglose); // Add Encadenamiento $encadenamiento = new Encadenamiento(); $encadenamiento->setPrimerRegistro('S'); $invoice->setEncadenamiento($encadenamiento); // Generate XML string $xml = $invoice->toXmlString(); // Debug output // echo "\nGenerated XML:\n"; // echo $xml; // echo "\n\n"; $xslt = new VerifactuDocumentValidator($xml); $xslt->validate(); $errors = $xslt->getVerifactuErrors(); if(count($errors) > 0) { nlog($xml); nlog($errors); } $this->assertCount(0, $errors); // Test deserialization $deserialized = Invoice::fromXml($xml); $this->assertNotNull($deserialized->getCupon()); $this->assertEquals('S', $deserialized->getCupon()); } public function testInvalidTipoFacturaThrowsException(): void { $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Invalid TipoFactura value'); $invoice = new Invoice(); $invoice ->setIdVersion('1.0') ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura()) ->setIdEmisorFactura('B12345678') ->setNumSerieFactura('FAC-2023-016') ->setFechaExpedicionFactura('01-01-2023')) ->setNombreRazonEmisor('Empresa Ejemplo SL') ->setTipoFactura('INVALID'); // This should throw the exception immediately } public function testInvalidTipoRectificativaThrowsException(): void { $this->expectException(\InvalidArgumentException::class); $invoice = new Invoice(); $invoice ->setIdVersion('1.0') ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura()) ->setIdEmisorFactura('B12345678') ->setNumSerieFactura('FAC-2023-017') ->setFechaExpedicionFactura('01-01-2023')) ->setNombreRazonEmisor('Empresa Ejemplo SL') ->setTipoFactura('R1') ->setTipoRectificativa('INVALID') // Invalid type ->setDescripcionOperacion('Rectificación inválida') ->setCuotaTotal(-21.00) ->setImporteTotal(-100.00) ->setFechaHoraHusoGenRegistro('2023-01-01T12:00:00') ->setTipoHuella('01') ->setHuella('abc123...'); // Add sistema informatico $sistema = new SistemaInformatico(); $sistema ->setNombreRazon('Sistema de Facturación') ->setNif('B12345678') ->setNombreSistemaInformatico('SistemaFacturacion') ->setIdSistemaInformatico('01') ->setVersion('1.0') ->setNumeroInstalacion('INST-001'); $invoice->setSistemaInformatico($sistema); $invoice->toXmlString(); } public function testInvalidNIFFormatThrowsException(): void { $this->expectException(\InvalidArgumentException::class); $invoice = new Invoice(); $invoice ->setIdVersion('1.0') ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura()) ->setIdEmisorFactura('B12345678') ->setNumSerieFactura('FAC-2023-019') ->setFechaExpedicionFactura('01-01-2023')) ->setNombreRazonEmisor('Empresa Ejemplo SL') ->setTipoFactura('F1') ->setDescripcionOperacion('Factura con NIF inválido') ->setCuotaTotal(21.00) ->setImporteTotal(100.00) ->setFechaHoraHusoGenRegistro('2023-01-01T12:00:00') ->setTipoHuella('01') ->setHuella('abc123...'); // Add emitter with invalid NIF $emisor = new PersonaFisicaJuridica(); $emisor ->setNif('INVALID_NIF') ->setRazonSocial('Empresa Ejemplo SL'); $invoice->setTercero($emisor); // Add sistema informatico $sistema = new SistemaInformatico(); $sistema ->setNombreRazon('Sistema de Facturación') ->setNif('B12345678') ->setNombreSistemaInformatico('SistemaFacturacion') ->setIdSistemaInformatico('01') ->setVersion('1.0') ->setNumeroInstalacion('INST-001'); $invoice->setSistemaInformatico($sistema); $invoice->toXmlString(); } public function testInvalidAmountFormatThrowsException(): void { $this->expectException(\InvalidArgumentException::class); $invoice = new Invoice(); $invoice ->setIdVersion('1.0') ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura()) ->setIdEmisorFactura('B12345678') ->setNumSerieFactura('FAC-2023-020') ->setFechaExpedicionFactura('01-01-2023')) ->setNombreRazonEmisor('Empresa Ejemplo SL') ->setTipoFactura('F1') ->setDescripcionOperacion('Factura con importe inválido') ->setCuotaTotal(21.00) ->setImporteTotal('INVALID') // Invalid format ->setFechaHoraHusoGenRegistro('2023-01-01T12:00:00') ->setTipoHuella('01') ->setHuella('abc123...'); // Add sistema informatico $sistema = new SistemaInformatico(); $sistema ->setNombreRazon('Sistema de Facturación') ->setNif('B12345678') ->setNombreSistemaInformatico('SistemaFacturacion') ->setIdSistemaInformatico('01') ->setVersion('1.0') ->setNumeroInstalacion('INST-001'); $invoice->setSistemaInformatico($sistema); $invoice->toXmlString(); } public function testInvalidSchemaThrowsException(): void { $this->expectException(\DOMException::class); $invoice = new Invoice(); $invoice->setIdVersion('1.0') ->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura()) ->setIdEmisorFactura('B12345678') ->setNumSerieFactura('TEST123') ->setFechaExpedicionFactura('01-01-2023')) ->setNombreRazonEmisor('Test Company') ->setTipoFactura('F1') ->setDescripcionOperacion('Test Operation') ->setCuotaTotal(100.00) ->setImporteTotal(121.00) ->setFechaHoraHusoGenRegistro(date('Y-m-d\TH:i:s')) ->setTipoHuella('01') ->setHuella('abc123...'); // Add required sistema informatico with valid values $sistema = new SistemaInformatico(); $sistema->setNombreRazon('Test System') ->setNif('B12345678') ->setNombreSistemaInformatico('Test Software') ->setIdSistemaInformatico('01') ->setVersion('1.0') ->setNumeroInstalacion('001'); $invoice->setSistemaInformatico($sistema); // Add required desglose with DetalleDesglose $desglose = new Desglose(); $detalleDesglose = new DetalleDesglose(); $detalleDesglose->setDesgloseIVA([ 'Impuesto' => '01', 'ClaveRegimen' => '01', 'BaseImponible' => 100.00, 'TipoImpositivo' => 21.00, 'Cuota' => 21.00 ]); $desglose->setDetalleDesglose($detalleDesglose); $invoice->setDesglose($desglose); // Add required encadenamiento $encadenamiento = new Encadenamiento(); $encadenamiento->setPrimerRegistro('S'); $invoice->setEncadenamiento($encadenamiento); // Generate valid XML first $validXml = $invoice->toXmlString(); // Create a new document with the valid XML $doc = new \DOMDocument(); $doc->loadXML($validXml); // Add an invalid element to trigger schema validation error $invalidElement = $doc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'sum1:InvalidElement'); $invalidElement->textContent = 'test'; $doc->documentElement->appendChild($invalidElement); // Try to validate the invalid XML using our validateXml method $reflectionClass = new \ReflectionClass(Invoice::class); $validateXmlMethod = $reflectionClass->getMethod('validateXml'); $validateXmlMethod->setAccessible(true); $validateXmlMethod->invoke(new Invoice(), $doc); $xslt = new XsltDocumentValidator($validXml); $xslt->validate(); $this->assertCount(0, $xslt->getErrors()); } protected function assertXmlEquals(string $expectedXml, string $actualXml): void { $this->assertEquals( $this->normalizeXml($expectedXml), $this->normalizeXml($actualXml) ); } protected function normalizeXml(string $xml): string { $doc = new \DOMDocument('1.0'); $doc->preserveWhiteSpace = false; $doc->formatOutput = true; if (!$doc->loadXML($xml)) { throw new \DOMException('Failed to load XML in normalizeXml'); } return $doc->saveXML(); } protected function assertValidatesAgainstXsd(string $xml, string $xsdPath): void { try { $doc = new \DOMDocument(); $doc->preserveWhiteSpace = false; $doc->formatOutput = true; if (!$doc->loadXML($xml, LIBXML_NOBLANKS)) { throw new \DOMException('Failed to load XML in assertValidatesAgainstXsd'); } libxml_use_internal_errors(true); $result = $doc->schemaValidate($xsdPath); if (!$result) { foreach (libxml_get_errors() as $error) { } libxml_clear_errors(); } $this->assertTrue( $result, 'XML does not validate against XSD schema' ); } catch (\Exception $e) { throw $e; } } protected function getTestXsdPath(): string { return __DIR__ . '/../schema/SuministroInformacion.xsd'; } }