Working on encoding
This commit is contained in:
parent
6bb576b949
commit
b7754d1b8f
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue