Working on encoding

This commit is contained in:
David Bomba 2025-05-05 11:35:29 +10:00
parent 6bb576b949
commit b7754d1b8f
6 changed files with 316 additions and 85 deletions

View File

@ -2,23 +2,225 @@
namespace App\Services\EDocument\Standards; namespace App\Services\EDocument\Standards;
use Carbon\Carbon;
use App\Models\Invoice; use App\Models\Invoice;
use BaconQrCode\Writer;
use App\Services\AbstractService; use App\Services\AbstractService;
use BaconQrCode\Renderer\ImageRenderer;
use Symfony\Component\Serializer\Serializer;
use BaconQrCode\Renderer\Image\SvgImageBackEnd;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use App\Services\EDocument\Standards\Verifactu\Types\Cabecera;
use App\Services\EDocument\Standards\Verifactu\Types\SoapBody;
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
use App\Services\EDocument\Standards\Verifactu\VerifactuClient; use App\Services\EDocument\Standards\Verifactu\VerifactuClient;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use App\Services\EDocument\Standards\Verifactu\Types\RegistroAlta;
use App\Services\EDocument\Standards\Verifactu\Types\SoapEnvelope;
use App\Services\EDocument\Standards\Verifactu\Types\ObligadoEmision;
use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter;
use App\Services\EDocument\Standards\Verifactu\InvoiceninjaToVerifactuMapper; use App\Services\EDocument\Standards\Verifactu\InvoiceninjaToVerifactuMapper;
use App\Services\EDocument\Standards\Verifactu\Types\RegFactuSistemaFacturacion;
class Verifactu extends AbstractService class Verifactu
{ {
public function __construct(private Invoice $invoice) public function __construct(private Invoice $invoice)
{ {
} }
public function run() public function send(mixed $registro)
{ {
$client = new VerifactuClient(VerifactuClient::MODE_PROD); $client = new VerifactuClient(VerifactuClient::MODE_PROD);
$mapper = new InvoiceninjaToVerifactuMapper(); $response =$client->sendRegistroAlta($this->getRegistroAlta());
$regFacAlta = $mapper->mapRegistroFacturacionAlta($this->invoice);
$response = $client->sendRegistroAlta($regFacAlta);
var_dump($response); var_dump($response);
}
public function getRegistroAlta(): string
{
$mapper = new InvoiceninjaToVerifactuMapper();
$regAlta = $mapper->mapRegistroAlta($this->invoice);
$soapEnvelope = new SoapEnvelope();
$soapBody = new SoapBody();
$RegFactuSistemaFacturacion = new RegFactuSistemaFacturacion();
$cabecera = new Cabecera();
$obligadoEmision = new ObligadoEmision();
$obligadoEmision->setNombreRazon($this->invoice->company->present()->name());
$obligadoEmision->setNIF($this->invoice->company->settings->vat_number);
$cabecera->setObligadoEmision($obligadoEmision);
$RegFactuSistemaFacturacion->setCabecera($cabecera);
$RegFactuSistemaFacturacion->setRegistroAlta($regAlta);
$soapBody->setRegFactuSistemaFacturacion($RegFactuSistemaFacturacion);
$soapEnvelope->setBody($soapBody);
nlog($soapEnvelope);
return $this->serializeXml($soapEnvelope);
}
public function serializeXml(RegistroAlta $registro): string
{
$serializer = $this->getSerializer();
$context = [\Symfony\Component\Serializer\Normalizer\DateTimeNormalizer::FORMAT_KEY => 'd-m-Y', \Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer::SKIP_NULL_VALUES => true];
$object = $serializer->normalize($registro, null, [\Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer::SKIP_NULL_VALUES => true]);
$object = $this->removeEmptyValues($object);
$data = $serializer->encode($object, 'xml', $context);
$data = str_replace(['<response>','</response>'], '', $data);
$data = preg_replace("/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/", "\n", $data);
return $data;
}
/**
* getQrCode
*
* @return string
*/
public function getQrCode(): string
{
//testmode
$base_url = 'https://prewww2.aeat.es/wlpl/TIKE-CONT/ValidarQR';
//production
// $base_url = https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR
// Format date to dd-mm-yyyy
$fecha = \Carbon\Carbon::parse($this->invoice->date)->format('d-m-Y');
// Format amount to 2 decimal places without thousands separator
$importe = number_format($this->invoice->amount, 2, '.', '');
// NIF is required for the QR code, if no NIF is present we use 00000000H
if(strlen($this->invoice->client->vat_number) > 2 && $this->invoice->client->country->iso_3166_2 === 'ES') {
$nif = $this->invoice->client->vat_number;
} else {
$nif = '00000000H'; //unknown / foreign client
}
$params = [
'nif' => $nif,
'numserie' => $this->invoice->number,
'fecha' => $fecha,
'importe' => $importe,
];
// Build URL with properly encoded parameters
$query = http_build_query($params);
try {
$renderer = new ImageRenderer(
new RendererStyle(200),
new SvgImageBackEnd()
);
$writer = new Writer($renderer);
$qr = $writer->writeString($base_url . '?' . $query, 'utf-8');
return htmlspecialchars("<svg viewBox='0 0 200 200' width='200' height='200' x='0' y='0' xmlns='http://www.w3.org/2000/svg'>
<rect x='0' y='0' width='100%'' height='100%' />{$qr}</svg>");
} catch (\Throwable $e) {
nlog(" QR failure => ".$e->getMessage());
return '';
}
}
/**
* getSerializer
*
* Returns the Symfony Serializer
*
* @return Serializer
*/
public function getSerializer(): Serializer
{
$phpDocExtractor = new PhpDocExtractor();
$reflectionExtractor = new ReflectionExtractor();
$typeExtractors = [$reflectionExtractor, $phpDocExtractor];
$descriptionExtractors = [$phpDocExtractor];
$propertyInitializableExtractors = [$reflectionExtractor];
$propertyInfo = new PropertyInfoExtractor(
$propertyInitializableExtractors,
$descriptionExtractors,
$typeExtractors,
);
$classMetadataFactory = new ClassMetadataFactory(new AttributeLoader());
$metadataAwareNameConverter = new MetadataAwareNameConverter($classMetadataFactory);
$normalizer = new ObjectNormalizer($classMetadataFactory, $metadataAwareNameConverter, null, $propertyInfo);
$normalizers = [new DateTimeNormalizer(), $normalizer, new ArrayDenormalizer()];
$namespaces = [
'soapenv' => 'http://schemas.xmlsoap.org/soap/envelope/',
'sum' => 'https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd',
'sum1' => 'https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd',
'xd' => 'http://www.w3.org/2000/09/xmldsig#'
];
$encoders = [
new XmlEncoder([
'xml_root_node_name' => 'soapenv:Envelope',
'xml_format_output' => true,
AbstractObjectNormalizer::SKIP_NULL_VALUES => true,
'xml_encoding' => 'UTF-8',
'xml_version' => '1.0',
'namespace_prefix_map' => $namespaces,
'default_root_ns' => 'soapenv',
'xml_namespaces' => array_combine(
array_map(fn($prefix) => "xmlns:$prefix", array_keys($namespaces)),
array_values($namespaces)
)
]),
new JsonEncoder()
];
return new Serializer($normalizers, $encoders);
}
/**
* removeEmptyValues
*
* Removes empty values from the array
*
* @param array $array
* @return array
*/
private function removeEmptyValues(array $array): array
{
foreach ($array as $key => $value) {
if (is_array($value)) {
$array[$key] = $this->removeEmptyValues($value);
if (empty($array[$key])) {
unset($array[$key]);
}
} elseif ($value === null || $value === '') {
unset($array[$key]);
}
}
return $array;
} }
} }

View File

@ -16,7 +16,6 @@ use App\Services\EDocument\Standards\Verifactu\Types\Destinatarios;
use App\Services\EDocument\Standards\Verifactu\Types\IDDestinatario; use App\Services\EDocument\Standards\Verifactu\Types\IDDestinatario;
use App\Services\EDocument\Standards\Verifactu\Types\IDFacturaExpedida; use App\Services\EDocument\Standards\Verifactu\Types\IDFacturaExpedida;
use App\Services\EDocument\Standards\Verifactu\Types\PersonaFisicaJuridica; use App\Services\EDocument\Standards\Verifactu\Types\PersonaFisicaJuridica;
use App\Services\EDocument\Standards\Verifactu\Types\RegistroFacturacionAlta;
class InvoiceninjaToVerifactuMapper class InvoiceninjaToVerifactuMapper
{ {
@ -62,46 +61,46 @@ class InvoiceninjaToVerifactuMapper
* When generateing R type invoices, we will always use values * When generateing R type invoices, we will always use values
* that substitute the original invoice, this requires settings * that substitute the original invoice, this requires settings
* *
* $registroFacturacionAlta->setTipoRectificativa('S'); // for Substitutive * $registroAlta->setTipoRectificativa('S'); // for Substitutive
*/ */
public function mapRegistroFacturacionAlta(Invoice $invoice): RegistroAlta // Registration Entry public function mapRegistroAlta(Invoice $invoice): RegistroAlta // Registration Entry
{ {
$registroFacturacionAlta = new RegistroAlta(); // Registration Entry $registroAlta = new RegistroAlta(); // Registration Entry
// Set version // Set version
$registroFacturacionAlta->setIDVersion('1.0'); $registroAlta->setIDVersion('1.0');
// Set invoice ID (IDFactura) // Set invoice ID (IDFactura)
$idFactura = new IDFactura(); // Issued Invoice ID $idFactura = new IDFactura(); // Issued Invoice ID
$idFactura->setIDEmisorFactura($invoice->company->settings->vat_number); // Invoice Issuer ID $idFactura->setIDEmisorFactura($invoice->company->settings->vat_number); // Invoice Issuer ID
$idFactura->setNumSerieFactura($invoice->number); // Invoice Serial Number $idFactura->setNumSerieFactura($invoice->number); // Invoice Serial Number
$idFactura->setFechaExpedicionFactura(\Carbon\Carbon::parse($invoice->date)->format('d-m-Y')); // Invoice Issue Date $idFactura->setFechaExpedicionFactura(\Carbon\Carbon::parse($invoice->date)->format('d-m-Y')); // Invoice Issue Date
$registroFacturacionAlta->setIDFactura($idFactura); $registroAlta->setIDFactura($idFactura);
// Set external reference (RefExterna) - The clients reference for this document - typically the PO Number, only apply if we have one. // Set external reference (RefExterna) - The clients reference for this document - typically the PO Number, only apply if we have one.
if(strlen($invoice->po_number) > 1) { if(strlen($invoice->po_number) > 1) {
$registroFacturacionAlta->setRefExterna($invoice->po_number); $registroAlta->setRefExterna($invoice->po_number);
} }
// Set issuer name (NombreRazonEmisor) // Set issuer name (NombreRazonEmisor)
$registroFacturacionAlta->setNombreRazonEmisor($invoice->company->present()->name()); $registroAlta->setNombreRazonEmisor($invoice->company->present()->name());
// Set correction and previous rejection (Subsanacion y RechazoPrevio) // Set correction and previous rejection (Subsanacion y RechazoPrevio)
//@todo we need to have logic surrounding these two fields if the are applicable to the current doc //@todo we need to have logic surrounding these two fields if the are applicable to the current doc
//@todo these _are_ optional fields //@todo these _are_ optional fields
// $registroFacturacionAlta->setSubsanacion('Subsanacion::VALUE_N'); // Correction // $registroAlta->setSubsanacion('Subsanacion::VALUE_N'); // Correction
// $registroFacturacionAlta->setRechazoPrevio('RechazoPrevio::VALUE_N'); // Previous Rejection // $registroAlta->setRechazoPrevio('RechazoPrevio::VALUE_N'); // Previous Rejection
// Set invoice type (TipoFactura) // Set invoice type (TipoFactura)
$registroFacturacionAlta->setTipoFactura($this->getInvoiceType($invoice)); $registroAlta->setTipoFactura($this->getInvoiceType($invoice));
// Delivery Date of the goods or services (we force invoice->date for this.) // Delivery Date of the goods or services (we force invoice->date for this.)
$registroFacturacionAlta->setFechaOperacion(\Carbon\Carbon::parse($invoice->date)->format('d-m-Y')); $registroAlta->setFechaOperacion(\Carbon\Carbon::parse($invoice->date)->format('d-m-Y'));
// Description of the operation (we use invoice->public_notes) BUT only if it's not empty // Description of the operation (we use invoice->public_notes) BUT only if it's not empty
if(strlen($invoice->public_notes ?? '') > 0) { if(strlen($invoice->public_notes ?? '') > 0) {
$registroFacturacionAlta->setDescripcionOperacion($invoice->public_notes); $registroAlta->setDescripcionOperacion($invoice->public_notes);
} }
// Set recipients (Destinatarios) // Set recipients (Destinatarios)
@ -120,7 +119,7 @@ class InvoiceninjaToVerifactuMapper
} }
$destinatarios->addIDDestinatario($destinatario); $destinatarios->addIDDestinatario($destinatario);
$registroFacturacionAlta->setDestinatarios($destinatarios); $registroAlta->setDestinatarios($destinatarios);
// Set breakdown (Desglose) MAXIMUM 12 Line items!!!!!!!! // Set breakdown (Desglose) MAXIMUM 12 Line items!!!!!!!!
$desglose = new Desglose(); // Breakdown $desglose = new Desglose(); // Breakdown
@ -134,21 +133,21 @@ class InvoiceninjaToVerifactuMapper
$desglose->addToDetalleDesglose($detalle); $desglose->addToDetalleDesglose($detalle);
} }
$registroFacturacionAlta->setDesglose($desglose); $registroAlta->setDesglose($desglose);
// Set total amounts (CuotaTotal e ImporteTotal) // Set total amounts (CuotaTotal e ImporteTotal)
$registroFacturacionAlta->setCuotaTotal($invoice->total_taxes); //@todo this is not correct $registroAlta->setCuotaTotal($invoice->total_taxes); //@todo this is not correct
$registroFacturacionAlta->setImporteTotal($invoice->amount); //@todo this is not correct $registroAlta->setImporteTotal($invoice->amount); //@todo this is not correct
// Set fingerprint type and value (TipoHuella y Huella) // Set fingerprint type and value (TipoHuella y Huella)
$registroFacturacionAlta->setTipoHuella('01'); $registroAlta->setTipoHuella('01');
// Set generation date (FechaHoraHusoGenRegistro) // Set generation date (FechaHoraHusoGenRegistro)
$registroFacturacionAlta->setFechaHoraHusoGenRegistro(\Carbon\Carbon::now()->format('Y-m-d\TH:i:sP')); //@todo set the timezone to the company locale $registroAlta->setFechaHoraHusoGenRegistro(\Carbon\Carbon::now()->format('Y-m-d\TH:i:sP')); //@todo set the timezone to the company locale
$registroFacturacionAlta->setHuella($this->getHash($invoice, $registroFacturacionAlta)); // Digital Fingerprint $registroAlta->setHuella($this->getHash($invoice, $registroAlta)); // Digital Fingerprint
return $registroFacturacionAlta; return $registroAlta;
} }
/** /**

View File

@ -6,36 +6,36 @@ use Symfony\Component\Serializer\Annotation\SerializedName;
class Encadenamiento class Encadenamiento
{ {
/** @var IDFacturaAR */ /** @var RegistroAnterior */
#[SerializedName('sum1:RegistroAnterior')] #[SerializedName('sum1:RegistroAnterior')]
protected $RegistroAnterior; protected $RegistroAnterior;
/** @var string */ /** @var string */
#[SerializedName('sum1:HuellaRegistroAnterior')] #[SerializedName('sum1:PrimerRegistro')]
protected $HuellaRegistroAnterior; protected $PrimerRegistro;
public function getRegistroAnterior(): IDFacturaAR public function getRegistroAnterior(): RegistroAnterior
{ {
return $this->RegistroAnterior; return $this->RegistroAnterior;
} }
public function setRegistroAnterior(IDFacturaAR $registroAnterior): self public function setRegistroAnterior(RegistroAnterior $registroAnterior): self
{ {
$this->RegistroAnterior = $registroAnterior; $this->RegistroAnterior = $registroAnterior;
return $this; return $this;
} }
public function getHuellaRegistroAnterior(): string public function getPrimerRegistro(): string
{ {
return $this->HuellaRegistroAnterior; return $this->PrimerRegistro;
} }
public function setHuellaRegistroAnterior(string $huellaRegistroAnterior): self public function setPrimerRegistro(string $primerRegistro): self
{ {
if (strlen($huellaRegistroAnterior) > 64) { if (strlen($primerRegistro) > 64) {
throw new \InvalidArgumentException('HuellaRegistroAnterior must not exceed 64 characters'); throw new \InvalidArgumentException('HuellaRegistroAnterior must not exceed 64 characters');
} }
$this->HuellaRegistroAnterior = $huellaRegistroAnterior; $this->PrimerRegistro = $primerRegistro;
return $this; return $this;
} }
} }

View File

@ -7,42 +7,51 @@ use Symfony\Component\Serializer\Annotation\SerializedName;
class RegistroAnterior class RegistroAnterior
{ {
/** @var string */ /** @var string */
#[SerializedName('sum1:NumRegistroAcuerdoFacturacion')] #[SerializedName('sum1:IDEmisorFactura')]
protected $NumRegistroAcuerdoFacturacion; protected $IDEmisorFactura;
/** @var string */ /** @var string */
#[SerializedName('sum1:FechaHoraHusoGenRegistro')] #[SerializedName('sum1:NumSerieFactura')]
protected $FechaHoraHusoGenRegistro; protected $NumSerieFactura;
/** @var string */
#[SerializedName('sum1:FechaExpedicionFactura')]
protected $FechaExpedicionFactura;
/** @var string */ /** @var string */
#[SerializedName('sum1:Huella')] #[SerializedName('sum1:Huella')]
protected $Huella; protected $Huella;
public function getNumRegistroAcuerdoFacturacion(): string public function getIDEmisorFactura(): string
{ {
return $this->NumRegistroAcuerdoFacturacion; return $this->IDEmisorFactura;
} }
public function setNumRegistroAcuerdoFacturacion(string $numRegistroAcuerdoFacturacion): self public function setIDEmisorFactura(string $IDEmisorFactura): self
{ {
if (strlen($numRegistroAcuerdoFacturacion) > 15) { $this->IDEmisorFactura = $IDEmisorFactura;
throw new \InvalidArgumentException('NumRegistroAcuerdoFacturacion must not exceed 15 characters');
}
$this->NumRegistroAcuerdoFacturacion = $numRegistroAcuerdoFacturacion;
return $this; return $this;
} }
public function getFechaHoraHusoGenRegistro(): string public function getNumSerieFactura(): string
{ {
return $this->FechaHoraHusoGenRegistro; return $this->NumSerieFactura;
} }
public function setFechaHoraHusoGenRegistro(string $fechaHoraHusoGenRegistro): self public function setNumSerieFactura(string $NumSerieFactura): self
{ {
if (!preg_match('/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}$/', $fechaHoraHusoGenRegistro)) { $this->NumSerieFactura = $NumSerieFactura;
throw new \InvalidArgumentException('FechaHoraHusoGenRegistro must be in ISO 8601 format with timezone (e.g. 2024-09-13T19:20:30+01:00)'); return $this;
} }
$this->FechaHoraHusoGenRegistro = $fechaHoraHusoGenRegistro;
public function getFechaExpedicionFactura(): string
{
return $this->FechaExpedicionFactura;
}
public function setFechaExpedicionFactura(string $FechaExpedicionFactura): self
{
$this->FechaExpedicionFactura = $FechaExpedicionFactura;
return $this; return $this;
} }
@ -51,12 +60,9 @@ class RegistroAnterior
return $this->Huella; return $this->Huella;
} }
public function setHuella(string $huella): self public function setHuella(string $Huella): self
{ {
if (strlen($huella) > 64) { $this->Huella = $Huella;
throw new \InvalidArgumentException('Huella must not exceed 64 characters');
}
$this->Huella = $huella;
return $this; return $this;
} }
} }

View File

@ -2,11 +2,14 @@
namespace App\Services\EDocument\Standards\Verifactu\Types; namespace App\Services\EDocument\Standards\Verifactu\Types;
use Symfony\Component\Serializer\Annotation\XmlRoot;
use Symfony\Component\Serializer\Annotation\SerializedName; use Symfony\Component\Serializer\Annotation\SerializedName;
use App\Services\EDocument\Standards\Verifactu\Types\SoapBody;
use App\Services\EDocument\Standards\Verifactu\Types\SoapHeader;
class SoapEnvelope class SoapEnvelope
{ {
/** @var SoapHeader */ /** @var SoapHeader */
#[SerializedName('soapenv:Header')] #[SerializedName('soapenv:Header')]
protected $Header; protected $Header;
@ -14,7 +17,7 @@ class SoapEnvelope
#[SerializedName('soapenv:Body')] #[SerializedName('soapenv:Body')]
protected $Body; protected $Body;
public function getHeader(): SoapHeader public function getHeader(): ?SoapHeader
{ {
return $this->Header; return $this->Header;
} }
@ -25,7 +28,7 @@ class SoapEnvelope
return $this; return $this;
} }
public function getBody(): SoapBody public function getBody(): ?SoapBody
{ {
return $this->Body; return $this->Body;
} }

View File

@ -1,10 +1,16 @@
<?php <?php
namespace Tests\Feature\Verifactu; namespace Tests\Feature\Verifactu;
use PHPUnit\Framework\TestCase; use Tests\TestCase;
use App\Models\Invoice;
use Tests\MockAccountData;
use App\Helpers\Invoice\InvoiceSum;
use Database\Factories\InvoiceFactory;
use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\Serializer;
use App\Services\EDocument\Standards\Verifactu;
use Symfony\Component\Serializer\Encoder\XmlEncoder; use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
@ -12,6 +18,7 @@ use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use App\Services\EDocument\Standards\Verifactu\Types\RegistroAlta;
use App\Services\EDocument\Standards\Verifactu\Types\SoapEnvelope; use App\Services\EDocument\Standards\Verifactu\Types\SoapEnvelope;
use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
@ -20,46 +27,60 @@ use App\Services\EDocument\Standards\Verifactu\Types\RegFactuSistemaFacturacion;
class SerializerTest extends TestCase class SerializerTest extends TestCase
{ {
use MockAccountData;
use DatabaseTransactions;
public $invoice;
public $invoice_calc;
protected function setUp(): void
{
parent::setUp();
$this->makeTestData();
$this->invoice->line_items = $this->buildLineItems();
$this->invoice->uses_inclusive_taxes = true;
$this->invoice_calc = new InvoiceSum($this->invoice);
}
public function testDeserialize() public function testDeserialize()
{ {
$document = file_get_contents(__DIR__ . '/invoice.xml'); $document = file_get_contents(__DIR__ . '/invoice.xml');
$phpDocExtractor = new PhpDocExtractor();
$reflectionExtractor = new ReflectionExtractor();
// list of PropertyTypeExtractorInterface (any iterable) $verifactu = new Verifactu($this->invoice);
$typeExtractors = [$reflectionExtractor,$phpDocExtractor];
// list of PropertyDescriptionExtractorInterface (any iterable) $serializer = $verifactu->getSerializer();
$descriptionExtractors = [$phpDocExtractor];
// list of PropertyInitializableExtractorInterface (any iterable) $parent_class = SoapEnvelope::class;
$propertyInitializableExtractors = [$reflectionExtractor];
$propertyInfo = new PropertyInfoExtractor( $invoice = $serializer->deserialize($document, $parent_class, 'xml', [\Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer::SKIP_NULL_VALUES => true]);
$propertyInitializableExtractors, nlog($invoice);
$descriptionExtractors, $this->assertInstanceOf(SoapEnvelope::class, $invoice);
$typeExtractors,
);
$classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); }
$metadataAwareNameConverter = new MetadataAwareNameConverter($classMetadataFactory); public function testSerializeXml()
{
$normalizer = new ObjectNormalizer($classMetadataFactory, $metadataAwareNameConverter, null, $propertyInfo); $document = file_get_contents(__DIR__ . '/invoice-alta.xml');
$verifactu = new Verifactu($this->invoice);
$normalizers = [new DateTimeNormalizer(), $normalizer, new ArrayDenormalizer()]; $serializer = $verifactu->getSerializer();
$encoders = [new XmlEncoder(['xml_format_output' => true,\Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer::SKIP_NULL_VALUES => true]), new JsonEncoder()];
$serializer = new Serializer($normalizers, $encoders);
$parent_class = SoapEnvelope::class; $parent_class = SoapEnvelope::class;
$invoice = $serializer->deserialize($document, $parent_class, 'xml', [\Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer::SKIP_NULL_VALUES => true]); $invoice = $serializer->deserialize($document, $parent_class, 'xml', [\Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer::SKIP_NULL_VALUES => true]);
$this->assertInstanceOf(SoapEnvelope::class, $invoice); nlog($invoice);
$xml = $verifactu->serializeXml($invoice);
nlog($xml);
$this->assertStringContainsString('RegistroAlta', $xml);
} }
} }