Compare commits

...

25 Commits

Author SHA1 Message Date
David Bomba 91a5457ab9
Merge pull request #10996 from 2hy6v/2025_05_01_verifactu_tests
InvoiceSchemaTestsAeat.php added
2025-06-02 04:24:15 +10:00
Daniel Barduhn 8ea919f191 InvoiceSchemaTestsAeat.php added 2025-05-24 12:16:21 +02:00
David Bomba 15033f581b Minor fixes 2025-05-05 14:32:53 +10:00
David Bomba 89266be7ae Tests 2025-05-05 14:13:00 +10:00
David Bomba 5f9ff21edf Attributes for parent type 2025-05-05 14:05:08 +10:00
David Bomba b7754d1b8f Working on encoding 2025-05-05 11:35:29 +10:00
David Bomba 6bb576b949 Fixes for verifactu 2025-05-05 10:34:08 +10:00
David Bomba f1b374ee38 Update mapper class, line by line! 2025-05-05 10:09:39 +10:00
David Bomba e641825b3a Type definitions 2025-05-05 08:48:25 +10:00
David Bomba de690a8be5 Fixes for tests 2025-05-05 08:24:27 +10:00
David Bomba 41b2387643 Fixes for tests 2025-05-05 08:22:07 +10:00
David Bomba 1dee59bafa Updated getters/setters 2025-05-05 08:07:45 +10:00
David Bomba 81c8380e0c
Merge pull request #10941 from 2hy6v/2025_05_01_verifactu_soap_service
2025 05 01 verifactu soap service
2025-05-04 09:54:52 +10:00
Daniel Barduhn 7b47634213 edited mapper, client and verifactu 2025-05-03 20:50:45 +02:00
David Bomba ef4f474f12 Fixes for tests 2025-05-02 18:06:54 +10:00
David Bomba b25a69a5be Updates for Verifactu 2025-05-02 18:06:26 +10:00
Daniel Barduhn e7a63237f2 added missing classmap entries 2025-05-01 12:06:44 +02:00
Daniel Barduhn e4295332b9 initial idea of soap client 2025-05-01 11:55:53 +02:00
David Bomba f155feb641 Model updates 2025-05-01 19:51:01 +10:00
David Bomba 99875d2f98 Fixes for tests 2025-05-01 18:22:17 +10:00
David Bomba bd4dddda0c Updates for models 2025-05-01 18:19:41 +10:00
David Bomba 0886daa78f Updates for models 2025-05-01 18:03:22 +10:00
David Bomba e86a3b06db Updates for models 2025-05-01 18:00:11 +10:00
David Bomba 794a792b39 Updates for models 2025-05-01 17:40:18 +10:00
David Bomba 2444eacb13 First push, working on models 2025-05-01 17:26:38 +10:00
46 changed files with 7760 additions and 4 deletions

View File

@ -59,8 +59,12 @@ class UpdateExchangeRates implements ShouldQueue
/* Update all currencies */
Currency::all()->each(function ($currency) use ($currency_api) {
if(isset($currency_api->rates->{$currency->code})) {
$currency->exchange_rate = $currency_api->rates->{$currency->code};
$currency->save();
}
});
/* Rebuild the cache */
@ -76,8 +80,12 @@ class UpdateExchangeRates implements ShouldQueue
/* Update all currencies */
Currency::all()->each(function ($currency) use ($currency_api) {
if (isset($currency_api->rates->{$currency->code})) {
$currency->exchange_rate = $currency_api->rates->{$currency->code};
$currency->save();
}
});
/* Rebuild the cache */

View File

@ -0,0 +1,232 @@
<?php
namespace App\Services\EDocument\Standards;
use Carbon\Carbon;
use App\Models\Invoice;
use BaconQrCode\Writer;
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 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\Types\RegFactuSistemaFacturacion;
class Verifactu
{
public function __construct(private Invoice $invoice)
{
}
public function send(mixed $registro)
{
$client = new VerifactuClient(VerifactuClient::MODE_PROD);
$response = $client->sendRegistroAlta($this->getRegistroAlta());
var_dump($response);
}
public function getRegistroAlta(): string
{
$mapper = new InvoiceninjaToVerifactuMapper();
$regAlta = $mapper->mapRegistroAlta($this->invoice);
$soapEnvelope = new SoapEnvelope();
$soapBody = new SoapBody();
$RegFactuSistemaFacturacion = new RegFactuSistemaFacturacion();
//The User or Corp that generated AND sends the invoice (ie Invoice Ninja!)
$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);
//The invoice itself
$RegFactuSistemaFacturacion->setRegistroAlta($regAlta);
$soapBody->setRegFactuSistemaFacturacion($RegFactuSistemaFacturacion);
$soapEnvelope->setBody($soapBody);
return $this->serializeXml($soapEnvelope);
}
public function serializeXml(SoapEnvelope $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

@ -0,0 +1,308 @@
<?php
declare(strict_types=1);
namespace App\Services\EDocument\Standards\Verifactu;
use Carbon\Carbon;
use App\Models\Invoice;
use App\DataMapper\Tax\BaseRule;
use App\Services\EDocument\Standards\Verifactu\Types\IDOtro;
use App\Services\EDocument\Standards\Verifactu\Types\Detalle;
use App\Services\EDocument\Standards\Verifactu\Types\Desglose;
use App\Services\EDocument\Standards\Verifactu\Types\IDFactura;
use App\Services\EDocument\Standards\Verifactu\Types\RegistroAlta;
use App\Services\EDocument\Standards\Verifactu\Types\Destinatarios;
use App\Services\EDocument\Standards\Verifactu\Types\IDDestinatario;
use App\Services\EDocument\Standards\Verifactu\Types\IDFacturaExpedida;
use App\Services\EDocument\Standards\Verifactu\Types\PersonaFisicaJuridica;
class InvoiceninjaToVerifactuMapper
{
public array $rechazo_previo = [
'S', // No previous rejection by AEAT
'N', // Previous rejection
'X', // No previous rejection but the registration does not exist
];
public array $subsanacion = [
'S', // Correction
'N', // No correction
];
/**
* F series invoices are for the ORIGINAL / INITIAL version of the invoice.
* R series invoices are for the CORRECTED version of the invoice.
*
* F1 is a standard invoice. Where the full customer details are provided.
* F2 is a simplified invoice. Where the customer details are not provided.
* F3 is a substitute invoice. Used to replace F2 invoices - we will not implement this!
*
* R1 Corrective invoice for errors in the original invoice.
* R2 Used when customer enters bankruptcy during the invoice lifetime.
* R3 Bad debt invoice for VAT refund.
* R4 General purpose corrective invoice
* R5 Corrective invoice for F2 type invoices.
*
* @var array
*/
public array $invoice_types = [
'F1', // Invoice
'F2', // Simplified Invoice
'F3', // Substitute Invoice
'R1', // Rectification Invoice
'R2', // Rectification Invoice
'R3', // Rectification Invoice
'R4', // Rectification Invoice
'R5', // Rectification Invoice
];
/**
* When generateing R type invoices, we will always use values
* that substitute the original invoice, this requires settings
*
* $registroAlta->setTipoRectificativa('S'); // for Substitutive
*/
public function mapRegistroAlta(Invoice $invoice): RegistroAlta // Registration Entry
{
$registroAlta = new RegistroAlta(); // Registration Entry
// Set version
$registroAlta->setIDVersion('1.0');
// Set invoice ID (IDFactura)
$idFactura = new IDFactura(); // Issued Invoice ID (Invoice)
$idFactura->setIDEmisorFactura($invoice->company->settings->vat_number); // Invoice Issuer ID
$idFactura->setNumSerieFactura($invoice->number); // Invoice Serial Number
$idFactura->setFechaExpedicionFactura(\Carbon\Carbon::parse($invoice->date)->format('d-m-Y')); // Invoice Issue Date
$registroAlta->setIDFactura($idFactura);
// 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) {
$registroAlta->setRefExterna($invoice->po_number);
}
// Set issuer name (NombreRazonEmisor)
$registroAlta->setNombreRazonEmisor($invoice->company->present()->name());
// 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 these _are_ optional fields
// $registroAlta->setSubsanacion('Subsanacion::VALUE_N'); // Correction
// $registroAlta->setRechazoPrevio('RechazoPrevio::VALUE_N'); // Previous Rejection
// Set invoice type (TipoFactura)
$registroAlta->setTipoFactura($this->getInvoiceType($invoice));
// Delivery Date of the goods or services (we force invoice->date for this.)
$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
if(strlen($invoice->public_notes ?? '') > 0) {
$registroAlta->setDescripcionOperacion($invoice->public_notes);
}
// Set recipients (Destinatarios)
$destinatarios = new Destinatarios(); // Recipients
$destinatario = new IDDestinatario(); // Natural/Legal Person
$destinatario->setNombreRazon($invoice->client->present()->name()); // Business Name
// For Spanish clients with a VAT, we just need to set the NIF
if (strlen($invoice->client->vat_number ?? '') > 2 && $invoice->client->country->iso_3166_2 === 'ES') {
$destinatario->setNIF($invoice->client->vat_number); // Tax ID Number
} else {
// For all other clients, we need to set the IDOtro
// this requires some logic to build
$idOtro = $this->buildIdOtro($invoice);
$destinatario->setIDOtro($idOtro);
}
$destinatarios->addIDDestinatario($destinatario);
$registroAlta->setDestinatarios($destinatarios);
// Set breakdown (Desglose) MAXIMUM 12 Line items!!!!!!!!
$desglose = new Desglose(); // Breakdown
foreach($invoice->line_items as $item) {
$detalle = new Detalle(); // Detail
$detalle->setImpuesto('01'); // Tax (IVA) //@todo, need to implement logic for the other tax codes
$detalle->setTipoImpositivo($item->tax_rate1);
$detalle->setBaseImponibleOimporteNoSujeto($item->line_total); // Taxable Base or Non-Taxable Amount
$detalle->setCuotaRepercutida($item->tax_amount); // Charged Tax Amount
$desglose->addToDetalleDesglose($detalle);
}
$registroAlta->setDesglose($desglose);
// Set total amounts (CuotaTotal e ImporteTotal)
$registroAlta->setCuotaTotal($invoice->total_taxes); //@todo this is not correct
$registroAlta->setImporteTotal($invoice->amount); //@todo this is not correct
// Set fingerprint type and value (TipoHuella y Huella)
$registroAlta->setTipoHuella('01');
// Set generation date (FechaHoraHusoGenRegistro)
$registroAlta->setFechaHoraHusoGenRegistro(\Carbon\Carbon::now()->format('Y-m-d\TH:i:sP')); //@todo set the timezone to the company locale
$registroAlta->setHuella($this->getHash($invoice, $registroAlta)); // Digital Fingerprint
return $registroAlta;
}
/**
* getHash
*
* 1. High Billing Record
* The fields to include in the string to calculate the footprint are:
*
* IDEmisorFactura : Identification of the invoice issuer.
* NumSerieFactura : Serial number of the invoice.
* InvoiceIssueDate : Date the invoice was issued.
* TipoFactura : Invoice type code.
* TotalQuota : Total amount of tax quotas.
* TotalAmount : Total amount of the invoice.
* Fingerprint (previous record) : Hash of the immediately preceding billing record (if any).
* DateTimeZoneGenRecord : Date and time of record generation.
*
* 2. Cancellation Billing Record
* In this case, the fields used to generate the hash are:
*
* IDEmisorFacturaAnulada : Identification of the issuer of the cancelled invoice.
* NumSerieFacturaAnulada : Serial number of the cancelled invoice.
* CancelledInvoiceIssueDate : Date of issue of the canceled invoice.
* Fingerprint (previous record) : Hash of the cancelled invoice.
* DateTimeZoneGenRecord : Date and time of record generation.
*
* 3. Event Registration
* For event logs, the data string to be processed includes:
* NIF of the issuer and the person obliged to issue .
* Event ID .
* Identification of the computer system .
* Billing software version .
* Installation number .
* Event type .
* Trace of the previous event (if applicable).
* Date and time of event generation .
*
* Based on the type of record, the hash will need to be calculated differently.
*
* @param Invoice $invoice
* @param RegistroAlta $registroAlta
* @return string
*/
private function getHash(Invoice $invoice, RegistroAlta $registroAlta): string
{
// $hash = '';
// Tipo de factura Invoice type
// Número de factura Invoice number
// Fecha de emisión Date of issue
// NIF del emisor Issuer's Tax Identification Number (NIF)
// NIF del receptor Recipient's Tax Identification Number (NIF)
// Importe total Total amount
// Base imponible Taxable base
// IVA aplicado Applied VAT
// Tipo impositivo Tax rate
// Fecha operación Transaction date
// Descripción operación Description of the transaction
// Serie Invoice series
// Concepto Concept or description of the invoice
$hash = "IDEmisorFactura=" . $registroAlta->getIDFactura()->getIDEmisorFactura() .
"&NumSerieFactura=" . $registroAlta->getIDFactura()->getNumSerieFactura() .
"&FechaExpedicionFactura=" . $registroAlta->getIDFactura()->getFechaExpedicionFactura() .
"&TipoFactura=" . $registroAlta->getTipoFactura() .
"&CuotaTotal=" . $registroAlta->getCuotaTotal() .
"&ImporteTotal=" . $registroAlta->getImporteTotal() .
"&Huella=" . $registroAlta->getHuella() . // Fingerprint of the previous record
"&FechaHoraHusoGenRegistro=" . $registroAlta->getFechaHoraHusoGenRegistro();
$hash = utf8_encode($hash);
$hash = strtoupper(hash('sha256', $hash));
return $hash;
}
/**
* Generate hash for cancellation records
*
* The fields used to generate the hash are:
* - IDEmisorFacturaAnulada: Identification of the issuer of the cancelled invoice
* - NumSerieFacturaAnulada: Serial number of the cancelled invoice
* - FechaExpedicionFacturaAnulada: Date of issue of the canceled invoice
* - Huella: Hash of the cancelled invoice
* - FechaHoraHusoGenRegistro: Date and time of record generation
*/
// private function getHashForCancellation(RegistroFacturacionAnulacion $registroAnulacion): string
// {
// $hash = "IDEmisorFacturaAnulada=" . $registroAnulacion->getIDFactura()->getIDEmisorFactura() .
// "&NumSerieFacturaAnulada=" . $registroAnulacion->getIDFactura()->getNumSerieFactura() .
// "&FechaExpedicionFacturaAnulada=" . $registroAnulacion->getIDFactura()->getFechaExpedicionFactura() .
// "&Huella=" . $registroAnulacion->getHuella() . // Hash of the cancelled invoice //@todo, when we init the doc, we need to set this!!
// "&FechaHoraHusoGenRegistro=" . $registroAnulacion->getFechaHoraHusoGenRegistro();
// $hash = utf8_encode($hash);
// return strtoupper(hash('sha256', $hash));
// }
/**
* getInvoiceType
*
* We do not yet have any UI for this. We'll need to implement UI
* functionality that allows the user to initially select F1/F2
*
* and then on editting, they'll be able to select R1/R2/R3/R4/R5
* be able to select R1/R2/R3/R4/R5
* @return string
*/
private function getInvoiceType(Invoice $invoice): string
{
//@todo we need to have logic surrounding these two fields if the are applicable to the current doc
return match($invoice->status_id) {
Invoice::STATUS_DRAFT => 'F1',
Invoice::STATUS_SENT => 'R4',
Invoice::STATUS_PAID => 'R4',
Invoice::STATUS_OVERDUE => 'R4',
Invoice::STATUS_CANCELLED => 'R4',
default => 'F1',
};
}
/**
* buildIdOtro
*
* Client Identifier mapping
* @param Invoice $invoice
* @return IDOtro
*/
private function buildIdOtro(Invoice $invoice): IDOtro
{
$idOtro = new IDOtro(); // Other ID
$br = new BaseRule();
$eu_countries = $br->eu_country_codes;
$client_country_code = $invoice->client->country->iso_3166_2;
if(in_array($client_country_code, $eu_countries)) {
// Is this B2C or B2B?
if(strlen($invoice->client->vat_number ?? '') > 2) {
$idOtro->setIDType('02'); // VAT Number
$idOtro->setID($invoice->client->vat_number);
} else {
$idOtro->setIDType('04'); // Legal Entity ID
$idOtro->setID($invoice->client->id_number);
}
}
else {
//foreign country
$idOtro->setIDType('03');
$idOtro->setID(strlen($invoice->client->vat_number ?? '') > 2 ? $invoice->client->vat_number : $invoice->client->id_number);
}
return $idOtro;
}
}

View File

@ -0,0 +1,111 @@
<?php
namespace App\Services\EDocument\Standards\Verifactu\Types;
use Symfony\Component\Serializer\Annotation\SerializedName;
class Cabecera
{
/** @var ObligadoEmision */
#[SerializedName('sum1:ObligadoEmision')]
protected $ObligadoEmision;
/** @var PersonaFisicaJuridicaES|null */
#[SerializedName('sum1:Representante')]
protected $Representante;
/** @var array{FechaFinVeriFactu?: string}|null */
#[SerializedName('sum1:RemisionVoluntaria')]
protected $RemisionVoluntaria;
/** @var array{RefRequerimiento: string, FinRequerimiento?: string}|null */
#[SerializedName('sum1:RemisionRequerimiento')]
protected $RemisionRequerimiento;
public function getObligadoEmision(): ObligadoEmision
{
return $this->ObligadoEmision;
}
public function setObligadoEmision(ObligadoEmision $obligadoEmision): self
{
$this->ObligadoEmision = $obligadoEmision;
return $this;
}
public function getRepresentante(): ?PersonaFisicaJuridicaES
{
return $this->Representante;
}
public function setRepresentante(?PersonaFisicaJuridicaES $representante): self
{
$this->Representante = $representante;
return $this;
}
/**
* @return array{FechaFinVeriFactu?: string}|null
*/
public function getRemisionVoluntaria(): ?array
{
return $this->RemisionVoluntaria;
}
/**
* @param array{FechaFinVeriFactu?: string}|null $remisionVoluntaria
*/
public function setRemisionVoluntaria(?array $remisionVoluntaria): self
{
if ($remisionVoluntaria !== null) {
if (isset($remisionVoluntaria['FechaFinVeriFactu'])) {
// Validate date format DD-MM-YYYY
if (!preg_match('/^\d{2}-\d{2}-\d{4}$/', $remisionVoluntaria['FechaFinVeriFactu'])) {
throw new \InvalidArgumentException('FechaFinVeriFactu must be in DD-MM-YYYY format');
}
// Validate date components
list($day, $month, $year) = explode('-', $remisionVoluntaria['FechaFinVeriFactu']);
if (!checkdate((int)$month, (int)$day, (int)$year)) {
throw new \InvalidArgumentException('Invalid date in FechaFinVeriFactu');
}
}
}
$this->RemisionVoluntaria = $remisionVoluntaria;
return $this;
}
/**
* @return array{RefRequerimiento: string, FinRequerimiento?: string}|null
*/
public function getRemisionRequerimiento(): ?array
{
return $this->RemisionRequerimiento;
}
/**
* @param array{RefRequerimiento: string, FinRequerimiento?: string}|null $remisionRequerimiento
*/
public function setRemisionRequerimiento(?array $remisionRequerimiento): self
{
if ($remisionRequerimiento !== null) {
if (strlen($remisionRequerimiento['RefRequerimiento']) > 18) {
throw new \InvalidArgumentException('RefRequerimiento must not exceed 18 characters');
}
if (isset($remisionRequerimiento['FinRequerimiento'])) {
// Validate date format DD-MM-YYYY
if (!preg_match('/^\d{2}-\d{2}-\d{4}$/', $remisionRequerimiento['FinRequerimiento'])) {
throw new \InvalidArgumentException('FinRequerimiento must be in DD-MM-YYYY format');
}
// Validate date components
list($day, $month, $year) = explode('-', $remisionRequerimiento['FinRequerimiento']);
if (!checkdate((int)$month, (int)$day, (int)$year)) {
throw new \InvalidArgumentException('Invalid date in FinRequerimiento');
}
}
}
$this->RemisionRequerimiento = $remisionRequerimiento;
return $this;
}
}

View File

@ -0,0 +1,64 @@
<?php
namespace App\Services\EDocument\Standards\Verifactu\Types\Common;
trait TextTypes
{
protected function validateMaxLength(string $value, int $maxLength, string $fieldName): void
{
if (strlen($value) > $maxLength) {
throw new \InvalidArgumentException("$fieldName must not exceed $maxLength characters");
}
}
protected function validateExactLength(string $value, int $length, string $fieldName): void
{
if (strlen($value) !== $length) {
throw new \InvalidArgumentException("$fieldName must be exactly $length characters long");
}
}
protected function validateNIF(string $nif): void
{
$this->validateExactLength($nif, 9, 'NIF');
// TODO: Add more specific NIF validation rules
}
protected function validateDate(string $date): void
{
if (!preg_match('/^\d{2}-\d{2}-\d{4}$/', $date)) {
throw new \InvalidArgumentException('Date must be in DD-MM-YYYY format');
}
list($day, $month, $year) = explode('-', $date);
if (!checkdate((int)$month, (int)$day, (int)$year)) {
throw new \InvalidArgumentException('Invalid date');
}
}
protected function validateTimestamp(string $timestamp): void
{
if (!preg_match('/^\d{2}-\d{2}-\d{4} \d{2}:\d{2}:\d{2}$/', $timestamp)) {
throw new \InvalidArgumentException('Timestamp must be in DD-MM-YYYY HH:mm:ss format');
}
list($date, $time) = explode(' ', $timestamp);
list($day, $month, $year) = explode('-', $date);
list($hour, $minute, $second) = explode(':', $time);
if (!checkdate((int)$month, (int)$day, (int)$year)) {
throw new \InvalidArgumentException('Invalid date in timestamp');
}
if ($hour > 23 || $minute > 59 || $second > 59) {
throw new \InvalidArgumentException('Invalid time in timestamp');
}
}
protected function validateNumericString(string $value, int $maxIntegerDigits, int $maxDecimalDigits, string $fieldName): void
{
if (!preg_match('/^[+-]?\d{1,' . $maxIntegerDigits . '}(\.\d{0,' . $maxDecimalDigits . '})?$/', $value)) {
throw new \InvalidArgumentException("$fieldName must have at most $maxIntegerDigits digits before decimal point and $maxDecimalDigits after");
}
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Services\EDocument\Standards\Verifactu\Types;
use Symfony\Component\Serializer\Annotation\SerializedName;
class Desglose
{
/** @var DetalleDesglose[] */
#[SerializedName('sum1:DetalleDesglose')]
protected $DetalleDesglose = [];
/**
* @return DetalleDesglose[]
*/
public function getDetalleDesglose(): array
{
return $this->DetalleDesglose;
}
public function addDetalleDesglose(DetalleDesglose $detalleDesglose): self
{
$this->DetalleDesglose[] = $detalleDesglose;
return $this;
}
/**
* @param array<DetalleDesglose> $detalles
*/
public function setDetalleDesglose(array $detalles): self
{
$this->DetalleDesglose = $detalles;
return $this;
}
}

View File

@ -0,0 +1,91 @@
<?php
namespace App\Services\EDocument\Standards\Verifactu\Types;
use Symfony\Component\Serializer\Annotation\SerializedName;
class DesgloseRectificacion
{
/** @var float */
#[SerializedName('sum1:BaseRectificada')]
protected $BaseRectificada;
/** @var float */
#[SerializedName('sum1:CuotaRectificada')]
protected $CuotaRectificada;
/** @var float|null */
#[SerializedName('sum1:CuotaRecargoRectificado')]
protected $CuotaRecargoRectificado;
public function getBaseRectificada(): float
{
return $this->BaseRectificada;
}
public function setBaseRectificada(float $baseRectificada): self
{
// Validate format: max 12 digits before decimal point, 2 after
$parts = explode('.', (string)$baseRectificada);
$integerPart = $parts[0];
$decimalPart = $parts[1] ?? '';
if (strlen($integerPart) > 12) {
throw new \InvalidArgumentException('BaseRectificada must have at most 12 digits before decimal point');
}
if (strlen($decimalPart) > 2) {
throw new \InvalidArgumentException('BaseRectificada must have at most 2 decimal places');
}
$this->BaseRectificada = $baseRectificada;
return $this;
}
public function getCuotaRectificada(): float
{
return $this->CuotaRectificada;
}
public function setCuotaRectificada(float $cuotaRectificada): self
{
// Validate format: max 12 digits before decimal point, 2 after
$parts = explode('.', (string)$cuotaRectificada);
$integerPart = $parts[0];
$decimalPart = $parts[1] ?? '';
if (strlen($integerPart) > 12) {
throw new \InvalidArgumentException('CuotaRectificada must have at most 12 digits before decimal point');
}
if (strlen($decimalPart) > 2) {
throw new \InvalidArgumentException('CuotaRectificada must have at most 2 decimal places');
}
$this->CuotaRectificada = $cuotaRectificada;
return $this;
}
public function getCuotaRecargoRectificado(): ?float
{
return $this->CuotaRecargoRectificado;
}
public function setCuotaRecargoRectificado(?float $cuotaRecargoRectificado): self
{
if ($cuotaRecargoRectificado !== null) {
// Validate format: max 12 digits before decimal point, 2 after
$parts = explode('.', (string)$cuotaRecargoRectificado);
$integerPart = $parts[0];
$decimalPart = $parts[1] ?? '';
if (strlen($integerPart) > 12) {
throw new \InvalidArgumentException('CuotaRecargoRectificado must have at most 12 digits before decimal point');
}
if (strlen($decimalPart) > 2) {
throw new \InvalidArgumentException('CuotaRecargoRectificado must have at most 2 decimal places');
}
}
$this->CuotaRecargoRectificado = $cuotaRecargoRectificado;
return $this;
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace App\Services\EDocument\Standards\Verifactu\Types;
use Symfony\Component\Serializer\Annotation\SerializedName;
class Destinatarios
{
/** @var IDDestinatario[] */
#[SerializedName('sum1:IDDestinatario')]
protected $IDDestinatario = [];
/**
* @return IDDestinatario[]
*/
public function getIDDestinatario(): array
{
return $this->IDDestinatario;
}
public function addIDDestinatario(IDDestinatario $idDestinatario): self
{
if (count($this->IDDestinatario) >= 1000) {
throw new \InvalidArgumentException('Maximum number of IDDestinatario (1000) exceeded');
}
$this->IDDestinatario[] = $idDestinatario;
return $this;
}
/**
* @param array<IDDestinatario> $destinatarios
*/
public function setIDDestinatario(array $destinatarios): self
{
$this->IDDestinatario = $destinatarios;
return $this;
}
}

View File

@ -0,0 +1,253 @@
<?php
namespace App\Services\EDocument\Standards\Verifactu\Types;
use Symfony\Component\Serializer\Annotation\SerializedName;
class Detalle
{
public array $impuestos = [
'01', //IVA
'02', //IPSI (Ceuta y Melilla)
'03', //IGIC (Canarias)
'05', //Otros
];
/** @var string|null */
#[SerializedName('sum1:Impuesto')]
protected $Impuesto;
/** @var string|null */
#[SerializedName('sum1:ClaveRegimen')]
protected $ClaveRegimen;
/** @var string|null */
#[SerializedName('sum1:CalificacionOperacion')]
protected $CalificacionOperacion;
/** @var string|null */
#[SerializedName('sum1:OperacionExenta')]
protected $OperacionExenta;
/** @var float|null */
#[SerializedName('sum1:TipoImpositivo')]
protected $TipoImpositivo;
/** @var float */
#[SerializedName('sum1:BaseImponibleOimporteNoSujeto')]
protected $BaseImponibleOimporteNoSujeto;
/** @var float|null */
#[SerializedName('sum1:BaseImponibleACoste')]
protected $BaseImponibleACoste;
/** @var float|null */
#[SerializedName('sum1:CuotaRepercutida')]
protected $CuotaRepercutida;
/** @var float|null */
#[SerializedName('sum1:TipoRecargoEquivalencia')]
protected $TipoRecargoEquivalencia;
/** @var float|null */
#[SerializedName('sum1:CuotaRecargoEquivalencia')]
protected $CuotaRecargoEquivalencia;
public function getImpuesto(): ?string
{
return $this->Impuesto;
}
public function setImpuesto(?string $impuesto): self
{
$this->Impuesto = $impuesto;
return $this;
}
public function getClaveRegimen(): ?string
{
return $this->ClaveRegimen;
}
public function setClaveRegimen(?string $claveRegimen): self
{
if ($claveRegimen !== null && !preg_match('/^\d{2}$/', $claveRegimen)) {
throw new \InvalidArgumentException('ClaveRegimen must be a 2-digit number');
}
$this->ClaveRegimen = $claveRegimen;
return $this;
}
public function getCalificacionOperacion(): ?string
{
return $this->CalificacionOperacion;
}
public function setCalificacionOperacion(?string $calificacionOperacion): self
{
if ($calificacionOperacion !== null) {
if (!preg_match('/^[A-Z]\d$/', $calificacionOperacion)) {
throw new \InvalidArgumentException('CalificacionOperacion must be a letter followed by a digit');
}
if ($this->OperacionExenta !== null) {
throw new \InvalidArgumentException('Cannot set CalificacionOperacion when OperacionExenta is set');
}
}
$this->CalificacionOperacion = $calificacionOperacion;
return $this;
}
public function getOperacionExenta(): ?string
{
return $this->OperacionExenta;
}
public function setOperacionExenta(?string $operacionExenta): self
{
if ($operacionExenta !== null) {
if (!preg_match('/^[A-Z]\d$/', $operacionExenta)) {
throw new \InvalidArgumentException('OperacionExenta must be a letter followed by a digit');
}
if ($this->CalificacionOperacion !== null) {
throw new \InvalidArgumentException('Cannot set OperacionExenta when CalificacionOperacion is set');
}
}
$this->OperacionExenta = $operacionExenta;
return $this;
}
public function getTipoImpositivo(): ?float
{
return $this->TipoImpositivo;
}
public function setTipoImpositivo(?float $tipoImpositivo): self
{
if ($tipoImpositivo !== null) {
$parts = explode('.', (string)$tipoImpositivo);
$decimalPart = $parts[1] ?? '';
if (strlen($decimalPart) > 2) {
throw new \InvalidArgumentException('TipoImpositivo must have at most 2 decimal places');
}
if ($tipoImpositivo < 0 || $tipoImpositivo > 100) {
throw new \InvalidArgumentException('TipoImpositivo must be between 0 and 100');
}
}
$this->TipoImpositivo = $tipoImpositivo;
return $this;
}
public function getBaseImponibleOimporteNoSujeto(): float
{
return $this->BaseImponibleOimporteNoSujeto;
}
public function setBaseImponibleOimporteNoSujeto(float $baseImponibleOimporteNoSujeto): self
{
$parts = explode('.', (string)$baseImponibleOimporteNoSujeto);
$integerPart = $parts[0];
$decimalPart = $parts[1] ?? '';
if (strlen($integerPart) > 12) {
throw new \InvalidArgumentException('BaseImponibleOimporteNoSujeto must have at most 12 digits before decimal point');
}
if (strlen($decimalPart) > 2) {
throw new \InvalidArgumentException('BaseImponibleOimporteNoSujeto must have at most 2 decimal places');
}
$this->BaseImponibleOimporteNoSujeto = $baseImponibleOimporteNoSujeto;
return $this;
}
public function getBaseImponibleACoste(): ?float
{
return $this->BaseImponibleACoste;
}
public function setBaseImponibleACoste(?float $baseImponibleACoste): self
{
if ($baseImponibleACoste !== null) {
$parts = explode('.', (string)$baseImponibleACoste);
$integerPart = $parts[0];
$decimalPart = $parts[1] ?? '';
if (strlen($integerPart) > 12) {
throw new \InvalidArgumentException('BaseImponibleACoste must have at most 12 digits before decimal point');
}
if (strlen($decimalPart) > 2) {
throw new \InvalidArgumentException('BaseImponibleACoste must have at most 2 decimal places');
}
}
$this->BaseImponibleACoste = $baseImponibleACoste;
return $this;
}
public function getCuotaRepercutida(): ?float
{
return $this->CuotaRepercutida;
}
public function setCuotaRepercutida(?float $cuotaRepercutida): self
{
if ($cuotaRepercutida !== null) {
$parts = explode('.', (string)$cuotaRepercutida);
$integerPart = $parts[0];
$decimalPart = $parts[1] ?? '';
if (strlen($integerPart) > 12) {
throw new \InvalidArgumentException('CuotaRepercutida must have at most 12 digits before decimal point');
}
if (strlen($decimalPart) > 2) {
throw new \InvalidArgumentException('CuotaRepercutida must have at most 2 decimal places');
}
}
$this->CuotaRepercutida = $cuotaRepercutida;
return $this;
}
public function getTipoRecargoEquivalencia(): ?float
{
return $this->TipoRecargoEquivalencia;
}
public function setTipoRecargoEquivalencia(?float $tipoRecargoEquivalencia): self
{
if ($tipoRecargoEquivalencia !== null) {
$parts = explode('.', (string)$tipoRecargoEquivalencia);
$decimalPart = $parts[1] ?? '';
if (strlen($decimalPart) > 2) {
throw new \InvalidArgumentException('TipoRecargoEquivalencia must have at most 2 decimal places');
}
if ($tipoRecargoEquivalencia < 0 || $tipoRecargoEquivalencia > 100) {
throw new \InvalidArgumentException('TipoRecargoEquivalencia must be between 0 and 100');
}
}
$this->TipoRecargoEquivalencia = $tipoRecargoEquivalencia;
return $this;
}
public function getCuotaRecargoEquivalencia(): ?float
{
return $this->CuotaRecargoEquivalencia;
}
public function setCuotaRecargoEquivalencia(?float $cuotaRecargoEquivalencia): self
{
if ($cuotaRecargoEquivalencia !== null) {
$parts = explode('.', (string)$cuotaRecargoEquivalencia);
$integerPart = $parts[0];
$decimalPart = $parts[1] ?? '';
if (strlen($integerPart) > 12) {
throw new \InvalidArgumentException('CuotaRecargoEquivalencia must have at most 12 digits before decimal point');
}
if (strlen($decimalPart) > 2) {
throw new \InvalidArgumentException('CuotaRecargoEquivalencia must have at most 2 decimal places');
}
}
$this->CuotaRecargoEquivalencia = $cuotaRecargoEquivalencia;
return $this;
}
}

View File

@ -0,0 +1,201 @@
<?php
namespace App\Services\EDocument\Standards\Verifactu\Types;
use Symfony\Component\Serializer\Annotation\SerializedName;
class DetalleDesglose
{
/** @var string */
#[SerializedName('sum1:ClaveRegimen')]
protected $ClaveRegimen;
/** @var string */
#[SerializedName('sum1:CalificacionOperacion')]
protected $CalificacionOperacion;
/** @var string|null */
#[SerializedName('sum1:OperacionExenta')]
protected $OperacionExenta;
/** @var float|null */
#[SerializedName('sum1:TipoImpositivo')]
protected $TipoImpositivo;
/** @var float */
#[SerializedName('sum1:BaseImponibleOimporteNoSujeto')]
protected $BaseImponibleOimporteNoSujeto;
/** @var float|null */
#[SerializedName('sum1:BaseImponibleACoste')]
protected $BaseImponibleACoste;
/** @var float|null */
#[SerializedName('sum1:CuotaRepercutida')]
protected $CuotaRepercutida;
/** @var float|null */
#[SerializedName('sum1:TipoRecargoEquivalencia')]
protected $TipoRecargoEquivalencia;
/** @var float|null */
#[SerializedName('sum1:CuotaRecargoEquivalencia')]
protected $CuotaRecargoEquivalencia;
public function getClaveRegimen(): string
{
return $this->ClaveRegimen;
}
public function setClaveRegimen(string $claveRegimen): self
{
if (!preg_match('/^\d{2}$/', $claveRegimen)) {
throw new \InvalidArgumentException('ClaveRegimen must be a 2-digit number');
}
$this->ClaveRegimen = $claveRegimen;
return $this;
}
public function getCalificacionOperacion(): string
{
return $this->CalificacionOperacion;
}
public function setCalificacionOperacion(string $calificacionOperacion): self
{
if (!preg_match('/^[A-Z]\d$/', $calificacionOperacion)) {
throw new \InvalidArgumentException('CalificacionOperacion must be a letter followed by a digit');
}
if ($this->OperacionExenta !== null) {
throw new \InvalidArgumentException('Cannot set CalificacionOperacion when OperacionExenta is set');
}
$this->CalificacionOperacion = $calificacionOperacion;
return $this;
}
public function getOperacionExenta(): ?string
{
return $this->OperacionExenta;
}
public function setOperacionExenta(?string $operacionExenta): self
{
if ($operacionExenta !== null) {
if (!preg_match('/^[A-Z]\d$/', $operacionExenta)) {
throw new \InvalidArgumentException('OperacionExenta must be a letter followed by a digit');
}
if ($this->CalificacionOperacion !== null) {
throw new \InvalidArgumentException('Cannot set OperacionExenta when CalificacionOperacion is set');
}
}
$this->OperacionExenta = $operacionExenta;
return $this;
}
public function getTipoImpositivo(): ?float
{
return $this->TipoImpositivo;
}
public function setTipoImpositivo(?float $tipoImpositivo): self
{
if ($tipoImpositivo !== null && ($tipoImpositivo < 0 || $tipoImpositivo > 100)) {
throw new \InvalidArgumentException('TipoImpositivo must be between 0 and 100');
}
$this->TipoImpositivo = $tipoImpositivo;
return $this;
}
public function getBaseImponibleOimporteNoSujeto(): float
{
return $this->BaseImponibleOimporteNoSujeto;
}
public function setBaseImponibleOimporteNoSujeto(float $baseImponibleOimporteNoSujeto): self
{
$parts = explode('.', (string)$baseImponibleOimporteNoSujeto);
$integerPart = $parts[0];
$decimalPart = $parts[1] ?? '';
if (strlen($integerPart) > 12) {
throw new \InvalidArgumentException('BaseImponibleOimporteNoSujeto must have at most 12 digits before decimal point');
}
if (strlen($decimalPart) > 2) {
throw new \InvalidArgumentException('BaseImponibleOimporteNoSujeto must have at most 2 decimal places');
}
$this->BaseImponibleOimporteNoSujeto = $baseImponibleOimporteNoSujeto;
return $this;
}
public function getBaseImponibleACoste(): ?float
{
return $this->BaseImponibleACoste;
}
public function setBaseImponibleACoste(?float $baseImponibleACoste): self
{
if ($baseImponibleACoste !== null) {
$parts = explode('.', (string)$baseImponibleACoste);
$integerPart = $parts[0];
$decimalPart = $parts[1] ?? '';
if (strlen($integerPart) > 12) {
throw new \InvalidArgumentException('BaseImponibleACoste must have at most 12 digits before decimal point');
}
if (strlen($decimalPart) > 2) {
throw new \InvalidArgumentException('BaseImponibleACoste must have at most 2 decimal places');
}
}
$this->BaseImponibleACoste = $baseImponibleACoste;
return $this;
}
public function getCuotaRepercutida(): ?float
{
return $this->CuotaRepercutida;
}
public function setCuotaRepercutida(?float $cuotaRepercutida): self
{
if ($cuotaRepercutida !== null) {
$parts = explode('.', (string)$cuotaRepercutida);
$integerPart = $parts[0];
$decimalPart = $parts[1] ?? '';
if (strlen($integerPart) > 12) {
throw new \InvalidArgumentException('CuotaRepercutida must have at most 12 digits before decimal point');
}
if (strlen($decimalPart) > 2) {
throw new \InvalidArgumentException('CuotaRepercutida must have at most 2 decimal places');
}
}
$this->CuotaRepercutida = $cuotaRepercutida;
return $this;
}
public function getTipoRecargoEquivalencia(): ?float
{
return $this->TipoRecargoEquivalencia;
}
public function setTipoRecargoEquivalencia(?float $tipoRecargoEquivalencia): self
{
if ($tipoRecargoEquivalencia !== null && ($tipoRecargoEquivalencia < 0 || $tipoRecargoEquivalencia > 100)) {
throw new \InvalidArgumentException('TipoRecargoEquivalencia must be between 0 and 100');
}
$this->TipoRecargoEquivalencia = $tipoRecargoEquivalencia;
return $this;
}
public function getCuotaRecargoEquivalencia(): ?float
{
return $this->CuotaRecargoEquivalencia;
}
public function setCuotaRecargoEquivalencia(?float $cuotaRecargoEquivalencia): self
{
$this->CuotaRecargoEquivalencia = $cuotaRecargoEquivalencia;
return $this;
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace App\Services\EDocument\Standards\Verifactu\Types;
use Symfony\Component\Serializer\Annotation\SerializedName;
class Encadenamiento
{
/** @var RegistroAnterior */
#[SerializedName('sum1:RegistroAnterior')]
protected $RegistroAnterior;
/** @var string */
#[SerializedName('sum1:PrimerRegistro')]
protected $PrimerRegistro;
public function getRegistroAnterior(): RegistroAnterior
{
return $this->RegistroAnterior;
}
public function setRegistroAnterior(RegistroAnterior $registroAnterior): self
{
$this->RegistroAnterior = $registroAnterior;
return $this;
}
public function getPrimerRegistro(): string
{
return $this->PrimerRegistro;
}
public function setPrimerRegistro(string $primerRegistro): self
{
if (strlen($primerRegistro) > 64) {
throw new \InvalidArgumentException('HuellaRegistroAnterior must not exceed 64 characters');
}
$this->PrimerRegistro = $primerRegistro;
return $this;
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Services\EDocument\Standards\Verifactu\Types;
use Symfony\Component\Serializer\Annotation\SerializedName;
class IDDestinatario extends PersonaFisicaJuridica
{
}

View File

@ -0,0 +1,71 @@
<?php
namespace App\Services\EDocument\Standards\Verifactu\Types;
use Symfony\Component\Serializer\Annotation\SerializedName;
class IDFactura
{
/** @var string */
#[SerializedName('sum1:IDEmisorFactura')]
protected $IDEmisorFactura;
/** @var string */
#[SerializedName('sum1:NumSerieFactura')]
protected $NumSerieFactura;
/** @var string */
#[SerializedName('sum1:FechaExpedicionFactura')]
protected $FechaExpedicionFactura;
public function getIDEmisorFactura(): string
{
return $this->IDEmisorFactura;
}
public function setIDEmisorFactura(string $idEmisorFactura): self
{
// Validate NIF format (letter or number followed by 8 numbers)
if (!preg_match('/^[A-Z0-9][0-9]{8}$/', $idEmisorFactura)) {
throw new \InvalidArgumentException('IDEmisorFactura must be a valid NIF (letter/number followed by 8 numbers)');
}
$this->IDEmisorFactura = $idEmisorFactura;
return $this;
}
public function getNumSerieFactura(): string
{
return $this->NumSerieFactura;
}
public function setNumSerieFactura(string $numSerieFactura): self
{
if (strlen($numSerieFactura) > 60) {
throw new \InvalidArgumentException('NumSerieFactura must not exceed 60 characters');
}
$this->NumSerieFactura = $numSerieFactura;
return $this;
}
public function getFechaExpedicionFactura(): string
{
return $this->FechaExpedicionFactura;
}
public function setFechaExpedicionFactura(string $fechaExpedicionFactura): self
{
// Validate date format DD-MM-YYYY
if (!preg_match('/^\d{2}-\d{2}-\d{4}$/', $fechaExpedicionFactura)) {
throw new \InvalidArgumentException('FechaExpedicionFactura must be in DD-MM-YYYY format');
}
// Validate date components
list($day, $month, $year) = explode('-', $fechaExpedicionFactura);
if (!checkdate((int)$month, (int)$day, (int)$year)) {
throw new \InvalidArgumentException('Invalid date');
}
$this->FechaExpedicionFactura = $fechaExpedicionFactura;
return $this;
}
}

View File

@ -0,0 +1,111 @@
<?php
namespace App\Services\EDocument\Standards\Verifactu\Types;
use Symfony\Component\Serializer\Annotation\SerializedName;
class IDFacturaAR
{
/** @var string */
#[SerializedName('sum1:IDEmisorFactura')]
protected $IDEmisorFactura;
/** @var string */
#[SerializedName('sum1:NumSerieFactura')]
protected $NumSerieFactura;
/** @var string */
#[SerializedName('sum1:FechaExpedicionFactura')]
protected $FechaExpedicionFactura;
/** @var string|null */
protected $NumSerieFacturaOrigen;
/** @var string|null */
protected $FechaExpedicionFacturaOrigen;
public function getIDEmisorFactura(): string
{
return $this->IDEmisorFactura;
}
public function setIDEmisorFactura(string $idEmisorFactura): self
{
// Validate NIF format
if (!preg_match('/^[A-Z0-9]{9}$/', $idEmisorFactura)) {
throw new \InvalidArgumentException('IDEmisorFactura must be a valid NIF (9 alphanumeric characters)');
}
$this->IDEmisorFactura = $idEmisorFactura;
return $this;
}
public function getNumSerieFactura(): string
{
return $this->NumSerieFactura;
}
public function setNumSerieFactura(string $numSerieFactura): self
{
if (strlen($numSerieFactura) > 60) {
throw new \InvalidArgumentException('NumSerieFactura must not exceed 60 characters');
}
$this->NumSerieFactura = $numSerieFactura;
return $this;
}
public function getFechaExpedicionFactura(): string
{
return $this->FechaExpedicionFactura;
}
public function setFechaExpedicionFactura(string $fechaExpedicionFactura): self
{
if (!preg_match('/^\d{2}-\d{2}-\d{4}$/', $fechaExpedicionFactura)) {
throw new \InvalidArgumentException('FechaExpedicionFactura must be in DD-MM-YYYY format');
}
list($day, $month, $year) = explode('-', $fechaExpedicionFactura);
if (!checkdate((int)$month, (int)$day, (int)$year)) {
throw new \InvalidArgumentException('Invalid date in FechaExpedicionFactura');
}
$this->FechaExpedicionFactura = $fechaExpedicionFactura;
return $this;
}
public function getNumSerieFacturaOrigen(): ?string
{
return $this->NumSerieFacturaOrigen;
}
public function setNumSerieFacturaOrigen(?string $numSerieFacturaOrigen): self
{
if ($numSerieFacturaOrigen !== null && strlen($numSerieFacturaOrigen) > 60) {
throw new \InvalidArgumentException('NumSerieFacturaOrigen must not exceed 60 characters');
}
$this->NumSerieFacturaOrigen = $numSerieFacturaOrigen;
return $this;
}
public function getFechaExpedicionFacturaOrigen(): ?string
{
return $this->FechaExpedicionFacturaOrigen;
}
public function setFechaExpedicionFacturaOrigen(?string $fechaExpedicionFacturaOrigen): self
{
if ($fechaExpedicionFacturaOrigen !== null) {
// Validate date format DD-MM-YYYY
if (!preg_match('/^\d{2}-\d{2}-\d{4}$/', $fechaExpedicionFacturaOrigen)) {
throw new \InvalidArgumentException('FechaExpedicionFacturaOrigen must be in DD-MM-YYYY format');
}
// Validate date components
list($day, $month, $year) = explode('-', $fechaExpedicionFacturaOrigen);
if (!checkdate((int)$month, (int)$day, (int)$year)) {
throw new \InvalidArgumentException('Invalid date');
}
}
$this->FechaExpedicionFacturaOrigen = $fechaExpedicionFacturaOrigen;
return $this;
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace App\Services\EDocument\Standards\Verifactu\Types;
use Symfony\Component\Serializer\Annotation\SerializedName;
class IDFacturaExpedida
{
/** @var string */
#[SerializedName('sum1:IDEmisorFactura')]
protected $IDEmisorFactura;
/** @var string */
#[SerializedName('sum1:NumSerieFactura')]
protected $NumSerieFactura;
/** @var string */
#[SerializedName('sum1:FechaExpedicionFactura')]
protected $FechaExpedicionFactura;
public function getIDEmisorFactura(): string
{
return $this->IDEmisorFactura;
}
public function setIDEmisorFactura(string $idEmisorFactura): self
{
// Validate NIF format
if (!preg_match('/^[A-Z0-9]{9}$/', $idEmisorFactura)) {
throw new \InvalidArgumentException('IDEmisorFactura must be a valid NIF (9 alphanumeric characters)');
}
$this->IDEmisorFactura = $idEmisorFactura;
return $this;
}
public function getNumSerieFactura(): string
{
return $this->NumSerieFactura;
}
public function setNumSerieFactura(string $numSerieFactura): self
{
if (strlen($numSerieFactura) > 60) {
throw new \InvalidArgumentException('NumSerieFactura must not exceed 60 characters');
}
$this->NumSerieFactura = $numSerieFactura;
return $this;
}
public function getFechaExpedicionFactura(): string
{
return $this->FechaExpedicionFactura;
}
public function setFechaExpedicionFactura(string $fechaExpedicionFactura): self
{
if (!preg_match('/^\d{2}-\d{2}-\d{4}$/', $fechaExpedicionFactura)) {
throw new \InvalidArgumentException('FechaExpedicionFactura must be in DD-MM-YYYY format');
}
list($day, $month, $year) = explode('-', $fechaExpedicionFactura);
if (!checkdate((int)$month, (int)$day, (int)$year)) {
throw new \InvalidArgumentException('Invalid date in FechaExpedicionFactura');
}
$this->FechaExpedicionFactura = $fechaExpedicionFactura;
return $this;
}
}

View File

@ -0,0 +1,79 @@
<?php
namespace App\Services\EDocument\Standards\Verifactu\Types;
use Symfony\Component\Serializer\Annotation\SerializedName;
class IDOtro
{
// 01 NIFContraparte Spanish Tax ID (NIF) of the counterparty NIF de la contraparte (solo válido con NIF, no en IDOtro)
// 02 VATNumber EU VAT Number Número de IVA de operadores intracomunitarios
// 03 Passport/Foreign ID National ID, passport, or similar from non-EU countries Documento oficial de identificación expedido por otro país
// 04 Legal Entity ID Tax ID for foreign legal entities Código de identificación fiscal de personas jurídicas extranjeras
// 05 Residence Cert. Certificate of residence issued by a tax authority Certificado de residencia fiscal
// 06 Other Other officially recognized identifier Otro documento reconocido oficialmente
public array $id_types = [
'01',
'02',
'03',
'04',
'05',
'06',
];
/** @var string */
#[SerializedName('sum1:CodigoPais')] // iso 2 country code
protected $CodigoPais;
/** @var string */
#[SerializedName('sum1:IDType')]
protected $IDType;
/** @var string */
#[SerializedName('sum1:ID')]
protected $ID;
public function getCodigoPais(): string
{
return $this->CodigoPais;
}
public function setCodigoPais(string $codigoPais): self
{
if (strlen($codigoPais) !== 2) {
throw new \InvalidArgumentException('CodigoPais must be a 2-character ISO country code');
}
$this->CodigoPais = $codigoPais;
return $this;
}
public function getIDType(): string
{
return $this->IDType;
}
public function setIDType(string $idType): self
{
$validTypes = ['02', '03', '04', '05', '06', '07'];
if (!in_array($idType, $validTypes)) {
throw new \InvalidArgumentException('Invalid IDType value');
}
$this->IDType = $idType;
return $this;
}
public function getID(): string
{
return $this->ID;
}
public function setID(string $id): self
{
if (strlen($id) > 20) {
throw new \InvalidArgumentException('ID must not exceed 20 characters');
}
$this->ID = $id;
return $this;
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace App\Services\EDocument\Standards\Verifactu\Types;
use App\Services\EDocument\Standards\Verifactu\Types\Common\TextTypes;
use Symfony\Component\Serializer\Annotation\SerializedName;
class ImporteSgn14_2
{
use TextTypes;
/** @var string */
#[SerializedName('sum1:Value')]
protected $Value;
public function __construct(string $value)
{
$this->setValue($value);
}
public function getValue(): string
{
return $this->Value;
}
public function setValue(string $value): self
{
$this->validateNumericString($value, 14, 2, 'Amount');
$this->Value = $value;
return $this;
}
public function __toString(): string
{
return $this->Value;
}
}

View File

@ -0,0 +1,134 @@
<?php
namespace App\Services\EDocument\Standards\Verifactu\Types;
use Symfony\Component\Serializer\Annotation\SerializedName;
class Incidencia
{
/** @var string */
#[SerializedName('sum1:Codigo')]
protected $Codigo;
/** @var string */
#[SerializedName('sum1:Descripcion')]
protected $Descripcion;
/** @var string|null Max length 120 characters */
#[SerializedName('sum1:NombreRazon')]
protected $NombreRazon;
/** @var string|null NIF format */
#[SerializedName('sum1:NIF')]
protected $NIF;
/** @var string|null */
#[SerializedName('sum1:FechaHora')]
protected $FechaHora;
/** @var string */
#[SerializedName('sum1:CodigoIncidencia')]
protected $CodigoIncidencia;
/** @var string */
#[SerializedName('sum1:DescripcionIncidencia')]
protected $DescripcionIncidencia;
public function getCodigo(): string
{
return $this->Codigo;
}
public function setCodigo(string $codigo): self
{
if (!preg_match('/^\d{3}$/', $codigo)) {
throw new \InvalidArgumentException('Codigo must be a 3-digit number');
}
$this->Codigo = $codigo;
return $this;
}
public function getDescripcion(): string
{
return $this->Descripcion;
}
public function setDescripcion(string $descripcion): self
{
if (strlen($descripcion) > 500) {
throw new \InvalidArgumentException('Descripcion must not exceed 500 characters');
}
$this->Descripcion = $descripcion;
return $this;
}
public function getNombreRazon(): ?string
{
return $this->NombreRazon;
}
public function setNombreRazon(?string $nombreRazon): self
{
if ($nombreRazon !== null && strlen($nombreRazon) > 120) {
throw new \InvalidArgumentException('NombreRazon must not exceed 120 characters');
}
$this->NombreRazon = $nombreRazon;
return $this;
}
public function getNIF(): ?string
{
return $this->NIF;
}
public function setNIF(?string $nif): self
{
// TODO: Add NIF validation
$this->NIF = $nif;
return $this;
}
public function getFechaHora(): ?string
{
return $this->FechaHora;
}
public function setFechaHora(?string $fechaHora): self
{
if ($fechaHora !== null) {
if (!\DateTime::createFromFormat('Y-m-d H:i:s', $fechaHora)) {
throw new \InvalidArgumentException('FechaHora must be in YYYY-MM-DD HH:mm:ss format');
}
}
$this->FechaHora = $fechaHora;
return $this;
}
public function getCodigoIncidencia(): string
{
return $this->CodigoIncidencia;
}
public function setCodigoIncidencia(string $codigoIncidencia): self
{
if (!preg_match('/^\d{3}$/', $codigoIncidencia)) {
throw new \InvalidArgumentException('CodigoIncidencia must be a 3-digit number');
}
$this->CodigoIncidencia = $codigoIncidencia;
return $this;
}
public function getDescripcionIncidencia(): string
{
return $this->DescripcionIncidencia;
}
public function setDescripcionIncidencia(string $descripcionIncidencia): self
{
if (strlen($descripcionIncidencia) > 500) {
throw new \InvalidArgumentException('DescripcionIncidencia must not exceed 500 characters');
}
$this->DescripcionIncidencia = $descripcionIncidencia;
return $this;
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace App\Services\EDocument\Standards\Verifactu\Types;
use Symfony\Component\Serializer\Annotation\SerializedName;
/**
* ObligadoEmision represents a required entity with NombreRazon and NIF.
* Extends PersonaFisicaJuridicaES but enforces both properties to be required at construction time.
*/
class ObligadoEmision
{
/** @var string */
#[SerializedName('sum1:NombreRazon')]
protected $NombreRazon;
/** @var string|null */
#[SerializedName('sum1:NIF')]
protected $NIF;
public function setNombreRazon(?string $nombreRazon): self
{
if (empty($nombreRazon)) {
throw new \InvalidArgumentException('NombreRazon is required for ObligadoEmision');
}
$this->NombreRazon = $nombreRazon;
return $this;
}
public function getNombreRazon(): string
{
return $this->NombreRazon;
}
public function getNIF(): string
{
return $this->NIF;
}
public function setNIF(string $nif): self
{
if (empty($nif)) {
throw new \InvalidArgumentException('NIF is required for ObligadoEmision');
}
$this->NIF = $nif;
return $this;
}
}

View File

@ -0,0 +1,100 @@
<?php
namespace App\Services\EDocument\Standards\Verifactu\Types;
use Symfony\Component\Serializer\Annotation\SerializedName;
class OperacionExenta
{
public const E1 = 'E1'; // EXENTA por Art. 20
public const E2 = 'E2'; // EXENTA por Art. 21
public const E3 = 'E3'; // EXENTA por Art. 22
public const E4 = 'E4'; // EXENTA por Art. 24
public const E5 = 'E5'; // EXENTA por Art. 25
public const E6 = 'E6'; // EXENTA por otros
/** @var string */
#[SerializedName('sum1:Value')]
protected $Value;
/** @var string */
#[SerializedName('sum1:CausaExencion')]
protected $CausaExencion;
/** @var float */
#[SerializedName('sum1:BaseImponible')]
protected $BaseImponible;
public function __construct(string $value)
{
$this->setValue($value);
}
public function getValue(): string
{
return $this->Value;
}
public function setValue(string $value): self
{
$validValues = [
self::E1,
self::E2,
self::E3,
self::E4,
self::E5,
self::E6,
];
if (!in_array($value, $validValues)) {
throw new \InvalidArgumentException(sprintf(
'Invalid OperacionExenta value. Must be one of: %s',
implode(', ', $validValues)
));
}
$this->Value = $value;
return $this;
}
public function __toString(): string
{
return $this->Value;
}
public function getCausaExencion(): string
{
return $this->CausaExencion;
}
public function setCausaExencion(string $causaExencion): self
{
if (!preg_match('/^[A-Z]\d{2}$/', $causaExencion)) {
throw new \InvalidArgumentException('CausaExencion must be a letter followed by two digits');
}
$this->CausaExencion = $causaExencion;
return $this;
}
public function getBaseImponible(): float
{
return $this->BaseImponible;
}
public function setBaseImponible(float $baseImponible): self
{
$parts = explode('.', (string)$baseImponible);
$integerPart = $parts[0];
$decimalPart = $parts[1] ?? '';
if (strlen($integerPart) > 12) {
throw new \InvalidArgumentException('BaseImponible must have at most 12 digits before decimal point');
}
if (strlen($decimalPart) > 2) {
throw new \InvalidArgumentException('BaseImponible must have at most 2 decimal places');
}
$this->BaseImponible = $baseImponible;
return $this;
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace App\Services\EDocument\Standards\Verifactu\Types;
use Symfony\Component\Serializer\Annotation\SerializedName;
class PersonaFisicaJuridica
{
/** @var string */
#[SerializedName('sum1:NombreRazon')]
protected $NombreRazon;
/** @var string|null */
#[SerializedName('sum1:NIF')]
protected $NIF;
/** @var IDOtro|null */
#[SerializedName('sum1:IDOtro')]
protected $IDOtro;
public function getNombreRazon(): string
{
return $this->NombreRazon;
}
public function setNombreRazon(string $nombreRazon): self
{
if (strlen($nombreRazon) > 120) {
throw new \InvalidArgumentException('NombreRazon must not exceed 120 characters');
}
$this->NombreRazon = $nombreRazon;
return $this;
}
public function getNIF(): ?string
{
return $this->NIF;
}
public function setNIF(?string $nif): self
{
if ($nif !== null) {
if (!preg_match('/^[A-Z0-9]{9}$/', $nif)) {
throw new \InvalidArgumentException('NIF must be a valid NIF (9 alphanumeric characters)');
}
$this->NIF = $nif;
$this->IDOtro = null; // Clear IDOtro as it's a choice
}
return $this;
}
public function getIDOtro(): ?IDOtro
{
return $this->IDOtro;
}
public function setIDOtro(?IDOtro $idOtro): self
{
if ($idOtro !== null) {
$this->IDOtro = $idOtro;
$this->NIF = null; // Clear NIF as it's a choice
}
return $this;
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace App\Services\EDocument\Standards\Verifactu\Types;
use Symfony\Component\Serializer\Annotation\SerializedName;
// User type is a person submitting on behalf of the company.
class PersonaFisicaJuridicaES
{
/** @var string NIF format */
#[SerializedName('sum1:NIF')]
protected $NIF;
/** @var string|null Max length 120 characters */
#[SerializedName('sum1:NombreRazon')]
protected $NombreRazon;
public function getNIF(): string
{
return $this->NIF;
}
public function setNIF(string $nif): self
{
// Validate NIF format (letter or number followed by 8 numbers)
if (!preg_match('/^[A-Z0-9][0-9]{8}$/', $nif)) {
throw new \InvalidArgumentException('NIF must be a valid format (letter/number followed by 8 numbers)');
}
$this->NIF = $nif;
return $this;
}
public function getNombreRazon(): ?string
{
return $this->NombreRazon;
}
public function setNombreRazon(?string $nombreRazon): self
{
if ($nombreRazon !== null && strlen($nombreRazon) > 120) {
throw new \InvalidArgumentException('NombreRazon must not exceed 120 characters');
}
$this->NombreRazon = $nombreRazon;
return $this;
}
}

View File

@ -0,0 +1,124 @@
<?php
namespace App\Services\EDocument\Standards\Verifactu\Types;
use Symfony\Component\Serializer\Annotation\SerializedName;
class RechazoPrevio
{
/** @var string */
#[SerializedName('sum1:NumRegistroAcuerdoFacturacion')]
protected $NumRegistroAcuerdoFacturacion;
/** @var string */
#[SerializedName('sum1:FechaRegistroAcuerdoFacturacion')]
protected $FechaRegistroAcuerdoFacturacion;
/** @var string */
#[SerializedName('sum1:MotivoRechazo')]
protected $MotivoRechazo;
/** @var string */
#[SerializedName('sum1:Codigo')]
protected $Codigo;
/** @var string */
#[SerializedName('sum1:Descripcion')]
protected $Descripcion;
/** @var string */
#[SerializedName('sum1:FechaHora')]
protected $FechaHora;
public function getNumRegistroAcuerdoFacturacion(): string
{
return $this->NumRegistroAcuerdoFacturacion;
}
public function setNumRegistroAcuerdoFacturacion(string $numRegistroAcuerdoFacturacion): self
{
if (strlen($numRegistroAcuerdoFacturacion) > 15) {
throw new \InvalidArgumentException('NumRegistroAcuerdoFacturacion must not exceed 15 characters');
}
$this->NumRegistroAcuerdoFacturacion = $numRegistroAcuerdoFacturacion;
return $this;
}
public function getFechaRegistroAcuerdoFacturacion(): string
{
return $this->FechaRegistroAcuerdoFacturacion;
}
public function setFechaRegistroAcuerdoFacturacion(string $fechaRegistroAcuerdoFacturacion): self
{
// Validate date format YYYY-MM-DD
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $fechaRegistroAcuerdoFacturacion)) {
throw new \InvalidArgumentException('FechaRegistroAcuerdoFacturacion must be in YYYY-MM-DD format');
}
// Validate date components
list($year, $month, $day) = explode('-', $fechaRegistroAcuerdoFacturacion);
if (!checkdate((int)$month, (int)$day, (int)$year)) {
throw new \InvalidArgumentException('Invalid date');
}
$this->FechaRegistroAcuerdoFacturacion = $fechaRegistroAcuerdoFacturacion;
return $this;
}
public function getMotivoRechazo(): string
{
return $this->MotivoRechazo;
}
public function setMotivoRechazo(string $motivoRechazo): self
{
if (strlen($motivoRechazo) > 2000) {
throw new \InvalidArgumentException('MotivoRechazo must not exceed 2000 characters');
}
$this->MotivoRechazo = $motivoRechazo;
return $this;
}
public function getCodigo(): string
{
return $this->Codigo;
}
public function setCodigo(string $codigo): self
{
if (!preg_match('/^\d{3}$/', $codigo)) {
throw new \InvalidArgumentException('Codigo must be a 3-digit number');
}
$this->Codigo = $codigo;
return $this;
}
public function getDescripcion(): string
{
return $this->Descripcion;
}
public function setDescripcion(string $descripcion): self
{
if (strlen($descripcion) > 500) {
throw new \InvalidArgumentException('Descripcion must not exceed 500 characters');
}
$this->Descripcion = $descripcion;
return $this;
}
public function getFechaHora(): string
{
return $this->FechaHora;
}
public function setFechaHora(string $fechaHora): self
{
if (!preg_match('/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}$/', $fechaHora)) {
throw new \InvalidArgumentException('FechaHora must be in ISO 8601 format with timezone (e.g. 2024-09-13T19:20:30+01:00)');
}
$this->FechaHora = $fechaHora;
return $this;
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace App\Services\EDocument\Standards\Verifactu\Types;
use Symfony\Component\Serializer\Annotation\SerializedName;
class RegFactuSistemaFacturacion
{
/** @var Cabecera */
#[SerializedName('sum:Cabecera')]
protected $Cabecera;
/** @var RegistroFactura */
#[SerializedName('sum:RegistroFactura')]
protected $RegistroFactura;
public function getCabecera(): Cabecera
{
return $this->Cabecera;
}
public function setCabecera(Cabecera $cabecera): self
{
$this->Cabecera = $cabecera;
return $this;
}
public function getRegistroFactura(): RegistroFactura
{
return $this->RegistroFactura;
}
public function setRegistroFactura(RegistroFactura $registroFactura): self
{
$this->RegistroFactura = $registroFactura;
return $this;
}
}

View File

@ -0,0 +1,234 @@
<?php
namespace App\Services\EDocument\Standards\Verifactu\Types;
use Symfony\Component\Serializer\Annotation\SerializedName;
class RegistroAlta
{
/** @var string */
#[SerializedName('sum1:IDVersion')]
protected $IDVersion;
/** @var IDFactura */
#[SerializedName('sum1:IDFactura')]
protected $IDFactura;
/** @var string */
#[SerializedName('sum1:NombreRazonEmisor')]
protected $NombreRazonEmisor;
/** @var string */
#[SerializedName('sum1:TipoFactura')]
protected $TipoFactura;
/** @var string */
#[SerializedName('sum1:DescripcionOperacion')]
protected $DescripcionOperacion;
/** @var Destinatarios */
#[SerializedName('sum1:Destinatarios')]
protected $Destinatarios;
/** @var Desglose */
#[SerializedName('sum1:Desglose')]
protected $Desglose;
/** @var float */
#[SerializedName('sum1:CuotaTotal')]
protected $CuotaTotal;
/** @var float */
#[SerializedName('sum1:ImporteTotal')]
protected $ImporteTotal;
/** @var Encadenamiento|null */
#[SerializedName('sum1:Encadenamiento')]
protected $Encadenamiento;
/** @var SistemaInformatico */
#[SerializedName('sum1:SistemaInformatico')]
protected $SistemaInformatico;
/** @var string */
#[SerializedName('sum1:FechaHoraHusoGenRegistro')]
protected $FechaHoraHusoGenRegistro;
/** @var string */
#[SerializedName('sum1:TipoHuella')]
protected $TipoHuella;
/** @var string */
#[SerializedName('sum1:Huella')]
protected $Huella;
public function getIDVersion(): string
{
return $this->IDVersion;
}
public function setIDVersion(string $idVersion): self
{
$this->IDVersion = $idVersion;
return $this;
}
public function getIDFactura(): IDFactura
{
return $this->IDFactura;
}
public function setIDFactura(IDFactura $idFactura): self
{
$this->IDFactura = $idFactura;
return $this;
}
public function getNombreRazonEmisor(): string
{
return $this->NombreRazonEmisor;
}
public function setNombreRazonEmisor(string $nombreRazonEmisor): self
{
if (strlen($nombreRazonEmisor) > 120) {
throw new \InvalidArgumentException('NombreRazonEmisor must not exceed 120 characters');
}
$this->NombreRazonEmisor = $nombreRazonEmisor;
return $this;
}
public function getTipoFactura(): string
{
return $this->TipoFactura;
}
public function setTipoFactura(string $tipoFactura): self
{
if (!preg_match('/^F[1-4]$/', $tipoFactura)) {
throw new \InvalidArgumentException('TipoFactura must be F1, F2, F3, or F4');
}
$this->TipoFactura = $tipoFactura;
return $this;
}
public function getDescripcionOperacion(): string
{
return $this->DescripcionOperacion;
}
public function setDescripcionOperacion(string $descripcionOperacion): self
{
if (strlen($descripcionOperacion) > 500) {
throw new \InvalidArgumentException('DescripcionOperacion must not exceed 500 characters');
}
$this->DescripcionOperacion = $descripcionOperacion;
return $this;
}
public function getDestinatarios(): Destinatarios
{
return $this->Destinatarios;
}
public function setDestinatarios(Destinatarios $destinatarios): self
{
$this->Destinatarios = $destinatarios;
return $this;
}
public function getDesglose(): Desglose
{
return $this->Desglose;
}
public function setDesglose(Desglose $desglose): self
{
$this->Desglose = $desglose;
return $this;
}
public function getCuotaTotal(): float
{
return $this->CuotaTotal;
}
public function setCuotaTotal(float $cuotaTotal): self
{
$this->CuotaTotal = $cuotaTotal;
return $this;
}
public function getImporteTotal(): float
{
return $this->ImporteTotal;
}
public function setImporteTotal(float $importeTotal): self
{
$this->ImporteTotal = $importeTotal;
return $this;
}
public function getEncadenamiento(): ?Encadenamiento
{
return $this->Encadenamiento;
}
public function setEncadenamiento(?Encadenamiento $encadenamiento): self
{
$this->Encadenamiento = $encadenamiento;
return $this;
}
public function getSistemaInformatico(): SistemaInformatico
{
return $this->SistemaInformatico;
}
public function setSistemaInformatico(SistemaInformatico $sistemaInformatico): self
{
$this->SistemaInformatico = $sistemaInformatico;
return $this;
}
public function getFechaHoraHusoGenRegistro(): string
{
return $this->FechaHoraHusoGenRegistro;
}
public function setFechaHoraHusoGenRegistro(string $fechaHoraHusoGenRegistro): self
{
// Validate ISO 8601 date format with timezone
if (!preg_match('/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}$/', $fechaHoraHusoGenRegistro)) {
throw new \InvalidArgumentException('FechaHoraHusoGenRegistro must be in ISO 8601 format (YYYY-MM-DDThh:mm:ss±hh:mm)');
}
$this->FechaHoraHusoGenRegistro = $fechaHoraHusoGenRegistro;
return $this;
}
public function getTipoHuella(): string
{
return $this->TipoHuella;
}
public function setTipoHuella(string $tipoHuella): self
{
if (!preg_match('/^\d{2}$/', $tipoHuella)) {
throw new \InvalidArgumentException('TipoHuella must be a 2-digit number');
}
$this->TipoHuella = $tipoHuella;
return $this;
}
public function getHuella(): string
{
return $this->Huella;
}
public function setHuella(string $huella): self
{
$this->Huella = $huella;
return $this;
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace App\Services\EDocument\Standards\Verifactu\Types;
use Symfony\Component\Serializer\Annotation\SerializedName;
class RegistroAnterior
{
/** @var string */
#[SerializedName('sum1:IDEmisorFactura')]
protected $IDEmisorFactura;
/** @var string */
#[SerializedName('sum1:NumSerieFactura')]
protected $NumSerieFactura;
/** @var string */
#[SerializedName('sum1:FechaExpedicionFactura')]
protected $FechaExpedicionFactura;
/** @var string */
#[SerializedName('sum1:Huella')]
protected $Huella;
public function getIDEmisorFactura(): string
{
return $this->IDEmisorFactura;
}
public function setIDEmisorFactura(string $IDEmisorFactura): self
{
$this->IDEmisorFactura = $IDEmisorFactura;
return $this;
}
public function getNumSerieFactura(): string
{
return $this->NumSerieFactura;
}
public function setNumSerieFactura(string $NumSerieFactura): self
{
$this->NumSerieFactura = $NumSerieFactura;
return $this;
}
public function getFechaExpedicionFactura(): string
{
return $this->FechaExpedicionFactura;
}
public function setFechaExpedicionFactura(string $FechaExpedicionFactura): self
{
$this->FechaExpedicionFactura = $FechaExpedicionFactura;
return $this;
}
public function getHuella(): string
{
return $this->Huella;
}
public function setHuella(string $Huella): self
{
$this->Huella = $Huella;
return $this;
}
}

View File

@ -0,0 +1,149 @@
<?php
namespace App\Services\EDocument\Standards\Verifactu\Types;
use Symfony\Component\Serializer\Annotation\SerializedName;
class RegistroFactura
{
/** @var string */
#[SerializedName('sum1:NumRegistroAcuerdoFacturacion')]
protected $NumRegistroAcuerdoFacturacion;
/** @var string */
#[SerializedName('sum1:FechaHoraHusoGenRegistro')]
protected $FechaHoraHusoGenRegistro;
/** @var string */
#[SerializedName('sum1:Huella')]
protected $Huella;
/** @var string|null */
#[SerializedName('sum1:Signature')]
protected $Signature;
/** @var string */
#[SerializedName('sum1:TipoHuella')]
protected $TipoHuella;
/** @var string|null */
#[SerializedName('sum1:IDAcuerdoSistemaInformatico')]
protected $IDAcuerdoSistemaInformatico;
/** @var RegistroAlta */
#[SerializedName('sum1:RegistroAlta')]
protected $RegistroAlta;
/** @var RegistroFacturacionAnulacion|null */
#[SerializedName('sum1:RegistroAnulacion')]
protected $RegistroAnulacion;
public function getNumRegistroAcuerdoFacturacion(): string
{
return $this->NumRegistroAcuerdoFacturacion;
}
public function setNumRegistroAcuerdoFacturacion(string $numRegistroAcuerdoFacturacion): self
{
if (strlen($numRegistroAcuerdoFacturacion) > 15) {
throw new \InvalidArgumentException('NumRegistroAcuerdoFacturacion must not exceed 15 characters');
}
$this->NumRegistroAcuerdoFacturacion = $numRegistroAcuerdoFacturacion;
return $this;
}
public function getFechaHoraHusoGenRegistro(): string
{
return $this->FechaHoraHusoGenRegistro;
}
public function setFechaHoraHusoGenRegistro(string $fechaHoraHusoGenRegistro): self
{
if (!preg_match('/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}$/', $fechaHoraHusoGenRegistro)) {
throw new \InvalidArgumentException('FechaHoraHusoGenRegistro must be in ISO 8601 format with timezone (e.g. 2024-09-13T19:20:30+01:00)');
}
$this->FechaHoraHusoGenRegistro = $fechaHoraHusoGenRegistro;
return $this;
}
public function getHuella(): string
{
return $this->Huella;
}
public function setHuella(string $huella): self
{
if (strlen($huella) > 64) {
throw new \InvalidArgumentException('Huella must not exceed 64 characters');
}
$this->Huella = $huella;
return $this;
}
public function getSignature(): ?string
{
return $this->Signature;
}
public function setSignature(?string $signature): self
{
$this->Signature = $signature;
return $this;
}
public function getTipoHuella(): string
{
return $this->TipoHuella;
}
public function setTipoHuella(string $tipoHuella): self
{
if ($tipoHuella !== '01') {
throw new \InvalidArgumentException('TipoHuella must be "01" (SHA-256)');
}
$this->TipoHuella = $tipoHuella;
return $this;
}
public function getIDAcuerdoSistemaInformatico(): ?string
{
return $this->IDAcuerdoSistemaInformatico;
}
public function setIDAcuerdoSistemaInformatico(?string $idAcuerdoSistemaInformatico): self
{
if ($idAcuerdoSistemaInformatico !== null && strlen($idAcuerdoSistemaInformatico) > 16) {
throw new \InvalidArgumentException('IDAcuerdoSistemaInformatico must not exceed 16 characters');
}
$this->IDAcuerdoSistemaInformatico = $idAcuerdoSistemaInformatico;
return $this;
}
public function getRegistroAlta(): RegistroAlta
{
return $this->RegistroAlta;
}
public function setRegistroAlta(RegistroAlta $registroAlta): self
{
if ($registroAlta !== null && $this->RegistroAnulacion !== null) {
throw new \InvalidArgumentException('Cannot set both RegistroAlta and RegistroAnulacion');
}
$this->RegistroAlta = $registroAlta;
return $this;
}
public function getRegistroAnulacion(): ?RegistroFacturacionAnulacion
{
return $this->RegistroAnulacion;
}
public function setRegistroAnulacion(?RegistroFacturacionAnulacion $registroAnulacion): self
{
if ($registroAnulacion !== null && $this->RegistroAlta !== null) {
throw new \InvalidArgumentException('Cannot set both RegistroAlta and RegistroAnulacion');
}
$this->RegistroAnulacion = $registroAnulacion;
return $this;
}
}

View File

@ -0,0 +1,532 @@
<?php
namespace App\Services\EDocument\Standards\Verifactu\Types;
use Symfony\Component\Serializer\Annotation\SerializedName;
class RegistroFacturacionAlta
{
/** @var string */
#[SerializedName('sum1:IDVersion')]
protected $IDVersion;
/** @var IDFacturaExpedida */
#[SerializedName('sum1:IDFactura')]
protected $IDFactura;
/** @var string|null Max length 70 characters */
#[SerializedName('sum1:RefExterna')]
protected $RefExterna;
/** @var string Max length 120 characters */
#[SerializedName('sum1:NombreRazonEmisor')]
protected $NombreRazonEmisor;
/** @var Subsanacion|null */
#[SerializedName('sum1:Subsanacion')]
protected $Subsanacion;
/** @var RechazoPrevio|null */
#[SerializedName('sum1:RechazoPrevio')]
protected $RechazoPrevio;
/** @var string */
#[SerializedName('sum1:TipoFactura')]
protected $TipoFactura;
/** @var string|null */
#[SerializedName('sum1:TipoRectificativa')]
protected $TipoRectificativa;
/** @var IDFacturaAR[]|null */
#[SerializedName('sum1:FacturasRectificadas')]
protected $FacturasRectificadas = [];
/** @var IDFacturaAR[]|null */
#[SerializedName('sum1:FacturasSustituidas')]
protected $FacturasSustituidas = [];
/** @var DesgloseRectificacion|null */
#[SerializedName('sum1:ImporteRectificacion')]
protected $ImporteRectificacion;
/** @var string|null */
#[SerializedName('sum1:FechaOperacion')]
protected $FechaOperacion;
/** @var string Max length 500 characters */
#[SerializedName('sum1:DescripcionOperacion')]
protected $DescripcionOperacion;
/** @var string|null */
#[SerializedName('sum1:FacturaSimplificadaArt7273')]
protected $FacturaSimplificadaArt7273;
/** @var string|null */
#[SerializedName('sum1:FacturaSinIdentifDestinatarioArt61d')]
protected $FacturaSinIdentifDestinatarioArt61d;
/** @var string|null */
#[SerializedName('sum1:Macrodato')]
protected $Macrodato;
/** @var string|null */
#[SerializedName('sum1:EmitidaPorTerceroODestinatario')]
protected $EmitidaPorTerceroODestinatario;
/** @var PersonaFisicaJuridica|null */
#[SerializedName('sum1:Tercero')]
protected $Tercero;
/** @var PersonaFisicaJuridica[]|null */
#[SerializedName('sum1:Destinatarios')]
protected $Destinatarios = [];
/** @var array|null */
#[SerializedName('sum1:Cupon')]
protected $Cupon;
/** @var Desglose */
#[SerializedName('sum1:Desglose')]
protected $Desglose;
/** @var float */
#[SerializedName('sum1:CuotaTotal')]
protected $CuotaTotal;
/** @var float */
#[SerializedName('sum1:ImporteTotal')]
protected $ImporteTotal;
/** @var array */
#[SerializedName('sum1:Encadenamiento')]
protected $Encadenamiento;
/** @var SistemaInformatico */
#[SerializedName('sum1:SistemaInformatico')]
protected $SistemaInformatico;
/** @var \DateTime */
#[SerializedName('sum1:FechaHoraHusoGenRegistro')]
protected $FechaHoraHusoGenRegistro;
/** @var string|null Max length 15 characters */
#[SerializedName('sum1:NumRegistroAcuerdoFacturacion')]
protected $NumRegistroAcuerdoFacturacion;
/** @var string|null Max length 16 characters */
#[SerializedName('sum1:IDAcuerdoSistemaInformatico')]
protected $IDAcuerdoSistemaInformatico;
/** @var string */
#[SerializedName('sum1:TipoHuella')]
protected $TipoHuella;
/** @var string Max length 64 characters */
#[SerializedName('sum1:Huella')]
protected $Huella = '';
/** @var string|null */
#[SerializedName('sum1:Signature')]
protected $Signature;
// Getters and setters with validation
public function getIDVersion(): string
{
return $this->IDVersion;
}
public function setIDVersion(string $idVersion): self
{
$this->IDVersion = $idVersion;
return $this;
}
public function getIDFactura(): IDFacturaExpedida
{
return $this->IDFactura;
}
public function setIDFactura(IDFacturaExpedida $idFactura): self
{
$this->IDFactura = $idFactura;
return $this;
}
public function getRefExterna(): ?string
{
return $this->RefExterna;
}
public function setRefExterna(?string $refExterna): self
{
if ($refExterna !== null && strlen($refExterna) > 70) {
throw new \InvalidArgumentException('RefExterna must not exceed 70 characters');
}
$this->RefExterna = $refExterna;
return $this;
}
public function getNombreRazonEmisor(): string
{
return $this->NombreRazonEmisor;
}
public function setNombreRazonEmisor(string $nombreRazonEmisor): self
{
if (strlen($nombreRazonEmisor) > 120) {
throw new \InvalidArgumentException('NombreRazonEmisor must not exceed 120 characters');
}
$this->NombreRazonEmisor = $nombreRazonEmisor;
return $this;
}
public function getHuella(): string
{
return $this->Huella;
}
public function setHuella(string $huella): self
{
if (strlen($huella) > 64) {
throw new \InvalidArgumentException('Huella must not exceed 64 characters');
}
$this->Huella = $huella;
return $this;
}
public function getSignature(): ?string
{
return $this->Signature;
}
public function setSignature(?string $signature): self
{
$this->Signature = $signature;
return $this;
}
public function getFechaHoraHusoGenRegistro(): \DateTime
{
return $this->FechaHoraHusoGenRegistro;
}
public function setFechaHoraHusoGenRegistro(\DateTime $fechaHoraHusoGenRegistro): self
{
$this->FechaHoraHusoGenRegistro = $fechaHoraHusoGenRegistro;
return $this;
}
public function getNumRegistroAcuerdoFacturacion(): ?string
{
return $this->NumRegistroAcuerdoFacturacion;
}
public function setNumRegistroAcuerdoFacturacion(?string $numRegistroAcuerdoFacturacion): self
{
if ($numRegistroAcuerdoFacturacion !== null && strlen($numRegistroAcuerdoFacturacion) > 15) {
throw new \InvalidArgumentException('NumRegistroAcuerdoFacturacion must not exceed 15 characters');
}
$this->NumRegistroAcuerdoFacturacion = $numRegistroAcuerdoFacturacion;
return $this;
}
public function getIDAcuerdoSistemaInformatico(): ?string
{
return $this->IDAcuerdoSistemaInformatico;
}
public function setIDAcuerdoSistemaInformatico(?string $idAcuerdoSistemaInformatico): self
{
if ($idAcuerdoSistemaInformatico !== null && strlen($idAcuerdoSistemaInformatico) > 16) {
throw new \InvalidArgumentException('IDAcuerdoSistemaInformatico must not exceed 16 characters');
}
$this->IDAcuerdoSistemaInformatico = $idAcuerdoSistemaInformatico;
return $this;
}
public function getTipoHuella(): string
{
return $this->TipoHuella;
}
public function setTipoHuella(string $tipoHuella): self
{
if ($tipoHuella !== '01') {
throw new \InvalidArgumentException('TipoHuella must be "01" (SHA-256)');
}
$this->TipoHuella = $tipoHuella;
return $this;
}
public function getFacturasRectificadas(): array
{
return $this->FacturasRectificadas;
}
public function addFacturaRectificada(IDFacturaAR $facturaRectificada): self
{
$this->FacturasRectificadas[] = $facturaRectificada;
return $this;
}
public function getFacturasSustituidas(): array
{
return $this->FacturasSustituidas;
}
public function addFacturaSustituida(IDFacturaAR $facturaSustituida): self
{
$this->FacturasSustituidas[] = $facturaSustituida;
return $this;
}
public function getImporteRectificacion(): ?DesgloseRectificacion
{
return $this->ImporteRectificacion;
}
public function setImporteRectificacion(?DesgloseRectificacion $importeRectificacion): self
{
$this->ImporteRectificacion = $importeRectificacion;
return $this;
}
public function getFechaOperacion(): ?string
{
return $this->FechaOperacion;
}
public function setFechaOperacion(?string $fechaOperacion): self
{
if ($fechaOperacion !== null) {
if (!preg_match('/^\d{2}-\d{2}-\d{4}$/', $fechaOperacion)) {
throw new \InvalidArgumentException('FechaOperacion must be in DD-MM-YYYY format');
}
list($day, $month, $year) = explode('-', $fechaOperacion);
if (!checkdate((int)$month, (int)$day, (int)$year)) {
throw new \InvalidArgumentException('Invalid date in FechaOperacion');
}
}
$this->FechaOperacion = $fechaOperacion;
return $this;
}
public function getDescripcionOperacion(): string
{
return $this->DescripcionOperacion;
}
public function setDescripcionOperacion(string $descripcionOperacion): self
{
if (strlen($descripcionOperacion) > 500) {
throw new \InvalidArgumentException('DescripcionOperacion must not exceed 500 characters');
}
$this->DescripcionOperacion = $descripcionOperacion;
return $this;
}
public function getFacturaSimplificadaArt7273(): ?string
{
return $this->FacturaSimplificadaArt7273;
}
public function setFacturaSimplificadaArt7273(?string $facturaSimplificadaArt7273): self
{
if ($facturaSimplificadaArt7273 !== null && !in_array($facturaSimplificadaArt7273, ['S', 'N'])) {
throw new \InvalidArgumentException('FacturaSimplificadaArt7273 must be either "S" or "N"');
}
$this->FacturaSimplificadaArt7273 = $facturaSimplificadaArt7273;
return $this;
}
public function getFacturaSinIdentifDestinatarioArt61d(): ?string
{
return $this->FacturaSinIdentifDestinatarioArt61d;
}
public function setFacturaSinIdentifDestinatarioArt61d(?string $facturaSinIdentifDestinatarioArt61d): self
{
if ($facturaSinIdentifDestinatarioArt61d !== null && !in_array($facturaSinIdentifDestinatarioArt61d, ['S', 'N'])) {
throw new \InvalidArgumentException('FacturaSinIdentifDestinatarioArt61d must be either "S" or "N"');
}
$this->FacturaSinIdentifDestinatarioArt61d = $facturaSinIdentifDestinatarioArt61d;
return $this;
}
public function getMacrodato(): ?string
{
return $this->Macrodato;
}
public function setMacrodato(?string $macrodato): self
{
if ($macrodato !== null && !in_array($macrodato, ['S', 'N'])) {
throw new \InvalidArgumentException('Macrodato must be either "S" or "N"');
}
$this->Macrodato = $macrodato;
return $this;
}
public function getEmitidaPorTerceroODestinatario(): ?string
{
return $this->EmitidaPorTerceroODestinatario;
}
public function setEmitidaPorTerceroODestinatario(?string $emitidaPorTerceroODestinatario): self
{
if ($emitidaPorTerceroODestinatario !== null && !in_array($emitidaPorTerceroODestinatario, ['S', 'N'])) {
throw new \InvalidArgumentException('EmitidaPorTerceroODestinatario must be either "S" or "N"');
}
$this->EmitidaPorTerceroODestinatario = $emitidaPorTerceroODestinatario;
return $this;
}
public function getTercero(): ?PersonaFisicaJuridica
{
return $this->Tercero;
}
public function setTercero(?PersonaFisicaJuridica $tercero): self
{
$this->Tercero = $tercero;
return $this;
}
public function getDestinatarios(): array
{
return $this->Destinatarios;
}
public function addDestinatario(PersonaFisicaJuridica $destinatario): self
{
if (count($this->Destinatarios) >= 1000) {
throw new \InvalidArgumentException('Maximum number of Destinatarios (1000) exceeded');
}
$this->Destinatarios[] = $destinatario;
return $this;
}
public function getCupon(): ?array
{
return $this->Cupon;
}
public function setCupon(?array $cupon): self
{
$this->Cupon = $cupon;
return $this;
}
public function getDesglose(): Desglose
{
return $this->Desglose;
}
public function setDesglose(Desglose $desglose): self
{
$this->Desglose = $desglose;
return $this;
}
public function getCuotaTotal(): float
{
return $this->CuotaTotal;
}
public function setCuotaTotal(float $cuotaTotal): self
{
$parts = explode('.', (string)$cuotaTotal);
$integerPart = $parts[0];
$decimalPart = $parts[1] ?? '';
if (strlen($integerPart) > 12) {
throw new \InvalidArgumentException('CuotaTotal must have at most 12 digits before decimal point');
}
if (strlen($decimalPart) > 2) {
throw new \InvalidArgumentException('CuotaTotal must have at most 2 decimal places');
}
$this->CuotaTotal = $cuotaTotal;
return $this;
}
public function getImporteTotal(): float
{
return $this->ImporteTotal;
}
public function setImporteTotal(float $importeTotal): self
{
$parts = explode('.', (string)$importeTotal);
$integerPart = $parts[0];
$decimalPart = $parts[1] ?? '';
if (strlen($integerPart) > 12) {
throw new \InvalidArgumentException('ImporteTotal must have at most 12 digits before decimal point');
}
if (strlen($decimalPart) > 2) {
throw new \InvalidArgumentException('ImporteTotal must have at most 2 decimal places');
}
$this->ImporteTotal = $importeTotal;
return $this;
}
public function getEncadenamiento(): array
{
return $this->Encadenamiento;
}
public function setEncadenamiento(array $encadenamiento): self
{
$this->Encadenamiento = $encadenamiento;
return $this;
}
public function getSistemaInformatico(): SistemaInformatico
{
return $this->SistemaInformatico;
}
public function setSistemaInformatico(SistemaInformatico $sistemaInformatico): self
{
$this->SistemaInformatico = $sistemaInformatico;
return $this;
}
public function toRegistroAlta(): RegistroAlta
{
$registroAlta = new RegistroAlta();
$registroAlta->setIDVersion($this->getIDVersion());
// Convert IDFacturaExpedida to IDFactura
$idFactura = new IDFactura();
$idFactura->setIDEmisorFactura($this->getIDFactura()->getIDEmisorFactura());
$idFactura->setNumSerieFactura($this->getIDFactura()->getNumSerieFactura());
$idFactura->setFechaExpedicionFactura($this->getIDFactura()->getFechaExpedicionFactura());
$registroAlta->setIDFactura($idFactura);
$registroAlta->setNombreRazonEmisor($this->getNombreRazonEmisor());
$registroAlta->setTipoFactura($this->getTipoFactura());
$registroAlta->setDescripcionOperacion($this->getDescripcionOperacion());
// Convert array of Destinatarios to Destinatarios object
$destinatarios = new Destinatarios();
foreach ($this->getDestinatarios() as $destinatario) {
$destinatarios->addDestinatario($destinatario);
}
$registroAlta->setDestinatarios($destinatarios);
$registroAlta->setDesglose($this->getDesglose());
$registroAlta->setCuotaTotal($this->getCuotaTotal());
$registroAlta->setImporteTotal($this->getImporteTotal());
$registroAlta->setSistemaInformatico($this->getSistemaInformatico());
$registroAlta->setFechaHoraHusoGenRegistro($this->getFechaHoraHusoGenRegistro()->format('Y-m-d\TH:i:sP'));
$registroAlta->setTipoHuella($this->getTipoHuella());
$registroAlta->setHuella($this->getHuella());
return $registroAlta;
}
}

View File

@ -0,0 +1,213 @@
<?php
namespace App\Services\EDocument\Standards\Verifactu\Types;
use Symfony\Component\Serializer\Annotation\SerializedName;
class RegistroFacturacionAnulacion
{
/** @var string */
#[SerializedName('sum1:IDVersion')]
protected $IDVersion;
/** @var IDFacturaAR */
#[SerializedName('sum1:IDFactura')]
protected $IDFactura;
/** @var string|null Max length 70 characters */
#[SerializedName('sum1:RefExterna')]
protected $RefExterna;
/** @var string Max length 120 characters */
#[SerializedName('sum1:NombreRazonEmisor')]
protected $NombreRazonEmisor;
/** @var string|null Max length 2000 characters */
#[SerializedName('sum1:MotivoAnulacion')]
protected $MotivoAnulacion;
/** @var SistemaInformatico */
#[SerializedName('sum1:SistemaInformatico')]
protected $SistemaInformatico;
/** @var string */
#[SerializedName('sum1:Huella')]
protected $Huella;
/** @var string|null */
#[SerializedName('sum1:Signature')]
protected $Signature;
/** @var string */
#[SerializedName('sum1:FechaHoraHusoGenRegistro')]
protected $FechaHoraHusoGenRegistro;
/** @var string|null Max length 15 characters */
#[SerializedName('sum1:NumRegistroAcuerdoFacturacion')]
protected $NumRegistroAcuerdoFacturacion;
/** @var string|null Max length 16 characters */
#[SerializedName('sum1:IDAcuerdoSistemaInformatico')]
protected $IDAcuerdoSistemaInformatico;
/** @var string */
#[SerializedName('sum1:TipoHuella')]
protected $TipoHuella;
public function getIDVersion(): string
{
return $this->IDVersion;
}
public function setIDVersion(string $idVersion): self
{
$this->IDVersion = $idVersion;
return $this;
}
public function getIDFactura(): IDFacturaAR
{
return $this->IDFactura;
}
public function setIDFactura(IDFacturaAR $idFactura): self
{
$this->IDFactura = $idFactura;
return $this;
}
public function getRefExterna(): ?string
{
return $this->RefExterna;
}
public function setRefExterna(?string $refExterna): self
{
if ($refExterna !== null && strlen($refExterna) > 70) {
throw new \InvalidArgumentException('RefExterna must not exceed 70 characters');
}
$this->RefExterna = $refExterna;
return $this;
}
public function getNombreRazonEmisor(): string
{
return $this->NombreRazonEmisor;
}
public function setNombreRazonEmisor(string $nombreRazonEmisor): self
{
if (strlen($nombreRazonEmisor) > 120) {
throw new \InvalidArgumentException('NombreRazonEmisor must not exceed 120 characters');
}
$this->NombreRazonEmisor = $nombreRazonEmisor;
return $this;
}
public function getMotivoAnulacion(): ?string
{
return $this->MotivoAnulacion;
}
public function setMotivoAnulacion(?string $motivoAnulacion): self
{
if ($motivoAnulacion !== null && strlen($motivoAnulacion) > 2000) {
throw new \InvalidArgumentException('MotivoAnulacion must not exceed 2000 characters');
}
$this->MotivoAnulacion = $motivoAnulacion;
return $this;
}
public function getSistemaInformatico(): SistemaInformatico
{
return $this->SistemaInformatico;
}
public function setSistemaInformatico(SistemaInformatico $sistemaInformatico): self
{
$this->SistemaInformatico = $sistemaInformatico;
return $this;
}
public function getHuella(): string
{
return $this->Huella;
}
public function setHuella(string $huella): self
{
if (strlen($huella) > 100) {
throw new \InvalidArgumentException('Huella must not exceed 100 characters');
}
$this->Huella = $huella;
return $this;
}
public function getSignature(): ?string
{
return $this->Signature;
}
public function setSignature(?string $signature): self
{
$this->Signature = $signature;
return $this;
}
public function getFechaHoraHusoGenRegistro(): string
{
return $this->FechaHoraHusoGenRegistro;
}
public function setFechaHoraHusoGenRegistro(string $fechaHoraHusoGenRegistro): self
{
// Validate ISO 8601 format with timezone
if (!preg_match('/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}$/', $fechaHoraHusoGenRegistro)) {
throw new \InvalidArgumentException('FechaHoraHusoGenRegistro must be in ISO 8601 format with timezone (e.g. 2024-09-13T19:20:30+01:00)');
}
$this->FechaHoraHusoGenRegistro = $fechaHoraHusoGenRegistro;
return $this;
}
public function getNumRegistroAcuerdoFacturacion(): ?string
{
return $this->NumRegistroAcuerdoFacturacion;
}
public function setNumRegistroAcuerdoFacturacion(?string $numRegistroAcuerdoFacturacion): self
{
if ($numRegistroAcuerdoFacturacion !== null && strlen($numRegistroAcuerdoFacturacion) > 15) {
throw new \InvalidArgumentException('NumRegistroAcuerdoFacturacion must not exceed 15 characters');
}
$this->NumRegistroAcuerdoFacturacion = $numRegistroAcuerdoFacturacion;
return $this;
}
public function getIDAcuerdoSistemaInformatico(): ?string
{
return $this->IDAcuerdoSistemaInformatico;
}
public function setIDAcuerdoSistemaInformatico(?string $idAcuerdoSistemaInformatico): self
{
if ($idAcuerdoSistemaInformatico !== null && strlen($idAcuerdoSistemaInformatico) > 16) {
throw new \InvalidArgumentException('IDAcuerdoSistemaInformatico must not exceed 16 characters');
}
$this->IDAcuerdoSistemaInformatico = $idAcuerdoSistemaInformatico;
return $this;
}
public function getTipoHuella(): string
{
return $this->TipoHuella;
}
public function setTipoHuella(string $tipoHuella): self
{
if ($tipoHuella !== '01') {
throw new \InvalidArgumentException('TipoHuella must be "01" (SHA-256)');
}
$this->TipoHuella = $tipoHuella;
return $this;
}
}

View File

@ -0,0 +1,134 @@
<?php
namespace App\Services\EDocument\Standards\Verifactu\Types;
use Symfony\Component\Serializer\Annotation\SerializedName;
class SistemaInformatico extends PersonaFisicaJuridicaES
{
/** @var string */
#[SerializedName('sum1:NombreSistemaInformatico')]
protected $NombreSistemaInformatico;
/** @var string */
#[SerializedName('sum1:IdSistemaInformatico')]
protected $IdSistemaInformatico;
/** @var string */
#[SerializedName('sum1:Version')]
protected $Version;
/** @var string */
#[SerializedName('sum1:NumeroInstalacion')]
protected $NumeroInstalacion;
/** @var string */
#[SerializedName('sum1:TipoUsoPosibleSoloVerifactu')]
protected $TipoUsoPosibleSoloVerifactu;
/** @var string */
#[SerializedName('sum1:TipoUsoPosibleMultiOT')]
protected $TipoUsoPosibleMultiOT;
/** @var string */
#[SerializedName('sum1:IndicadorMultiplesOT')]
protected $IndicadorMultiplesOT;
public function getNombreSistemaInformatico(): string
{
return $this->NombreSistemaInformatico;
}
public function setNombreSistemaInformatico(string $nombreSistemaInformatico): self
{
if (strlen($nombreSistemaInformatico) > 120) {
throw new \InvalidArgumentException('NombreSistemaInformatico must not exceed 120 characters');
}
$this->NombreSistemaInformatico = $nombreSistemaInformatico;
return $this;
}
public function getIdSistemaInformatico(): string
{
return $this->IdSistemaInformatico;
}
public function setIdSistemaInformatico(string $idSistemaInformatico): self
{
if (strlen($idSistemaInformatico) > 20) {
throw new \InvalidArgumentException('IdSistemaInformatico must not exceed 20 characters');
}
$this->IdSistemaInformatico = $idSistemaInformatico;
return $this;
}
public function getVersion(): string
{
return $this->Version;
}
public function setVersion(string $version): self
{
if (strlen($version) > 20) {
throw new \InvalidArgumentException('Version must not exceed 20 characters');
}
$this->Version = $version;
return $this;
}
public function getNumeroInstalacion(): string
{
return $this->NumeroInstalacion;
}
public function setNumeroInstalacion(string $numeroInstalacion): self
{
if (strlen($numeroInstalacion) > 20) {
throw new \InvalidArgumentException('NumeroInstalacion must not exceed 20 characters');
}
$this->NumeroInstalacion = $numeroInstalacion;
return $this;
}
public function getTipoUsoPosibleSoloVerifactu(): string
{
return $this->TipoUsoPosibleSoloVerifactu;
}
public function setTipoUsoPosibleSoloVerifactu(string $tipoUsoPosibleSoloVerifactu): self
{
if (!in_array($tipoUsoPosibleSoloVerifactu, ['S', 'N'])) {
throw new \InvalidArgumentException('TipoUsoPosibleSoloVerifactu must be either "S" or "N"');
}
$this->TipoUsoPosibleSoloVerifactu = $tipoUsoPosibleSoloVerifactu;
return $this;
}
public function getTipoUsoPosibleMultiOT(): string
{
return $this->TipoUsoPosibleMultiOT;
}
public function setTipoUsoPosibleMultiOT(string $tipoUsoPosibleMultiOT): self
{
if (!in_array($tipoUsoPosibleMultiOT, ['S', 'N'])) {
throw new \InvalidArgumentException('TipoUsoPosibleMultiOT must be either "S" or "N"');
}
$this->TipoUsoPosibleMultiOT = $tipoUsoPosibleMultiOT;
return $this;
}
public function getIndicadorMultiplesOT(): string
{
return $this->IndicadorMultiplesOT;
}
public function setIndicadorMultiplesOT(string $indicadorMultiplesOT): self
{
if (!in_array($indicadorMultiplesOT, ['S', 'N'])) {
throw new \InvalidArgumentException('IndicadorMultiplesOT must be either "S" or "N"');
}
$this->IndicadorMultiplesOT = $indicadorMultiplesOT;
return $this;
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace App\Services\EDocument\Standards\Verifactu\Types;
use Symfony\Component\Serializer\Annotation\SerializedName;
class SoapBody
{
/** @var RegFactuSistemaFacturacion */
#[SerializedName('sum:RegFactuSistemaFacturacion')]
protected $RegFactuSistemaFacturacion;
public function getRegFactuSistemaFacturacion(): RegFactuSistemaFacturacion
{
return $this->RegFactuSistemaFacturacion;
}
public function setRegFactuSistemaFacturacion(RegFactuSistemaFacturacion $regFactuSistemaFacturacion): self
{
$this->RegFactuSistemaFacturacion = $regFactuSistemaFacturacion;
return $this;
}
}

View File

@ -0,0 +1,53 @@
<?php
namespace App\Services\EDocument\Standards\Verifactu\Types;
use Symfony\Component\Serializer\Annotation\XmlRoot;
use Symfony\Component\Serializer\Annotation\SerializedName;
use App\Services\EDocument\Standards\Verifactu\Types\SoapBody;
use App\Services\EDocument\Standards\Verifactu\Types\SoapHeader;
class SoapEnvelope
{
#[SerializedName('@xmlns:soapenv')]
public $xmlns_soapenv = 'http://schemas.xmlsoap.org/soap/envelope/';
#[SerializedName('@xmlns:sum')]
public $xmlns_sum = 'https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd';
#[SerializedName('@xmlns:sum1')]
public $xmlns_sum1 = 'https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd';
#[SerializedName('@xmlns:xd')]
public $xmlns_xd = 'http://www.w3.org/2000/09/xmldsig#';
/** @var SoapHeader */
#[SerializedName('soapenv:Header')]
protected $Header;
/** @var SoapBody */
#[SerializedName('soapenv:Body')]
protected $Body;
public function getHeader(): ?SoapHeader
{
return $this->Header;
}
public function setHeader(SoapHeader $header): self
{
$this->Header = $header;
return $this;
}
public function getBody(): ?SoapBody
{
return $this->Body;
}
public function setBody(SoapBody $body): self
{
$this->Body = $body;
return $this;
}
}

View File

@ -0,0 +1,53 @@
<?php
namespace App\Services\EDocument\Standards\Verifactu\Types;
use Symfony\Component\Serializer\Annotation\SerializedName;
class SoapHeader
{
/** @var string|null */
#[SerializedName('soapenv:Action')]
protected $Action;
/** @var string|null */
#[SerializedName('soapenv:MessageID')]
protected $MessageID;
/** @var string|null */
#[SerializedName('soapenv:To')]
protected $To;
public function getAction(): ?string
{
return $this->Action;
}
public function setAction(?string $action): self
{
$this->Action = $action;
return $this;
}
public function getMessageID(): ?string
{
return $this->MessageID;
}
public function setMessageID(?string $messageID): self
{
$this->MessageID = $messageID;
return $this;
}
public function getTo(): ?string
{
return $this->To;
}
public function setTo(?string $to): self
{
$this->To = $to;
return $this;
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace App\Services\EDocument\Standards\Verifactu\Types;
use Symfony\Component\Serializer\Annotation\SerializedName;
class Subsanacion
{
/** @var string */
#[SerializedName('sum1:NumRegistroAcuerdoFacturacion')]
protected $NumRegistroAcuerdoFacturacion;
/** @var string */
#[SerializedName('sum1:FechaRegistroAcuerdoFacturacion')]
protected $FechaRegistroAcuerdoFacturacion;
public function getNumRegistroAcuerdoFacturacion(): string
{
return $this->NumRegistroAcuerdoFacturacion;
}
public function setNumRegistroAcuerdoFacturacion(string $numRegistroAcuerdoFacturacion): self
{
if (strlen($numRegistroAcuerdoFacturacion) > 15) {
throw new \InvalidArgumentException('NumRegistroAcuerdoFacturacion must not exceed 15 characters');
}
$this->NumRegistroAcuerdoFacturacion = $numRegistroAcuerdoFacturacion;
return $this;
}
public function getFechaRegistroAcuerdoFacturacion(): string
{
return $this->FechaRegistroAcuerdoFacturacion;
}
public function setFechaRegistroAcuerdoFacturacion(string $fechaRegistroAcuerdoFacturacion): self
{
// Validate date format YYYY-MM-DD
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $fechaRegistroAcuerdoFacturacion)) {
throw new \InvalidArgumentException('FechaRegistroAcuerdoFacturacion must be in YYYY-MM-DD format');
}
// Validate date components
list($year, $month, $day) = explode('-', $fechaRegistroAcuerdoFacturacion);
if (!checkdate((int)$month, (int)$day, (int)$year)) {
throw new \InvalidArgumentException('Invalid date');
}
$this->FechaRegistroAcuerdoFacturacion = $fechaRegistroAcuerdoFacturacion;
return $this;
}
}

View File

@ -0,0 +1,187 @@
<?php
/**
* VerifactuClient.php
*
* SOAP client for sending invoices (facturas) to AEAT Verifactu service.
* Supports production and test endpoints via a mode switch.
*/
namespace App\Services\EDocument\Standards\Verifactu;
use App\Services\EDocument\Standards\Verifactu\Types\Cabecera;
use App\Services\EDocument\Standards\Verifactu\Types\Desglose;
use App\Services\EDocument\Standards\Verifactu\Types\DesgloseRectificacion;
use App\Services\EDocument\Standards\Verifactu\Types\Destinatarios;
use App\Services\EDocument\Standards\Verifactu\Types\Detalle;
use App\Services\EDocument\Standards\Verifactu\Types\DetalleDesglose;
use App\Services\EDocument\Standards\Verifactu\Types\Encadenamiento;
use App\Services\EDocument\Standards\Verifactu\Types\IDDestinatario;
use App\Services\EDocument\Standards\Verifactu\Types\IDFactura;
use App\Services\EDocument\Standards\Verifactu\Types\IDFacturaAR;
use App\Services\EDocument\Standards\Verifactu\Types\IDFacturaExpedida;
use App\Services\EDocument\Standards\Verifactu\Types\IDOtro;
use App\Services\EDocument\Standards\Verifactu\Types\ImporteSgn14_2;
use App\Services\EDocument\Standards\Verifactu\Types\Incidencia;
use App\Services\EDocument\Standards\Verifactu\Types\ObligadoEmision;
use App\Services\EDocument\Standards\Verifactu\Types\OperacionExenta;
use App\Services\EDocument\Standards\Verifactu\Types\PersonaFisicaJuridica;
use App\Services\EDocument\Standards\Verifactu\Types\PersonaFisicaJuridicaES;
use App\Services\EDocument\Standards\Verifactu\Types\RechazoPrevio;
use App\Services\EDocument\Standards\Verifactu\Types\RegFactuSistemaFacturacion;
use App\Services\EDocument\Standards\Verifactu\Types\RegistroAlta;
use App\Services\EDocument\Standards\Verifactu\Types\RegistroAnterior;
use App\Services\EDocument\Standards\Verifactu\Types\RegistroFactura;
use App\Services\EDocument\Standards\Verifactu\Types\RegistroFacturacionAlta;
use App\Services\EDocument\Standards\Verifactu\Types\RegistroFacturacionAnulacion;
use App\Services\EDocument\Standards\Verifactu\Types\SistemaInformatico;
use App\Services\EDocument\Standards\Verifactu\Types\Subsanacion;
class VerifactuClient
{
const MODE_PROD = 'prod';
const MODE_TEST = 'test';
/**
* @var array<string,string>
*/
private static array $endpoints = [
self::MODE_PROD => 'https://www1.agenciatributaria.gob.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP',
self::MODE_TEST => 'https://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP',
];
private \SoapClient $client;
private string $mode;
/**
* @param string $mode One of VerifactuClient::MODE_PROD or MODE_TEST
* @param string|null $wsdl Path to the WSDL file; defaults to xsd/SistemaFacturacion.wsdl
* @param array $options Additional SoapClient options
*
* @throws \InvalidArgumentException
* @throws \SoapFault
*/
public function __construct(string $mode = self::MODE_TEST, string $wsdl = null, array $options = [])
{
if (!isset(self::$endpoints[$mode])) {
throw new \InvalidArgumentException("Invalid mode '{$mode}', must be 'prod' or 'test'.");
}
$this->mode = $mode;
$endpoint = self::$endpoints[$mode];
$wsdlPath = $wsdl ?: __DIR__ . '/xsd/SistemaFacturacion.wsdl';
// Default SOAP client options with classmap for generated s
$defaultOpts = [
'trace' => true,
'exceptions' => true,
'cache_wsdl' => WSDL_CACHE_NONE,
'location' => $endpoint,
'soap_version' => SOAP_1_1,
'classmap' => [
'Cabecera' => Cabecera::class,
'Desglose' => Desglose::class,
'DesgloseRectificacion' => DesgloseRectificacion::class,
'Destinatarios' => Destinatarios::class,
'Detalle' => Detalle::class,
'DetalleDesglose' => DetalleDesglose::class,
'Encadenamiento' => Encadenamiento::class,
'IDDestinatario' => IDDestinatario::class,
'IDFactura' => IDFactura::class,
'IDFacturaAR' => IDFacturaAR::class,
'IDFacturaExpedida' => IDFacturaExpedida::class,
'IDOtro' => IDOtro::class,
'ImporteSgn14_2' => ImporteSgn14_2::class,
'Incidencia' => Incidencia::class,
'ObligadoEmision' => ObligadoEmision::class,
'OperacionExenta' => OperacionExenta::class,
'PersonaFisicaJuridica' => PersonaFisicaJuridica::class,
'PersonaFisicaJuridicaES' => PersonaFisicaJuridicaES::class,
'RechazoPrevio' => RechazoPrevio::class,
'RegFactuSistemaFacturacion' => RegFactuSistemaFacturacion::class,
'RegistroAlta' => RegistroAlta::class,
'RegistroAnterior' => RegistroAnterior::class,
'RegistroFactura' => RegistroFactura::class,
'RegistroFacturacionAlta' => RegistroFacturacionAlta::class,
'RegistroFacturacionAnulacion' => RegistroFacturacionAnulacion::class,
'RegistroFacturacionSubsanacion' => Subsanacion::class,
'SistemaInformatico' => SistemaInformatico::class,
],
];
$opts = array_merge($defaultOpts, $options);
$this->client = new \SoapClient($wsdlPath, $opts);
}
/**
* Send an invoice registration (alta) request
*
* @param RegistroAlta $registro
* @return mixed The SOAP response
* @throws \SoapFault
*/
public function sendRegistroAlta(RegistroAlta $registro)
{
$factura = new RegistroFactura();
$factura->setRegistroAlta($registro);
$wrapper = new RegFactuSistemaFacturacion();
$wrapper->addToRegistroFactura($factura);
return $this->sendRegistroFactura($wrapper);
}
/**
* Send an invoice cancellation (anulación) request
*
* @param RegistroFacturacionAnulacion $registro
* @return mixed The SOAP response
* @throws \SoapFault
*/
public function sendRegistroAnulacion(RegistroFacturacionAnulacion $registro)
{
$factura = new RegistroFactura();
$factura->setRegistroAnulacion($registro);
$wrapper = new RegFactuSistemaFacturacion();
$wrapper->addToRegistroFactura($factura);
return $this->sendRegistroFactura($wrapper);
}
/**
* Low-level send: SoapClient marshals the object per classmap
*
* @param RegFactuSistemaFacturacion $wrapper
* @return mixed The SOAP response
* @throws \SoapFault
*/
public function sendRegistroFactura(RegFactuSistemaFacturacion $wrapper)
{
return $this->client->__soapCall(
'RegFactuSistemaFacturacion',
['RegFactuSistemaFacturacion' => $wrapper]
);
}
/**
* Get the last raw request XML
*
* @return string
*/
public function getLastRequest(): string
{
return $this->client->__getLastRequest();
}
/**
* Get the last raw response XML
*
* @return string
*/
public function getLastResponse(): string
{
return $this->client->__getLastResponse();
}
}

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- editado con XMLSpy v2019 sp1 (x64) (http://www.altova.com) por AEAT (Agencia Estatal de Administracion Tributaria ((AEAT))) -->
<!-- edited with XMLSpy v2009 sp1 (http://www.altova.com) by PC Corporativo (AGENCIA TRIBUTARIA) -->
<schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:sfLRC="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/ConsultaLR.xsd" xmlns:sf="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd" targetNamespace="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/ConsultaLR.xsd" elementFormDefault="qualified">
<import namespace="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd" schemaLocation="SuministroInformacion.xsd"/>
<!-- edited with XMLSpy v2009 sp1 (http://www.altova.com) by PC Corporativo (AGENCIA TRIBUTARIA) -->
<element name="ConsultaFactuSistemaFacturacion" type="sfLRC:ConsultaFactuSistemaFacturacionType">
<annotation>
<documentation>Servicio de consulta Registros Facturacion</documentation>
</annotation>
</element>
<complexType name="ConsultaFactuSistemaFacturacionType">
<sequence>
<element name="Cabecera" type="sf:CabeceraConsultaSf"/>
<element name="FiltroConsulta" type="sfLRC:LRFiltroRegFacturacionType"/>
</sequence>
</complexType>
<complexType name="LRFiltroRegFacturacionType">
<sequence>
<!-- <element name="PeriodoImputacion" type="sf:PeriodoImputacionType"/> -->
<element name="NumSerieFactura" type="sf:TextoIDFacturaType" minOccurs="0">
<annotation>
<documentation xml:lang="es"> Nº Serie+Nº Factura de la Factura del Emisor.</documentation>
</annotation>
</element>
<element name="Contraparte" type="sf:ContraparteConsultaType" minOccurs="0">
<annotation>
<documentation xml:lang="es">Contraparte del NIF de la cabecera que realiza la consulta.
Obligado si la cosulta la realiza el Destinatario de los registros de facturacion.
Destinatario si la cosulta la realiza el Obligado dde los registros de facturacion.</documentation>
</annotation>
</element>
<element name="FechaExpedicionFactura" type="sf:FechaExpedicionConsultaType" minOccurs="0"/>
<element name="SistemaInformatico" type="sf:SistemaInformaticoType" minOccurs="0"/>
<element name="ClavePaginacion" type="sf:IDFacturaExpedidaBCType" minOccurs="0"/>
</sequence>
</complexType>
</schema>

View File

@ -0,0 +1,823 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- editado con XMLSpy v2019 sp1 (x64) (http://www.altova.com) por AEAT (Agencia Estatal de Administracion Tributaria ((AEAT))) -->
<!-- edited with XMLSpy v2009 sp1 (http://www.altova.com) by PC Corporativo (AGENCIA TRIBUTARIA) -->
<schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:sf="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/EventosSIF.xsd" targetNamespace="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/EventosSIF.xsd" elementFormDefault="qualified">
<import namespace="http://www.w3.org/2000/09/xmldsig#" schemaLocation="http://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd"/>
<element name="RegistroEvento">
<complexType>
<sequence>
<element name="IDVersion" type="sf:VersionType"/>
<element name="Evento" type="sf:EventoType"/>
</sequence>
</complexType>
</element>
<complexType name="EventoType">
<sequence>
<element name="SistemaInformatico" type="sf:SistemaInformaticoType"/>
<element name="ObligadoEmision" type="sf:PersonaFisicaJuridicaESType">
<annotation>
<documentation xml:lang="es"> Obligado a expedir la factura. </documentation>
</annotation>
</element>
<element name="EmitidaPorTerceroODestinatario" type="sf:TercerosODestinatarioType" minOccurs="0"/>
<element name="TerceroODestinatario" type="sf:PersonaFisicaJuridicaType" minOccurs="0"/>
<element name="FechaHoraHusoGenEvento" type="dateTime">
<annotation>
<documentation xml:lang="es">Formato: YYYY-MM-DDThh:mm:ssTZD (ej: 2024-01-01T19:20:30+01:00) (ISO 8601)</documentation>
</annotation>
</element>
<element name="TipoEvento" type="sf:TipoEventoType"/>
<element name="DatosPropiosEvento" type="sf:DatosPropiosEventoType" minOccurs="0"/>
<element name="OtrosDatosEvento" type="sf:TextMax100Type" minOccurs="0"/>
<element name="Encadenamiento" type="sf:EncadenamientoType"/>
<element name="TipoHuella" type="sf:TipoHuellaType"/>
<element name="HuellaEvento" type="sf:TextMax64Type"/>
<element ref="ds:Signature"/>
</sequence>
</complexType>
<complexType name="SistemaInformaticoType">
<sequence>
<sequence>
<element name="NombreRazon" type="sf:TextMax120Type"/>
<choice>
<element name="NIF" type="sf:NIFType"/>
<element name="IDOtro" type="sf:IDOtroType"/>
</choice>
</sequence>
<element name="NombreSistemaInformatico" type="sf:TextMax30Type" minOccurs="0"/>
<element name="IdSistemaInformatico" type="sf:TextMax2Type"/>
<element name="Version" type="sf:TextMax50Type"/>
<element name="NumeroInstalacion" type="sf:TextMax100Type"/>
<element name="TipoUsoPosibleSoloVerifactu" type="sf:SiNoType" minOccurs="0"/>
<element name="TipoUsoPosibleMultiOT" type="sf:SiNoType" minOccurs="0"/>
<element name="IndicadorMultiplesOT" type="sf:SiNoType" minOccurs="0"/>
</sequence>
</complexType>
<complexType name="DatosPropiosEventoType">
<choice>
<element name="LanzamientoProcesoDeteccionAnomaliasRegFacturacion" type="sf:LanzamientoProcesoDeteccionAnomaliasRegFacturacionType"/>
<element name="DeteccionAnomaliasRegFacturacion" type="sf:DeteccionAnomaliasRegFacturacionType"/>
<element name="LanzamientoProcesoDeteccionAnomaliasRegEvento" type="sf:LanzamientoProcesoDeteccionAnomaliasRegEventoType"/>
<element name="DeteccionAnomaliasRegEvento" type="sf:DeteccionAnomaliasRegEventoType"/>
<element name="ExportacionRegFacturacionPeriodo" type="sf:ExportacionRegFacturacionPeriodoType"/>
<element name="ExportacionRegEventoPeriodo" type="sf:ExportacionRegEventoPeriodoType"/>
<element name="ResumenEventos" type="sf:ResumenEventosType"/>
</choice>
</complexType>
<complexType name="EncadenamientoType">
<choice>
<element name="PrimerEvento" type="sf:TextMax1Type"/>
<element name="EventoAnterior" type="sf:RegEventoAntType"/>
</choice>
</complexType>
<complexType name="LanzamientoProcesoDeteccionAnomaliasRegFacturacionType">
<sequence>
<element name="RealizadoProcesoSobreIntegridadHuellasRegFacturacion" type="sf:SiNoType"/>
<element name="NumeroDeRegistrosFacturacionProcesadosSobreIntegridadHuellas" type="sf:DigitosMax7Type" minOccurs="0"/>
<element name="RealizadoProcesoSobreIntegridadFirmasRegFacturacion" type="sf:SiNoType"/>
<element name="NumeroDeRegistrosFacturacionProcesadosSobreIntegridadFirmas" type="sf:DigitosMax7Type" minOccurs="0"/>
<element name="RealizadoProcesoSobreTrazabilidadCadenaRegFacturacion" type="sf:SiNoType"/>
<element name="NumeroDeRegistrosFacturacionProcesadosSobreTrazabilidadCadena" type="sf:DigitosMax7Type" minOccurs="0"/>
<element name="RealizadoProcesoSobreTrazabilidadFechasRegFacturacion" type="sf:SiNoType"/>
<element name="NumeroDeRegistrosFacturacionProcesadosSobreTrazabilidadFechas" type="sf:DigitosMax7Type" minOccurs="0"/>
</sequence>
</complexType>
<complexType name="DeteccionAnomaliasRegFacturacionType">
<sequence>
<element name="TipoAnomalia" type="sf:TipoAnomaliaType"/>
<element name="OtrosDatosAnomalia" type="sf:TextMax100Type" minOccurs="0"/>
<element name="RegistroFacturacionAnomalo" type="sf:IDFacturaExpedidaType" minOccurs="0"/>
</sequence>
</complexType>
<complexType name="LanzamientoProcesoDeteccionAnomaliasRegEventoType">
<sequence>
<element name="RealizadoProcesoSobreIntegridadHuellasRegEvento" type="sf:SiNoType"/>
<element name="NumeroDeRegistrosEventoProcesadosSobreIntegridadHuellas" type="sf:DigitosMax5Type" minOccurs="0"/>
<element name="RealizadoProcesoSobreIntegridadFirmasRegEvento" type="sf:SiNoType"/>
<element name="NumeroDeRegistrosEventoProcesadosSobreIntegridadFirmas" type="sf:DigitosMax5Type" minOccurs="0"/>
<element name="RealizadoProcesoSobreTrazabilidadCadenaRegEvento" type="sf:SiNoType"/>
<element name="NumeroDeRegistrosEventoProcesadosSobreTrazabilidadCadena" type="sf:DigitosMax5Type" minOccurs="0"/>
<element name="RealizadoProcesoSobreTrazabilidadFechasRegEvento" type="sf:SiNoType"/>
<element name="NumeroDeRegistrosEventoProcesadosSobreTrazabilidadFechas" type="sf:DigitosMax5Type" minOccurs="0"/>
</sequence>
</complexType>
<complexType name="DeteccionAnomaliasRegEventoType">
<sequence>
<element name="TipoAnomalia" type="sf:TipoAnomaliaType"/>
<element name="OtrosDatosAnomalia" type="sf:TextMax100Type" minOccurs="0"/>
<element name="RegEventoAnomalo" type="sf:RegEventoType" minOccurs="0"/>
</sequence>
</complexType>
<complexType name="ExportacionRegFacturacionPeriodoType">
<sequence>
<element name="FechaHoraHusoInicioPeriodoExport" type="dateTime">
<annotation>
<documentation xml:lang="es">Formato: YYYY-MM-DDThh:mm:ssTZD (ej: 2024-01-01T19:20:30+01:00) (ISO 8601)</documentation>
</annotation>
</element>
<element name="FechaHoraHusoFinPeriodoExport" type="dateTime">
<annotation>
<documentation xml:lang="es">Formato: YYYY-MM-DDThh:mm:ssTZD (ej: 2024-01-01T19:20:30+01:00) (ISO 8601)</documentation>
</annotation>
</element>
<element name="RegistroFacturacionInicialPeriodo" type="sf:IDFacturaExpedidaHuellaType"/>
<element name="RegistroFacturacionFinalPeriodo" type="sf:IDFacturaExpedidaHuellaType"/>
<element name="NumeroDeRegistrosFacturacionAltaExportados" type="sf:DigitosMax9Type"/>
<element name="SumaCuotaTotalAlta" type="sf:ImporteSgn12.2Type"/>
<element name="SumaImporteTotalAlta" type="sf:ImporteSgn12.2Type"/>
<element name="NumeroDeRegistrosFacturacionAnulacionExportados" type="sf:DigitosMax9Type"/>
<element name="RegistrosFacturacionExportadosDejanDeConservarse" type="sf:SiNoType"/>
</sequence>
</complexType>
<complexType name="ExportacionRegEventoPeriodoType">
<sequence>
<element name="FechaHoraHusoInicioPeriodoExport" type="dateTime">
<annotation>
<documentation xml:lang="es">Formato: YYYY-MM-DDThh:mm:ssTZD (ej: 2024-01-01T19:20:30+01:00) (ISO 8601)</documentation>
</annotation>
</element>
<element name="FechaHoraHusoFinPeriodoExport" type="dateTime">
<annotation>
<documentation xml:lang="es">Formato: YYYY-MM-DDThh:mm:ssTZD (ej: 2024-01-01T19:20:30+01:00) (ISO 8601)</documentation>
</annotation>
</element>
<element name="RegistroEventoInicialPeriodo" type="sf:RegEventoType"/>
<element name="RegistroEventoFinalPeriodo" type="sf:RegEventoType"/>
<element name="NumeroDeRegEventoExportados" type="sf:DigitosMax7Type"/>
<element name="RegEventoExportadosDejanDeConservarse" type="sf:SiNoType"/>
</sequence>
</complexType>
<complexType name="ResumenEventosType">
<sequence>
<element name="TipoEvento" type="sf:TipoEventoAgrType" maxOccurs="20"/>
<element name="RegistroFacturacionInicialPeriodo" type="sf:IDFacturaExpedidaHuellaType" minOccurs="0"/>
<element name="RegistroFacturacionFinalPeriodo" type="sf:IDFacturaExpedidaHuellaType" minOccurs="0"/>
<element name="NumeroDeRegistrosFacturacionAltaGenerados" type="sf:DigitosMax6Type"/>
<element name="SumaCuotaTotalAlta" type="sf:ImporteSgn12.2Type"/>
<element name="SumaImporteTotalAlta" type="sf:ImporteSgn12.2Type"/>
<element name="NumeroDeRegistrosFacturacionAnulacionGenerados" type="sf:DigitosMax6Type"/>
</sequence>
</complexType>
<complexType name="RegEventoType">
<sequence>
<element name="TipoEvento" type="sf:TipoEventoType"/>
<element name="FechaHoraHusoEvento" type="dateTime">
<annotation>
<documentation xml:lang="es">Formato: YYYY-MM-DDThh:mm:ssTZD (ej: 2024-01-01T19:20:30+01:00) (ISO 8601)</documentation>
</annotation>
</element>
<element name="HuellaEvento" type="sf:TextMax64Type"/>
</sequence>
</complexType>
<complexType name="RegEventoAntType">
<sequence>
<element name="TipoEvento" type="sf:TipoEventoType"/>
<element name="FechaHoraHusoGenEvento" type="dateTime">
<annotation>
<documentation xml:lang="es">Formato: YYYY-MM-DDThh:mm:ssTZD (ej: 2024-01-01T19:20:30+01:00) (ISO 8601)</documentation>
</annotation>
</element>
<element name="HuellaEvento" type="sf:TextMax64Type"/>
</sequence>
</complexType>
<complexType name="TipoEventoAgrType">
<sequence>
<element name="TipoEvento" type="sf:TipoEventoType"/>
<element name="NumeroDeEventos" type="sf:DigitosMax4Type"/>
</sequence>
</complexType>
<!-- Datos de persona Física o jurídica : Denominación, representación, identificación (NIF) -->
<complexType name="PersonaFisicaJuridicaESType">
<annotation>
<documentation xml:lang="es">Datos de una persona física o jurídica Española con un NIF asociado</documentation>
</annotation>
<sequence>
<element name="NombreRazon" type="sf:TextMax120Type"/>
<element name="NIF" type="sf:NIFType"/>
</sequence>
</complexType>
<simpleType name="NIFType">
<annotation>
<documentation xml:lang="es">NIF</documentation>
</annotation>
<restriction base="string">
<length value="9"/>
</restriction>
</simpleType>
<!-- Datos de persona Física o jurídica : Denominación, representación, identificación (NIF/Otro) -->
<complexType name="PersonaFisicaJuridicaType">
<annotation>
<documentation xml:lang="es">Datos de una persona física o jurídica Española o Extranjera</documentation>
</annotation>
<sequence>
<element name="NombreRazon" type="sf:TextMax120Type"/>
<choice>
<element name="NIF" type="sf:NIFType"/>
<element name="IDOtro" type="sf:IDOtroType"/>
</choice>
</sequence>
</complexType>
<!-- Datos de persona Física o jurídica : Denominación, representación, identificación (NIF/Otro) -->
<complexType name="IDOtroType">
<annotation>
<documentation xml:lang="es">Identificador de persona Física o jurídica distinto del NIF
(Código pais, Tipo de Identificador, y hasta 15 caractéres)
No se permite CodigoPais=ES e IDType=01-NIFContraparte
para ese caso, debe utilizarse NIF en lugar de IDOtro.
</documentation>
</annotation>
<sequence>
<element name="CodigoPais" type="sf:CountryType2" minOccurs="0"/>
<element name="IDType" type="sf:PersonaFisicaJuridicaIDTypeType"/>
<element name="ID" type="sf:TextMax20Type"/>
</sequence>
</complexType>
<!-- Tercero o Destinatario -->
<simpleType name="TercerosODestinatarioType">
<restriction base="string">
<enumeration value="D">
<annotation>
<documentation xml:lang="es">Destinatario</documentation>
</annotation>
</enumeration>
<enumeration value="T">
<annotation>
<documentation xml:lang="es">Tercero</documentation>
</annotation>
</enumeration>
</restriction>
</simpleType>
<simpleType name="SiNoType">
<restriction base="string">
<enumeration value="S"/>
<enumeration value="N"/>
</restriction>
</simpleType>
<simpleType name="VersionType">
<restriction base="string">
<enumeration value="1.0"/>
</restriction>
</simpleType>
<!-- Cadena de 120 caracteres -->
<simpleType name="TextMax120Type">
<restriction base="string">
<maxLength value="120"/>
</restriction>
</simpleType>
<!-- Cadena de 100 caracteres -->
<simpleType name="TextMax100Type">
<restriction base="string">
<maxLength value="100"/>
</restriction>
</simpleType>
<!-- Cadena de 64 caracteres -->
<simpleType name="TextMax64Type">
<restriction base="string">
<maxLength value="64"/>
</restriction>
</simpleType>
<!-- Cadena de 60 caracteres -->
<simpleType name="TextMax60Type">
<restriction base="string">
<maxLength value="60"/>
</restriction>
</simpleType>
<!-- Cadena de 50 caracteres -->
<simpleType name="TextMax50Type">
<restriction base="string">
<maxLength value="50"/>
</restriction>
</simpleType>
<!-- Cadena de 30 caracteres -->
<simpleType name="TextMax30Type">
<restriction base="string">
<maxLength value="30"/>
</restriction>
</simpleType>
<!-- Cadena de 20 caracteres -->
<simpleType name="TextMax20Type">
<restriction base="string">
<maxLength value="20"/>
</restriction>
</simpleType>
<!-- Cadena de 2 caracteres -->
<simpleType name="TextMax2Type">
<restriction base="string">
<maxLength value="2"/>
</restriction>
</simpleType>
<!-- Cadena de 1 caracteres -->
<simpleType name="TextMax1Type">
<restriction base="string">
<maxLength value="1"/>
</restriction>
</simpleType>
<!-- Definición de un tipo simple restringido a 9 dígitos -->
<simpleType name="DigitosMax9Type">
<restriction base="string">
<maxLength value="9"/>
<pattern value="\d{1,9}"/>
</restriction>
</simpleType>
<!-- Definición de un tipo simple restringido a 7 dígitos -->
<simpleType name="DigitosMax7Type">
<restriction base="string">
<maxLength value="7"/>
<pattern value="\d{1,7}"/>
</restriction>
</simpleType>
<!-- Definición de un tipo simple restringido a 6 dígitos -->
<simpleType name="DigitosMax6Type">
<restriction base="string">
<maxLength value="6"/>
<pattern value="\d{1,6}"/>
</restriction>
</simpleType>
<!-- Definición de un tipo simple restringido a 5 dígitos -->
<simpleType name="DigitosMax5Type">
<restriction base="string">
<maxLength value="5"/>
<pattern value="\d{1,5}"/>
</restriction>
</simpleType>
<!-- Definición de un tipo simple restringido a 4 dígitos -->
<simpleType name="DigitosMax4Type">
<restriction base="string">
<maxLength value="4"/>
<pattern value="\d{1,4}"/>
</restriction>
</simpleType>
<!-- Fecha (dd-mm-yyyy) -->
<simpleType name="fecha">
<restriction base="string">
<length value="10"/>
<pattern value="\d{2,2}-\d{2,2}-\d{4,4}"/>
</restriction>
</simpleType>
<!-- Importe de 15 dígitos (12+2) "." como separador decimal -->
<simpleType name="ImporteSgn12.2Type">
<restriction base="string">
<pattern value="(\+|-)?\d{1,12}(\.\d{0,2})?"/>
</restriction>
</simpleType>
<!-- Tipo de identificador fiscal de persona Física o jurídica -->
<simpleType name="PersonaFisicaJuridicaIDTypeType">
<restriction base="string">
<enumeration value="02">
<annotation>
<documentation xml:lang="es">NIF-IVA</documentation>
</annotation>
</enumeration>
<enumeration value="03">
<annotation>
<documentation xml:lang="es">Pasaporte</documentation>
</annotation>
</enumeration>
<enumeration value="04">
<annotation>
<documentation xml:lang="es">IDEnPaisResidencia</documentation>
</annotation>
</enumeration>
<enumeration value="05">
<annotation>
<documentation xml:lang="es">Certificado Residencia</documentation>
</annotation>
</enumeration>
<enumeration value="06">
<annotation>
<documentation xml:lang="es">Otro documento Probatorio</documentation>
</annotation>
</enumeration>
<enumeration value="07">
<annotation>
<documentation xml:lang="es">No Censado</documentation>
</annotation>
</enumeration>
</restriction>
</simpleType>
<!-- Tipo Hash -->
<simpleType name="TipoHuellaType">
<restriction base="string">
<enumeration value="01">
<annotation>
<documentation xml:lang="es">SHA-256</documentation>
</annotation>
</enumeration>
</restriction>
</simpleType>
<simpleType name="TipoEventoType">
<restriction base="string">
<enumeration value="01">
<annotation>
<documentation xml:lang="es">Inicio del funcionamiento del sistema informático como «NO VERI*FACTU».</documentation>
</annotation>
</enumeration>
<enumeration value="02">
<annotation>
<documentation xml:lang="es">Fin del funcionamiento del sistema informático como «NO VERI*FACTU».</documentation>
</annotation>
</enumeration>
<enumeration value="03">
<annotation>
<documentation xml:lang="es">Lanzamiento del proceso de detección de anomalías en los registros de facturación.</documentation>
</annotation>
</enumeration>
<enumeration value="04">
<annotation>
<documentation xml:lang="es">Detección de anomalías en la integridad, inalterabilidad y trazabilidad de registros de facturación.</documentation>
</annotation>
</enumeration>
<enumeration value="05">
<annotation>
<documentation xml:lang="es">Lanzamiento del proceso de detección de anomalías en los registros de evento.</documentation>
</annotation>
</enumeration>
<enumeration value="06">
<annotation>
<documentation xml:lang="es">Detección de anomalías en la integridad, inalterabilidad y trazabilidad de registros de evento.</documentation>
</annotation>
</enumeration>
<enumeration value="07">
<annotation>
<documentation xml:lang="es">Restauración de copia de seguridad, cuando ésta se gestione desde el propio sistema informático de facturación.</documentation>
</annotation>
</enumeration>
<enumeration value="08">
<annotation>
<documentation xml:lang="es">Exportación de registros de facturación generados en un periodo.</documentation>
</annotation>
</enumeration>
<enumeration value="09">
<annotation>
<documentation xml:lang="es">Exportación de registros de evento generados en un periodo.</documentation>
</annotation>
</enumeration>
<enumeration value="10">
<annotation>
<documentation xml:lang="es">Registro resumen de eventos</documentation>
</annotation>
</enumeration>
<enumeration value="90">
<annotation>
<documentation xml:lang="es">Otros tipos de eventos a registrar voluntariamente por la persona o entidad productora del sistema informático.
</documentation>
</annotation>
</enumeration>
</restriction>
</simpleType>
<simpleType name="TipoAnomaliaType">
<restriction base="string">
<enumeration value="01">
<annotation>
<documentation xml:lang="es">Integridad-huella</documentation>
</annotation>
</enumeration>
<enumeration value="02">
<annotation>
<documentation xml:lang="es">Integridad-firma</documentation>
</annotation>
</enumeration>
<enumeration value="03">
<annotation>
<documentation xml:lang="es">Integridad - Otros</documentation>
</annotation>
</enumeration>
<enumeration value="04">
<annotation>
<documentation xml:lang="es">Trazabilidad-cadena-registro - Reg. no primero pero con reg. anterior no anotado o inexistente</documentation>
</annotation>
</enumeration>
<enumeration value="05">
<annotation>
<documentation xml:lang="es">Trazabilidad-cadena-registro - Reg. no último pero con reg. posterior no anotado o inexistente</documentation>
</annotation>
</enumeration>
<enumeration value="06">
<annotation>
<documentation xml:lang="es">Trazabilidad-cadena-registro - Otros</documentation>
</annotation>
</enumeration>
<enumeration value="07">
<annotation>
<documentation xml:lang="es">Trazabilidad-cadena-huella - Huella del reg. no se corresponde con la 'huella del reg. anterior' almacenada en el registro posterior</documentation>
</annotation>
</enumeration>
<enumeration value="08">
<annotation>
<documentation xml:lang="es">Trazabilidad-cadena-huella - Campo 'huella del reg. anterior' no se corresponde con la huella del reg. anterior</documentation>
</annotation>
</enumeration>
<enumeration value="09">
<annotation>
<documentation xml:lang="es">Trazabilidad-cadena-huella - Otros</documentation>
</annotation>
</enumeration>
<enumeration value="10">
<annotation>
<documentation xml:lang="es">Trazabilidad-cadena - Otros</documentation>
</annotation>
</enumeration>
<enumeration value="11">
<annotation>
<documentation xml:lang="es">Trazabilidad-fechas - Fecha-hora anterior a la fecha del reg. anterior</documentation>
</annotation>
</enumeration>
<enumeration value="12">
<annotation>
<documentation xml:lang="es">Trazabilidad-fechas - Fecha-hora posterior a la fecha del reg. posterior</documentation>
</annotation>
</enumeration>
<enumeration value="13">
<annotation>
<documentation xml:lang="es">Trazabilidad-fechas - Reg. con fecha-hora de generación posterior a la fecha-hora actual del sistema</documentation>
</annotation>
</enumeration>
<enumeration value="14">
<annotation>
<documentation xml:lang="es">Trazabilidad-fechas - Otros</documentation>
</annotation>
</enumeration>
<enumeration value="15">
<annotation>
<documentation xml:lang="es">Trazabilidad - Otros</documentation>
</annotation>
</enumeration>
<enumeration value="90">
<annotation>
<documentation xml:lang="es">Otros</documentation>
</annotation>
</enumeration>
</restriction>
</simpleType>
<complexType name="IDFacturaExpedidaType">
<annotation>
<documentation xml:lang="es"> Datos de identificación de factura expedida para operaciones de consulta</documentation>
</annotation>
<sequence>
<element name="IDEmisorFactura" type="sf:NIFType"/>
<element name="NumSerieFactura" type="sf:TextMax60Type"/>
<element name="FechaExpedicionFactura" type="sf:fecha"/>
</sequence>
</complexType>
<complexType name="IDFacturaExpedidaHuellaType">
<annotation>
<documentation xml:lang="es">Datos de encadenamiento </documentation>
</annotation>
<sequence>
<element name="IDEmisorFactura" type="sf:NIFType"/>
<element name="NumSerieFactura" type="sf:TextMax60Type"/>
<element name="FechaExpedicionFactura" type="sf:fecha"/>
<element name="Huella" type="sf:TextMax64Type"/>
</sequence>
</complexType>
<!-- ISO 3166-1 alpha-2 codes -->
<simpleType name="CountryType2">
<restriction base="string">
<enumeration value="AF"/>
<enumeration value="AL"/>
<enumeration value="DE"/>
<enumeration value="AD"/>
<enumeration value="AO"/>
<enumeration value="AI"/>
<enumeration value="AQ"/>
<enumeration value="AG"/>
<enumeration value="SA"/>
<enumeration value="DZ"/>
<enumeration value="AR"/>
<enumeration value="AM"/>
<enumeration value="AW"/>
<enumeration value="AU"/>
<enumeration value="AT"/>
<enumeration value="AZ"/>
<enumeration value="BS"/>
<enumeration value="BH"/>
<enumeration value="BD"/>
<enumeration value="BB"/>
<enumeration value="BE"/>
<enumeration value="BZ"/>
<enumeration value="BJ"/>
<enumeration value="BM"/>
<enumeration value="BY"/>
<enumeration value="BO"/>
<enumeration value="BA"/>
<enumeration value="BW"/>
<enumeration value="BV"/>
<enumeration value="BR"/>
<enumeration value="BN"/>
<enumeration value="BG"/>
<enumeration value="BF"/>
<enumeration value="BI"/>
<enumeration value="BT"/>
<enumeration value="CV"/>
<enumeration value="KY"/>
<enumeration value="KH"/>
<enumeration value="CM"/>
<enumeration value="CA"/>
<enumeration value="CF"/>
<enumeration value="CC"/>
<enumeration value="CO"/>
<enumeration value="KM"/>
<enumeration value="CG"/>
<enumeration value="CD"/>
<enumeration value="CK"/>
<enumeration value="KP"/>
<enumeration value="KR"/>
<enumeration value="CI"/>
<enumeration value="CR"/>
<enumeration value="HR"/>
<enumeration value="CU"/>
<enumeration value="TD"/>
<enumeration value="CZ"/>
<enumeration value="CL"/>
<enumeration value="CN"/>
<enumeration value="CY"/>
<enumeration value="CW"/>
<enumeration value="DK"/>
<enumeration value="DM"/>
<enumeration value="DO"/>
<enumeration value="EC"/>
<enumeration value="EG"/>
<enumeration value="AE"/>
<enumeration value="ER"/>
<enumeration value="SK"/>
<enumeration value="SI"/>
<enumeration value="ES"/>
<enumeration value="US"/>
<enumeration value="EE"/>
<enumeration value="ET"/>
<enumeration value="FO"/>
<enumeration value="PH"/>
<enumeration value="FI"/>
<enumeration value="FJ"/>
<enumeration value="FR"/>
<enumeration value="GA"/>
<enumeration value="GM"/>
<enumeration value="GE"/>
<enumeration value="GS"/>
<enumeration value="GH"/>
<enumeration value="GI"/>
<enumeration value="GD"/>
<enumeration value="GR"/>
<enumeration value="GL"/>
<enumeration value="GU"/>
<enumeration value="GT"/>
<enumeration value="GG"/>
<enumeration value="GN"/>
<enumeration value="GQ"/>
<enumeration value="GW"/>
<enumeration value="GY"/>
<enumeration value="HT"/>
<enumeration value="HM"/>
<enumeration value="HN"/>
<enumeration value="HK"/>
<enumeration value="HU"/>
<enumeration value="IN"/>
<enumeration value="ID"/>
<enumeration value="IR"/>
<enumeration value="IQ"/>
<enumeration value="IE"/>
<enumeration value="IM"/>
<enumeration value="IS"/>
<enumeration value="IL"/>
<enumeration value="IT"/>
<enumeration value="JM"/>
<enumeration value="JP"/>
<enumeration value="JE"/>
<enumeration value="JO"/>
<enumeration value="KZ"/>
<enumeration value="KE"/>
<enumeration value="KG"/>
<enumeration value="KI"/>
<enumeration value="KW"/>
<enumeration value="LA"/>
<enumeration value="LS"/>
<enumeration value="LV"/>
<enumeration value="LB"/>
<enumeration value="LR"/>
<enumeration value="LY"/>
<enumeration value="LI"/>
<enumeration value="LT"/>
<enumeration value="LU"/>
<enumeration value="XG"/>
<enumeration value="MO"/>
<enumeration value="MK"/>
<enumeration value="MG"/>
<enumeration value="MY"/>
<enumeration value="MW"/>
<enumeration value="MV"/>
<enumeration value="ML"/>
<enumeration value="MT"/>
<enumeration value="FK"/>
<enumeration value="MP"/>
<enumeration value="MA"/>
<enumeration value="MH"/>
<enumeration value="MU"/>
<enumeration value="MR"/>
<enumeration value="YT"/>
<enumeration value="UM"/>
<enumeration value="MX"/>
<enumeration value="FM"/>
<enumeration value="MD"/>
<enumeration value="MC"/>
<enumeration value="MN"/>
<enumeration value="ME"/>
<enumeration value="MS"/>
<enumeration value="MZ"/>
<enumeration value="MM"/>
<enumeration value="NA"/>
<enumeration value="NR"/>
<enumeration value="CX"/>
<enumeration value="NP"/>
<enumeration value="NI"/>
<enumeration value="NE"/>
<enumeration value="NG"/>
<enumeration value="NU"/>
<enumeration value="NF"/>
<enumeration value="NO"/>
<enumeration value="NC"/>
<enumeration value="NZ"/>
<enumeration value="IO"/>
<enumeration value="OM"/>
<enumeration value="NL"/>
<enumeration value="BQ"/>
<enumeration value="PK"/>
<enumeration value="PW"/>
<enumeration value="PA"/>
<enumeration value="PG"/>
<enumeration value="PY"/>
<enumeration value="PE"/>
<enumeration value="PN"/>
<enumeration value="PF"/>
<enumeration value="PL"/>
<enumeration value="PT"/>
<enumeration value="PR"/>
<enumeration value="QA"/>
<enumeration value="GB"/>
<enumeration value="RW"/>
<enumeration value="RO"/>
<enumeration value="RU"/>
<enumeration value="SB"/>
<enumeration value="SV"/>
<enumeration value="WS"/>
<enumeration value="AS"/>
<enumeration value="KN"/>
<enumeration value="SM"/>
<enumeration value="SX"/>
<enumeration value="PM"/>
<enumeration value="VC"/>
<enumeration value="SH"/>
<enumeration value="LC"/>
<enumeration value="ST"/>
<enumeration value="SN"/>
<enumeration value="RS"/>
<enumeration value="SC"/>
<enumeration value="SL"/>
<enumeration value="SG"/>
<enumeration value="SY"/>
<enumeration value="SO"/>
<enumeration value="LK"/>
<enumeration value="SZ"/>
<enumeration value="ZA"/>
<enumeration value="SD"/>
<enumeration value="SS"/>
<enumeration value="SE"/>
<enumeration value="CH"/>
<enumeration value="SR"/>
<enumeration value="TH"/>
<enumeration value="TW"/>
<enumeration value="TZ"/>
<enumeration value="TJ"/>
<enumeration value="PS"/>
<enumeration value="TF"/>
<enumeration value="TL"/>
<enumeration value="TG"/>
<enumeration value="TK"/>
<enumeration value="TO"/>
<enumeration value="TT"/>
<enumeration value="TN"/>
<enumeration value="TC"/>
<enumeration value="TM"/>
<enumeration value="TR"/>
<enumeration value="TV"/>
<enumeration value="UA"/>
<enumeration value="UG"/>
<enumeration value="UY"/>
<enumeration value="UZ"/>
<enumeration value="VU"/>
<enumeration value="VA"/>
<enumeration value="VE"/>
<enumeration value="VN"/>
<enumeration value="VG"/>
<enumeration value="VI"/>
<enumeration value="WF"/>
<enumeration value="YE"/>
<enumeration value="DJ"/>
<enumeration value="ZM"/>
<enumeration value="ZW"/>
<enumeration value="QU"/>
<enumeration value="XB"/>
<enumeration value="XU"/>
<enumeration value="XN"/>
</restriction>
</simpleType>
</schema>

View File

@ -0,0 +1,195 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- editado con XMLSpy v2019 sp1 (x64) (http://www.altova.com) por AEAT (Agencia Estatal de Administracion Tributaria ((AEAT))) -->
<!-- edited with XMLSpy v2009 sp1 (http://www.altova.com) by PC Corporativo (AGENCIA TRIBUTARIA) -->
<schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:sfLRRC="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/RespuestaConsultaLR.xsd" xmlns:sf="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd" targetNamespace="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/RespuestaConsultaLR.xsd" elementFormDefault="qualified">
<import namespace="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd" schemaLocation="SuministroInformacion.xsd"/>
<!-- edited with XMLSpy v2009 sp1 (http://www.altova.com) by PC Corporativo (AGENCIA TRIBUTARIA) -->
<element name="RespuestaConsultaFactuSistemaFacturacion" type="sfLRRC:RespuestaConsultaFactuSistemaFacturacionType">
<annotation>
<documentation>Servicio de consulta de regIstros de facturacion</documentation>
</annotation>
</element>
<complexType name="RespuestaConsultaFactuSistemaFacturacionType">
<complexContent>
<extension base="sfLRRC:RespuestaConsultaType">
<sequence>
<element name="RegistroRespuestaConsultaFactuSistemaFacturacion" type="sfLRRC:RegistroRespuestaConsultaRegFacturacionType" minOccurs="0" maxOccurs="10000"/>
<element name="ClavePaginacion" type="sf:IDFacturaExpedidaBCType" minOccurs="0"/>
</sequence>
</extension>
</complexContent>
</complexType>
<complexType name="EstadoRegFactuType">
<sequence>
<element name="TimestampUltimaModificacion" type="dateTime"/>
<element name="EstadoRegistro" type="sfLRRC:EstadoRegistroType">
<annotation>
<documentation xml:lang="es">
Estado del registro almacenado en el sistema. Los estados posibles son: Correcta, AceptadaConErrores y Anulada
</documentation>
</annotation>
</element>
<element name="CodigoErrorRegistro" type="sfLRRC:ErrorDetalleType" minOccurs="0">
<annotation>
<documentation xml:lang="es">
Código del error de registro, en su caso.
</documentation>
</annotation>
</element>
<element name="DescripcionErrorRegistro" type="sf:TextMax500Type" minOccurs="0">
<annotation>
<documentation xml:lang="es">
Descripción detallada del error de registro, en su caso.
</documentation>
</annotation>
</element>
</sequence>
</complexType>
<complexType name="RegistroRespuestaConsultaRegFacturacionType">
<sequence>
<element name="IDFactura" type="sf:IDFacturaExpedidaType"/>
<element name="DatosRegistroFacturacion" type="sfLRRC:RespuestaDatosRegistroFacturacionType"/>
<element name="DatosPresentacion" type="sf:DatosPresentacion2Type" minOccurs="0"/>
<element name="EstadoRegistro" type="sfLRRC:EstadoRegFactuType" />
</sequence>
</complexType>
<complexType name="RespuestaConsultaType">
<sequence>
<element name="Cabecera" type="sf:CabeceraConsultaSf"/>
<element name="PeriodoImputacion">
<complexType>
<annotation>
<documentation xml:lang="es"> Período al que corresponden los apuntes. todos los apuntes deben corresponder al mismo período impositivo </documentation>
</annotation>
<sequence>
<element name="Ejercicio" type="sf:YearType"/>
<element name="Periodo" type="sf:TipoPeriodoType"/>
</sequence>
</complexType>
</element>
<element name="IndicadorPaginacion" type="sfLRRC:IndicadorPaginacionType"/>
<element name="ResultadoConsulta" type="sfLRRC:ResultadoConsultaType"/>
</sequence>
</complexType>
<!-- Datos del registro de facturacion -->
<complexType name="RespuestaDatosRegistroFacturacionType">
<annotation>
<documentation xml:lang="es"> Apunte correspondiente al libro de facturas expedidas. </documentation>
</annotation>
<sequence>
<element name="RefExterna" type="sf:TextMax70Type" minOccurs="0"/>
<element name="Subsanacion" type="sf:SubsanacionType" minOccurs="0"/>
<element name="RechazoPrevio" type="sf:RechazoPrevioType" minOccurs="0"/>
<element name="SinRegistroPrevio" type="sf:SinRegistroPrevioType" minOccurs="0"/>
<element name="GeneradoPor" type="sf:GeneradoPorType" minOccurs="0"/>
<element name="Generador" type="sf:PersonaFisicaJuridicaType" minOccurs="0"/>
<element name="TipoFactura" type="sf:ClaveTipoFacturaType" minOccurs="0">
<annotation>
<documentation xml:lang="es"> Clave del tipo de factura </documentation>
</annotation>
</element>
<element name="TipoRectificativa" type="sf:ClaveTipoRectificativaType" minOccurs="0">
<annotation>
<documentation xml:lang="es"> Identifica si el tipo de factura rectificativa es por sustitución o por diferencia </documentation>
</annotation>
</element>
<element name="FacturasRectificadas" minOccurs="0">
<complexType>
<annotation>
<documentation xml:lang="es">El ID de las facturas rectificadas, únicamente se rellena en el caso de rectificación de facturas</documentation>
</annotation>
<sequence>
<element name="IDFacturaRectificada" type="sf:IDFacturaARType" maxOccurs="1000"/>
</sequence>
</complexType>
</element>
<element name="FacturasSustituidas" minOccurs="0">
<complexType>
<annotation>
<documentation xml:lang="es">El ID de las facturas sustituidas, únicamente se rellena en el caso de facturas sustituidas</documentation>
</annotation>
<sequence>
<element name="IDFacturaSustituida" type="sf:IDFacturaARType" maxOccurs="1000"/>
</sequence>
</complexType>
</element>
<element name="ImporteRectificacion" type="sf:DesgloseRectificacionType" minOccurs="0"/>
<element name="FechaOperacion" type="sf:fecha" minOccurs="0"/>
<element name="DescripcionOperacion" type="sf:TextMax500Type" minOccurs="0"/>
<element name="FacturaSimplificadaArt7273" type="sf:SimplificadaCualificadaType" minOccurs="0"/>
<element name="FacturaSinIdentifDestinatarioArt61d" type="sf:CompletaSinDestinatarioType" minOccurs="0"/>
<element name="Macrodato" type="sf:MacrodatoType" minOccurs="0"/>
<element name="EmitidaPorTerceroODestinatario" type="sf:TercerosODestinatarioType" minOccurs="0"/>
<element name="Tercero" type="sf:PersonaFisicaJuridicaType" minOccurs="0">
<annotation>
<documentation xml:lang="es"> Tercero que expida la factura y/o genera el registro de alta. </documentation>
</annotation>
</element>
<element name="Destinatarios" minOccurs="0">
<complexType>
<annotation>
<documentation xml:lang="es">Contraparte de la operación. Cliente</documentation>
</annotation>
<sequence>
<element name="IDDestinatario" type="sf:PersonaFisicaJuridicaType" maxOccurs="1000"/>
</sequence>
</complexType>
</element>
<element name="Cupon" type="sf:CuponType" minOccurs="0"/>
<element name="Desglose" type="sf:DesgloseType" minOccurs="0"/>
<element name="CuotaTotal" type="sf:ImporteSgn12.2Type" minOccurs="0"/>
<element name="ImporteTotal" type="sf:ImporteSgn12.2Type" minOccurs="0"/>
<element name="Encadenamiento" minOccurs="0">
<complexType>
<choice>
<element name="PrimerRegistro" type="sf:PrimerRegistroCadenaType"/>
<element name="RegistroAnterior" type="sf:EncadenamientoFacturaAnteriorType"/>
</choice>
</complexType>
</element>
<element name="FechaHoraHusoGenRegistro" type="dateTime" minOccurs="0"/>
<element name="NumRegistroAcuerdoFacturacion" type="sf:TextMax15Type" minOccurs="0"/>
<element name="IdAcuerdoSistemaInformatico" type="sf:TextMax16Type" minOccurs="0"/>
<element name="TipoHuella" type="sf:TipoHuellaType" minOccurs="0"/>
<element name="Huella" type="sf:TextMax64Type" minOccurs="0"/>
<element name="NifRepresentante" type="sf:NIFType" minOccurs="0"/>
<element name="FechaFinVeriFactu" type="sf:fecha" minOccurs="0"/>
<element name="Incidencia" type="sf:IncidenciaType" minOccurs="0"/>
</sequence>
</complexType>
<simpleType name="IndicadorPaginacionType">
<restriction base="string">
<enumeration value="S"/>
<enumeration value="N"/>
</restriction>
</simpleType>
<simpleType name="ResultadoConsultaType">
<restriction base="string">
<enumeration value="ConDatos"/>
<enumeration value="SinDatos"/>
</restriction>
</simpleType>
<simpleType name="ErrorDetalleType">
<restriction base="integer"/>
</simpleType>
<!-- Estado del registro almacenado en el sistema -->
<simpleType name="EstadoRegistroType">
<restriction base="string">
<enumeration value="Correcta">
<annotation>
<documentation xml:lang="es">El registro se almacenado sin errores</documentation>
</annotation>
</enumeration>
<enumeration value="AceptadaConErrores">
<annotation>
<documentation xml:lang="es">El registro se almacenado tiene algunos errores. Ver detalle del error</documentation>
</annotation>
</enumeration>
<enumeration value="Anulada">
<annotation>
<documentation xml:lang="es">El registro almacenado ha sido anulado</documentation>
</annotation>
</enumeration>
</restriction>
</simpleType>
</schema>

View File

@ -0,0 +1,139 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- editado con XMLSpy v2019 sp1 (x64) (http://www.altova.com) por Puesto de Trabajo (Agencia Estatal de Administracion Tributaria ((AEAT))) -->
<!-- edited with XMLSpy v2009 sp1 (http://www.altova.com) by PC Corporativo (AGENCIA TRIBUTARIA) -->
<schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:sfR="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/RespuestaSuministro.xsd" xmlns:sf="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd" xmlns:sfLR="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd" targetNamespace="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/RespuestaSuministro.xsd" elementFormDefault="qualified">
<import namespace="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd" schemaLocation="SuministroInformacion.xsd"/>
<import namespace="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd" schemaLocation="SuministroLR.xsd"/>
<element name="RespuestaRegFactuSistemaFacturacion" type="sfR:RespuestaRegFactuSistemaFacturacionType"/>
<complexType name="RespuestaBaseType">
<sequence>
<element name="CSV" type="string" minOccurs="0">
<annotation>
<documentation xml:lang="es"> CSV asociado al envío generado por AEAT. Solo se genera si no hay rechazo del envio</documentation>
</annotation>
</element>
<element name="DatosPresentacion" type="sf:DatosPresentacionType" minOccurs="0">
<annotation>
<documentation xml:lang="es"> Se devuelven datos de la presentacion realizada. Solo se genera si no hay rechazo del envio </documentation>
</annotation>
</element>
<element name="Cabecera" type="sf:CabeceraType">
<annotation>
<documentation xml:lang="es"> Se devuelve la cabecera que se incluyó en el envío. </documentation>
</annotation>
</element>
<element name="TiempoEsperaEnvio" type="sf:Tipo6Type"/>
<element name="EstadoEnvio" type="sfR:EstadoEnvioType">
<annotation>
<documentation xml:lang="es">
Estado del envío en conjunto.
Si los datos de cabecera y todos los registros son correctos,el estado es correcto.
En caso de estructura y cabecera correctos donde todos los registros son incorrectos, el estado es incorrecto
En caso de estructura y cabecera correctos con al menos un registro incorrecto, el estado global es parcialmente correcto.
</documentation>
</annotation>
</element>
</sequence>
</complexType>
<complexType name="RespuestaRegFactuSistemaFacturacionType">
<annotation>
<documentation xml:lang="es"> Respuesta a un envío de registro de facturacion</documentation>
</annotation>
<complexContent>
<extension base="sfR:RespuestaBaseType">
<sequence>
<element name="RespuestaLinea" type="sfR:RespuestaExpedidaType" minOccurs="0" maxOccurs="1000">
<annotation>
<documentation xml:lang="es">
Estado detallado de cada línea del suministro.
</documentation>
</annotation>
</element>
</sequence>
</extension>
</complexContent>
</complexType>
<complexType name="RespuestaExpedidaType">
<annotation>
<documentation xml:lang="es"> Respuesta a un envío </documentation>
</annotation>
<sequence>
<element name="IDFactura" type="sf:IDFacturaExpedidaType">
<annotation>
<documentation xml:lang="es"> ID Factura Expedida </documentation>
</annotation>
</element>
<element name="Operacion" type="sf:OperacionType"/>
<element name="RefExterna" type="sf:TextMax70Type" minOccurs="0"/>
<element name="EstadoRegistro" type="sfR:EstadoRegistroType">
<annotation>
<documentation xml:lang="es">
Estado del registro. Correcto o Incorrecto
</documentation>
</annotation>
</element>
<element name="CodigoErrorRegistro" type="sfR:ErrorDetalleType" minOccurs="0">
<annotation>
<documentation xml:lang="es">
Código del error de registro, en su caso.
</documentation>
</annotation>
</element>
<element name="DescripcionErrorRegistro" type="sf:TextMax1500Type" minOccurs="0">
<annotation>
<documentation xml:lang="es">
Descripción detallada del error de registro, en su caso.
</documentation>
</annotation>
</element>
<element name="RegistroDuplicado" type="sf:RegistroDuplicadoType" minOccurs="0">
<annotation>
<documentation xml:lang="es">
Solo en el caso de que se rechace el registro por duplicado se devuelve este nodo con la informacion registrada en el sistema para este registro
</documentation>
</annotation>
</element>
</sequence>
</complexType>
<simpleType name="EstadoEnvioType">
<restriction base="string">
<enumeration value="Correcto">
<annotation>
<documentation xml:lang="es">Correcto</documentation>
</annotation>
</enumeration>
<enumeration value="ParcialmenteCorrecto">
<annotation>
<documentation xml:lang="es">Parcialmente correcto. Ver detalle de errores</documentation>
</annotation>
</enumeration>
<enumeration value="Incorrecto">
<annotation>
<documentation xml:lang="es">Incorrecto</documentation>
</annotation>
</enumeration>
</restriction>
</simpleType>
<simpleType name="EstadoRegistroType">
<restriction base="string">
<enumeration value="Correcto">
<annotation>
<documentation xml:lang="es">Correcto</documentation>
</annotation>
</enumeration>
<enumeration value="AceptadoConErrores">
<annotation>
<documentation xml:lang="es">Aceptado con Errores. Ver detalle del error</documentation>
</annotation>
</enumeration>
<enumeration value="Incorrecto">
<annotation>
<documentation xml:lang="es">Incorrecto</documentation>
</annotation>
</enumeration>
</restriction>
</simpleType>
<simpleType name="ErrorDetalleType">
<restriction base="integer"/>
</simpleType>
</schema>

View File

@ -0,0 +1,110 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- editado con XMLSpy v2019 sp1 (x64) (http://www.altova.com) por AEAT (Agencia Estatal de Administracion Tributaria ((AEAT))) -->
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:sfLR="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd" xmlns:sf="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd" xmlns:sfR="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/RespuestaSuministro.xsd" xmlns:sfLRC="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/ConsultaLR.xsd" xmlns:sfLRRC="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/RespuestaConsultaLR.xsd" xmlns:sfWdsl="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SistemaFacturacion.wsdl" xmlns:ns="http://www.w3.org/2000/09/xmldsig#" targetNamespace="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SistemaFacturacion.wsdl">
<wsdl:types>
<xs:schema targetNamespace="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SistemaFacturacion.wsdl" elementFormDefault="qualified" xmlns:sfWdsl="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SistemaFacturacion.wsdl" xmlns:sf="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd" xmlns:sfLR="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd" xmlns:sfLRC="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/ConsultaLR.xsd" xmlns:sfLRRC="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/RespuestaConsultaLR.xsd">
<xs:import namespace="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd" schemaLocation="SuministroInformacion.xsd"/>
<xs:import namespace="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd" schemaLocation="SuministroLR.xsd"/>
<xs:import namespace="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/ConsultaLR.xsd" schemaLocation="ConsultaLR.xsd"/>
<xs:import namespace="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/RespuestaConsultaLR.xsd" schemaLocation="RespuestaConsultaLR.xsd"/>
<xs:import namespace="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/RespuestaSuministro.xsd" schemaLocation="RespuestaSuministro.xsd"/>
</xs:schema>
</wsdl:types>
<wsdl:message name="EntradaRegFactuSistemaFacturacion">
<wsdl:part name="RegFactuSistemaFacturacion" element="sfLR:RegFactuSistemaFacturacion"/>
</wsdl:message>
<wsdl:message name="EntradaConsultaFactuSistemaFacturacion">
<wsdl:part name="ConsultaFactuSistemaFacturacion" element="sfLRC:ConsultaFactuSistemaFacturacion"/>
</wsdl:message>
<wsdl:message name="RespuestaRegFactuSistemaFacturacion">
<wsdl:part name="RespuestaRegFactuSistemaFacturacion" element="sfR:RespuestaRegFactuSistemaFacturacion"/>
</wsdl:message>
<wsdl:message name="RespuestaConsultaFactuSistemaFacturacion">
<wsdl:part name="RespuestaConsultaFactuSistemaFacturacion" element="sfLRRC:RespuestaConsultaFactuSistemaFacturacion"/>
</wsdl:message>
<wsdl:portType name="sfPortTypeVerifactu">
<wsdl:operation name="RegFactuSistemaFacturacion">
<wsdl:input message="sfWdsl:EntradaRegFactuSistemaFacturacion"/>
<wsdl:output message="sfWdsl:RespuestaRegFactuSistemaFacturacion"/>
</wsdl:operation>
<wsdl:operation name="ConsultaFactuSistemaFacturacion">
<wsdl:input message="sfWdsl:EntradaConsultaFactuSistemaFacturacion"/>
<wsdl:output message="sfWdsl:RespuestaConsultaFactuSistemaFacturacion"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:portType name="sfPortTypePorRequerimiento">
<wsdl:operation name="RegFactuSistemaFacturacion">
<wsdl:input message="sfWdsl:EntradaRegFactuSistemaFacturacion"/>
<wsdl:output message="sfWdsl:RespuestaRegFactuSistemaFacturacion"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="sfVerifactu" type="sfWdsl:sfPortTypeVerifactu">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="RegFactuSistemaFacturacion">
<soap:operation soapAction=""/>
<wsdl:input>
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output>
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="ConsultaFactuSistemaFacturacion">
<soap:operation soapAction=""/>
<wsdl:input>
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output>
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:binding name="sfRequerimiento" type="sfWdsl:sfPortTypePorRequerimiento">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="RegFactuSistemaFacturacion">
<soap:operation soapAction=""/>
<wsdl:input>
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output>
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="sfVerifactu">
<!-- Sistemas que emiten facturas verificables. Entorno de PRODUCCION -->
<wsdl:port name="SistemaVerifactu" binding="sfWdsl:sfVerifactu">
<soap:address location="https://www1.agenciatributaria.gob.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP"/>
</wsdl:port>
<!-- Sistemas que emiten facturas verificables. Entorno de PRODUCCION para acceso con certificado de sello -->
<wsdl:port name="SistemaVerifactuSello" binding="sfWdsl:sfVerifactu">
<soap:address location="https://www10.agenciatributaria.gob.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP"/>
</wsdl:port>
<!-- Sistemas que emiten facturas verificables. Entorno de PRUEBAS -->
<wsdl:port name="SistemaVerifactuPruebas" binding="sfWdsl:sfVerifactu">
<soap:address location="https://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP"/>
</wsdl:port>
<!-- Sistemas que emiten facturas verificables. Entorno de PRUEBAS para acceso con certificado de sello -->
<wsdl:port name="SistemaVerifactuSelloPruebas" binding="sfWdsl:sfVerifactu">
<soap:address location="https://prewww10.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP"/>
</wsdl:port>
</wsdl:service>
<wsdl:service name="sfRequerimiento">
<!-- Sistemas que emiten facturas NO verificables. (Remision bajo requerimiento). Entorno de PRODUCCION -->
<wsdl:port name="SistemaRequerimiento" binding="sfWdsl:sfRequerimiento">
<soap:address location="https://www1.agenciatributaria.gob.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/RequerimientoSOAP"/>
</wsdl:port>
<!-- Sistemas que emiten facturas NO verificables. (Remision bajo requerimiento). Entorno de PRODUCCION para acceso con certificado de sello -->
<wsdl:port name="SistemaRequerimientoSello" binding="sfWdsl:sfRequerimiento">
<soap:address location="https://www10.agenciatributaria.gob.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/RequerimientoSOAP"/>
</wsdl:port>
<!-- Sistemas que emiten facturas NO verificables. (Remision bajo requerimiento). Entorno de PRUEBAS -->
<wsdl:port name="SistemaRequerimientoPruebas" binding="sfWdsl:sfRequerimiento">
<soap:address location="https://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/RequerimientoSOAP"/>
</wsdl:port>
<!-- Sistemas que emiten facturas NO verificables. (Remision bajo requerimiento). Entorno de PRUEBAS para acceso con certificado de sello -->
<wsdl:port name="SistemaRequerimientoSelloPruebas" binding="sfWdsl:sfRequerimiento">
<soap:address location="https://prewww10.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/RequerimientoSOAP"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- editado con XMLSpy v2019 sp1 (x64) (http://www.altova.com) por Puesto de Trabajo (Agencia Estatal de Administracion Tributaria ((AEAT))) -->
<!-- edited with XMLSpy v2009 sp1 (http://www.altova.com) by PC Corporativo (AGENCIA TRIBUTARIA) -->
<schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:sfLR="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd" xmlns:sf="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd" targetNamespace="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd" elementFormDefault="qualified">
<import namespace="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd" schemaLocation="SuministroInformacion.xsd"/>
<element name="RegFactuSistemaFacturacion">
<complexType>
<sequence>
<element name="Cabecera" type="sf:CabeceraType"/>
<element name="RegistroFactura" type="sfLR:RegistroFacturaType" maxOccurs="1000"/>
</sequence>
</complexType>
</element>
<complexType name="RegistroFacturaType">
<annotation>
<documentation xml:lang="es">Datos correspondientes a los registros de facturacion</documentation>
</annotation>
<sequence>
<choice>
<element ref="sf:RegistroAlta"/>
<element ref="sf:RegistroAnulacion"/>
</choice>
</sequence>
</complexType>
</schema>

View File

@ -0,0 +1,584 @@
<?php
namespace Tests\Feature\Verifactu;
use PHPUnit\Framework\TestCase;
use App\Services\EDocument\Standards\Verifactu\VerifactuClient;
use App\Services\EDocument\Standards\Verifactu\Types\Cabecera;
use App\Services\EDocument\Standards\Verifactu\Types\Desglose;
use App\Services\EDocument\Standards\Verifactu\Types\DesgloseRectificacion;
use App\Services\EDocument\Standards\Verifactu\Types\Destinatarios;
use App\Services\EDocument\Standards\Verifactu\Types\DetalleDesglose;
use App\Services\EDocument\Standards\Verifactu\Types\Encadenamiento;
use App\Services\EDocument\Standards\Verifactu\Types\IDDestinatario;
use App\Services\EDocument\Standards\Verifactu\Types\IDFactura;
use App\Services\EDocument\Standards\Verifactu\Types\IDFacturaAR;
use App\Services\EDocument\Standards\Verifactu\Types\IDOtro;
use App\Services\EDocument\Standards\Verifactu\Types\ObligadoEmision;
use App\Services\EDocument\Standards\Verifactu\Types\RegFactuSistemaFacturacion;
use App\Services\EDocument\Standards\Verifactu\Types\RegistroAlta;
use App\Services\EDocument\Standards\Verifactu\Types\RegistroFactura;
use App\Services\EDocument\Standards\Verifactu\Types\RegistroFacturacionAnulacion;
use App\Services\EDocument\Standards\Verifactu\Types\SistemaInformatico;
class InvoiceSchemaTestsAeat extends TestCase
{
private VerifactuClient $client;
private const TEST_COMPANY_NIF = 'B12345678';
private const TEST_COMPANY_NAME = 'TEST COMPANY SL';
private const TEST_CLIENT_NIF = 'B87654321';
private const TEST_CLIENT_NAME = 'TEST CLIENT SL';
protected function setUp(): void
{
parent::setUp();
$this->client = new VerifactuClient(VerifactuClient::MODE_TEST);
}
public function testF1StandardInvoice(): void
{
$registro = $this->createF1StandardInvoice();
$response = $this->client->sendRegistroAlta($registro);
$this->assertNotNull($response);
// Additional assertions could be added based on expected response structure
}
public function testF2SimplifiedInvoice(): void
{
$registro = $this->createF2SimplifiedInvoice();
$response = $this->client->sendRegistroAlta($registro);
$this->assertNotNull($response);
}
public function testF3SubstituteInvoice(): void
{
$registro = $this->createF3SubstituteInvoice();
$response = $this->client->sendRegistroAlta($registro);
$this->assertNotNull($response);
}
public function testR1CorrectiveInvoiceError(): void
{
$registro = $this->createR1CorrectiveInvoice();
$response = $this->client->sendRegistroAlta($registro);
$this->assertNotNull($response);
}
public function testR2CorrectiveInvoiceBankruptcy(): void
{
$registro = $this->createR2CorrectiveInvoice();
$response = $this->client->sendRegistroAlta($registro);
$this->assertNotNull($response);
}
public function testR3CorrectiveInvoiceBadDebt(): void
{
$registro = $this->createR3CorrectiveInvoice();
$response = $this->client->sendRegistroAlta($registro);
$this->assertNotNull($response);
}
public function testR4CorrectiveInvoiceGeneral(): void
{
$registro = $this->createR4CorrectiveInvoice();
$response = $this->client->sendRegistroAlta($registro);
$this->assertNotNull($response);
}
public function testR5CorrectiveInvoiceSimplified(): void
{
$registro = $this->createR5CorrectiveInvoice();
$response = $this->client->sendRegistroAlta($registro);
$this->assertNotNull($response);
}
private function createF1StandardInvoice(): RegistroAlta
{
$registro = new RegistroAlta();
$registro->setIDVersion('1.0');
// Set invoice ID
$idFactura = new IDFactura();
$idFactura->setIDEmisorFactura(self::TEST_COMPANY_NIF);
$idFactura->setNumSerieFactura('F1-TEST-001');
$idFactura->setFechaExpedicionFactura(date('d-m-Y'));
$registro->setIDFactura($idFactura);
$registro->setNombreRazonEmisor(self::TEST_COMPANY_NAME);
$registro->setTipoFactura('F1');
$registro->setDescripcionOperacion('F1 Standard Invoice - Complete invoice with full customer details');
// Set recipients with full identification
$destinatarios = new Destinatarios();
$destinatario = new IDDestinatario();
$destinatario->setNombreRazon(self::TEST_CLIENT_NAME);
$destinatario->setNIF(self::TEST_CLIENT_NIF);
$destinatarios->addIDDestinatario($destinatario);
$registro->setDestinatarios($destinatarios);
// Set breakdown - F1 must include detailed VAT breakdown
$desglose = new Desglose();
$detalle = new DetalleDesglose();
$detalle->setClaveRegimen('01'); // General regime
$detalle->setCalificacionOperacion('S1'); // Not exempt
$detalle->setTipoImpositivo(21.0);
$detalle->setBaseImponibleOimporteNoSujeto(100.0);
$detalle->setCuotaRepercutida(21.0);
$desglose->addDetalleDesglose($detalle);
$registro->setDesglose($desglose);
$registro->setCuotaTotal(21.0);
$registro->setImporteTotal(121.0);
$this->setSistemaInformatico($registro);
$this->setCommonFields($registro);
return $registro;
}
private function createF2SimplifiedInvoice(): RegistroAlta
{
$registro = new RegistroAlta();
$registro->setIDVersion('1.0');
$idFactura = new IDFactura();
$idFactura->setIDEmisorFactura(self::TEST_COMPANY_NIF);
$idFactura->setNumSerieFactura('F2-TEST-001');
$idFactura->setFechaExpedicionFactura(date('d-m-Y'));
$registro->setIDFactura($idFactura);
$registro->setNombreRazonEmisor(self::TEST_COMPANY_NAME);
$registro->setTipoFactura('F2');
$registro->setDescripcionOperacion('F2 Simplified Invoice - No customer identification required');
// F2 invoices may not identify the recipient or have minimal identification
$destinatarios = new Destinatarios();
$destinatario = new IDDestinatario();
$destinatario->setNombreRazon('CONSUMIDOR FINAL');
// For F2, we can use IDOtro instead of NIF for non-Spanish customers
$idOtro = new IDOtro();
$idOtro->setIDType('04'); // Legal Entity ID
$idOtro->setID('NO-IDENTIFICADO');
$destinatario->setIDOtro($idOtro);
$destinatarios->addIDDestinatario($destinatario);
$registro->setDestinatarios($destinatarios);
// Simplified breakdown - amount must be ≤ 3000€
$desglose = new Desglose();
$detalle = new DetalleDesglose();
$detalle->setClaveRegimen('01');
$detalle->setCalificacionOperacion('S1');
$detalle->setTipoImpositivo(21.0);
$detalle->setBaseImponibleOimporteNoSujeto(50.0); // Keep under 3000€ limit
$detalle->setCuotaRepercutida(10.5);
$desglose->addDetalleDesglose($detalle);
$registro->setDesglose($desglose);
$registro->setCuotaTotal(10.5);
$registro->setImporteTotal(60.5);
$this->setSistemaInformatico($registro);
$this->setCommonFields($registro);
return $registro;
}
private function createF3SubstituteInvoice(): RegistroAlta
{
$registro = new RegistroAlta();
$registro->setIDVersion('1.0');
$idFactura = new IDFactura();
$idFactura->setIDEmisorFactura(self::TEST_COMPANY_NIF);
$idFactura->setNumSerieFactura('F3-TEST-001');
$idFactura->setFechaExpedicionFactura(date('d-m-Y'));
$registro->setIDFactura($idFactura);
$registro->setNombreRazonEmisor(self::TEST_COMPANY_NAME);
$registro->setTipoFactura('F3');
$registro->setDescripcionOperacion('F3 Substitute Invoice - Replaces simplified invoices with full details');
// F3 requires full customer identification (similar to F1)
$destinatarios = new Destinatarios();
$destinatario = new IDDestinatario();
$destinatario->setNombreRazon(self::TEST_CLIENT_NAME);
$destinatario->setNIF(self::TEST_CLIENT_NIF);
$destinatarios->addIDDestinatario($destinatario);
$registro->setDestinatarios($destinatarios);
$desglose = new Desglose();
$detalle = new DetalleDesglose();
$detalle->setClaveRegimen('01');
$detalle->setCalificacionOperacion('S1');
$detalle->setTipoImpositivo(21.0);
$detalle->setBaseImponibleOimporteNoSujeto(200.0);
$detalle->setCuotaRepercutida(42.0);
$desglose->addDetalleDesglose($detalle);
$registro->setDesglose($desglose);
$registro->setCuotaTotal(42.0);
$registro->setImporteTotal(242.0);
$this->setSistemaInformatico($registro);
$this->setCommonFields($registro);
return $registro;
}
private function createR1CorrectiveInvoice(): RegistroAlta
{
$registro = new RegistroAlta();
$registro->setIDVersion('1.0');
$idFactura = new IDFactura();
$idFactura->setIDEmisorFactura(self::TEST_COMPANY_NIF);
$idFactura->setNumSerieFactura('R1-TEST-001');
$idFactura->setFechaExpedicionFactura(date('d-m-Y'));
$registro->setIDFactura($idFactura);
$registro->setNombreRazonEmisor(self::TEST_COMPANY_NAME);
$registro->setTipoFactura('R1');
$registro->setDescripcionOperacion('R1 Corrective Invoice - Error based on law and Art. 80 LIVA');
$destinatarios = new Destinatarios();
$destinatario = new IDDestinatario();
$destinatario->setNombreRazon(self::TEST_CLIENT_NAME);
$destinatario->setNIF(self::TEST_CLIENT_NIF);
$destinatarios->addIDDestinatario($destinatario);
$registro->setDestinatarios($destinatarios);
// R1 corrective invoice with rectified amounts
$desglose = new Desglose();
$detalle = new DetalleDesglose();
$detalle->setClaveRegimen('01');
$detalle->setCalificacionOperacion('S1');
$detalle->setTipoImpositivo(21.0);
$detalle->setBaseImponibleOimporteNoSujeto(-50.0); // Negative amount for correction
$detalle->setCuotaRepercutida(-10.5);
// Add rectification breakdown
$desgloseRectificacion = new DesgloseRectificacion();
$desgloseRectificacion->setBaseRectificada(100.0); // Original amount being corrected
$desgloseRectificacion->setCuotaRectificada(21.0);
$detalle->setDesgloseRectificacion($desgloseRectificacion);
$desglose->addDetalleDesglose($detalle);
$registro->setDesglose($desglose);
$registro->setCuotaTotal(-10.5);
$registro->setImporteTotal(-60.5);
$this->setSistemaInformatico($registro);
$this->setCommonFields($registro);
return $registro;
}
private function createR2CorrectiveInvoice(): RegistroAlta
{
$registro = new RegistroAlta();
$registro->setIDVersion('1.0');
$idFactura = new IDFactura();
$idFactura->setIDEmisorFactura(self::TEST_COMPANY_NIF);
$idFactura->setNumSerieFactura('R2-TEST-001');
$idFactura->setFechaExpedicionFactura(date('d-m-Y'));
$registro->setIDFactura($idFactura);
$registro->setNombreRazonEmisor(self::TEST_COMPANY_NAME);
$registro->setTipoFactura('R2');
$registro->setDescripcionOperacion('R2 Corrective Invoice - Customer bankruptcy (Art. 80.Three LIVA)');
// R2 requires Spanish NIF for customer identification
$destinatarios = new Destinatarios();
$destinatario = new IDDestinatario();
$destinatario->setNombreRazon(self::TEST_CLIENT_NAME . ' (EN CONCURSO)');
$destinatario->setNIF(self::TEST_CLIENT_NIF);
$destinatarios->addIDDestinatario($destinatario);
$registro->setDestinatarios($destinatarios);
$desglose = new Desglose();
$detalle = new DetalleDesglose();
$detalle->setClaveRegimen('01');
$detalle->setCalificacionOperacion('S1');
$detalle->setTipoImpositivo(21.0);
$detalle->setBaseImponibleOimporteNoSujeto(150.0); // Base remains, VAT reduced to 0
$detalle->setCuotaRepercutida(0.0); // VAT eliminated due to bankruptcy
$desgloseRectificacion = new DesgloseRectificacion();
$desgloseRectificacion->setBaseRectificada(150.0);
$desgloseRectificacion->setCuotaRectificada(31.5); // Original VAT being corrected
$detalle->setDesgloseRectificacion($desgloseRectificacion);
$desglose->addDetalleDesglose($detalle);
$registro->setDesglose($desglose);
$registro->setCuotaTotal(0.0);
$registro->setImporteTotal(150.0);
$this->setSistemaInformatico($registro);
$this->setCommonFields($registro);
return $registro;
}
private function createR3CorrectiveInvoice(): RegistroAlta
{
$registro = new RegistroAlta();
$registro->setIDVersion('1.0');
$idFactura = new IDFactura();
$idFactura->setIDEmisorFactura(self::TEST_COMPANY_NIF);
$idFactura->setNumSerieFactura('R3-TEST-001');
$idFactura->setFechaExpedicionFactura(date('d-m-Y'));
$registro->setIDFactura($idFactura);
$registro->setNombreRazonEmisor(self::TEST_COMPANY_NAME);
$registro->setTipoFactura('R3');
$registro->setDescripcionOperacion('R3 Corrective Invoice - Bad debt for VAT refund (Art. 80.Four LIVA)');
// R3 requires Spanish NIF for customer identification
$destinatarios = new Destinatarios();
$destinatario = new IDDestinatario();
$destinatario->setNombreRazon(self::TEST_CLIENT_NAME . ' (CREDITO INCOBRABLE)');
$destinatario->setNIF(self::TEST_CLIENT_NIF);
$destinatarios->addIDDestinatario($destinatario);
$registro->setDestinatarios($destinatarios);
$desglose = new Desglose();
$detalle = new DetalleDesglose();
$detalle->setClaveRegimen('01');
$detalle->setCalificacionOperacion('S1');
$detalle->setTipoImpositivo(21.0);
$detalle->setBaseImponibleOimporteNoSujeto(300.0); // Base remains
$detalle->setCuotaRepercutida(0.0); // VAT refund due to bad debt
$desgloseRectificacion = new DesgloseRectificacion();
$desgloseRectificacion->setBaseRectificada(300.0);
$desgloseRectificacion->setCuotaRectificada(63.0); // Original VAT being refunded
$detalle->setDesgloseRectificacion($desgloseRectificacion);
$desglose->addDetalleDesglose($detalle);
$registro->setDesglose($desglose);
$registro->setCuotaTotal(0.0);
$registro->setImporteTotal(300.0);
$this->setSistemaInformatico($registro);
$this->setCommonFields($registro);
return $registro;
}
private function createR4CorrectiveInvoice(): RegistroAlta
{
$registro = new RegistroAlta();
$registro->setIDVersion('1.0');
$idFactura = new IDFactura();
$idFactura->setIDEmisorFactura(self::TEST_COMPANY_NIF);
$idFactura->setNumSerieFactura('R4-TEST-001');
$idFactura->setFechaExpedicionFactura(date('d-m-Y'));
$registro->setIDFactura($idFactura);
$registro->setNombreRazonEmisor(self::TEST_COMPANY_NAME);
$registro->setTipoFactura('R4');
$registro->setDescripcionOperacion('R4 General Corrective Invoice - Other corrections not covered by R1-R3');
$destinatarios = new Destinatarios();
$destinatario = new IDDestinatario();
$destinatario->setNombreRazon(self::TEST_CLIENT_NAME);
$destinatario->setNIF(self::TEST_CLIENT_NIF);
$destinatarios->addIDDestinatario($destinatario);
$registro->setDestinatarios($destinatarios);
$desglose = new Desglose();
$detalle = new DetalleDesglose();
$detalle->setClaveRegimen('01');
$detalle->setCalificacionOperacion('S1');
$detalle->setTipoImpositivo(21.0);
$detalle->setBaseImponibleOimporteNoSujeto(25.0); // Adjustment amount
$detalle->setCuotaRepercutida(5.25);
$desgloseRectificacion = new DesgloseRectificacion();
$desgloseRectificacion->setBaseRectificada(200.0); // Original amount
$desgloseRectificacion->setCuotaRectificada(42.0);
$detalle->setDesgloseRectificacion($desgloseRectificacion);
$desglose->addDetalleDesglose($detalle);
$registro->setDesglose($desglose);
$registro->setCuotaTotal(5.25);
$registro->setImporteTotal(30.25);
$this->setSistemaInformatico($registro);
$this->setCommonFields($registro);
return $registro;
}
private function createR5CorrectiveInvoice(): RegistroAlta
{
$registro = new RegistroAlta();
$registro->setIDVersion('1.0');
$idFactura = new IDFactura();
$idFactura->setIDEmisorFactura(self::TEST_COMPANY_NIF);
$idFactura->setNumSerieFactura('R5-TEST-001');
$idFactura->setFechaExpedicionFactura(date('d-m-Y'));
$registro->setIDFactura($idFactura);
$registro->setNombreRazonEmisor(self::TEST_COMPANY_NAME);
$registro->setTipoFactura('R5');
$registro->setDescripcionOperacion('R5 Corrective Invoice - Correction of simplified invoices (F2)');
// R5 corrects F2 invoices, so minimal customer identification like F2
$destinatarios = new Destinatarios();
$destinatario = new IDDestinatario();
$destinatario->setNombreRazon('CONSUMIDOR FINAL (CORRECCION)');
$idOtro = new IDOtro();
$idOtro->setIDType('04');
$idOtro->setID('NO-IDENTIFICADO-CORR');
$destinatario->setIDOtro($idOtro);
$destinatarios->addIDDestinatario($destinatario);
$registro->setDestinatarios($destinatarios);
$desglose = new Desglose();
$detalle = new DetalleDesglose();
$detalle->setClaveRegimen('01');
$detalle->setCalificacionOperacion('S1');
$detalle->setTipoImpositivo(21.0);
$detalle->setBaseImponibleOimporteNoSujeto(-10.0); // Correction to F2 invoice
$detalle->setCuotaRepercutida(-2.1);
$desgloseRectificacion = new DesgloseRectificacion();
$desgloseRectificacion->setBaseRectificada(50.0); // Original F2 amount
$desgloseRectificacion->setCuotaRectificada(10.5);
$detalle->setDesgloseRectificacion($desgloseRectificacion);
$desglose->addDetalleDesglose($detalle);
$registro->setDesglose($desglose);
$registro->setCuotaTotal(-2.1);
$registro->setImporteTotal(-12.1);
$this->setSistemaInformatico($registro);
$this->setCommonFields($registro);
return $registro;
}
private function setSistemaInformatico(RegistroAlta $registro): void
{
$sistemaInformatico = new SistemaInformatico();
$sistemaInformatico->setNombreRazon('INVOICE NINJA TEST SYSTEM');
$sistemaInformatico->setNIF(self::TEST_COMPANY_NIF);
$sistemaInformatico->setNombreSistemaInformatico('InvoiceNinja Verifactu');
$sistemaInformatico->setIdSistemaInformatico('INV-NINJA-001');
$sistemaInformatico->setVersion('1.0.0');
$sistemaInformatico->setNumeroInstalacion('001');
$sistemaInformatico->setTipoUsoPosibleSoloVerifactu('S');
$sistemaInformatico->setTipoUsoPosibleMultiOT('N');
$sistemaInformatico->setIndicadorMultiplesOT('N');
$registro->setSistemaInformatico($sistemaInformatico);
}
private function setCommonFields(RegistroAlta $registro): void
{
$registro->setFechaHoraHusoGenRegistro(date('Y-m-d\TH:i:sP'));
$registro->setTipoHuella('01');
$registro->setHuella('TEST-HASH-' . uniqid());
// Add encadenamiento (chaining) for blockchain
$encadenamiento = new Encadenamiento();
$registroAnterior = new IDFacturaAR();
$registroAnterior->setIDEmisorFactura(self::TEST_COMPANY_NIF);
$registroAnterior->setNumSerieFactura('PREV-001');
$registroAnterior->setFechaExpedicionFactura(date('d-m-Y', strtotime('-1 day')));
$registroAnterior->setHuella('PREVIOUS-HASH-' . uniqid());
$encadenamiento->setRegistroAnterior($registroAnterior);
$registro->setEncadenamiento($encadenamiento);
}
public function testAllInvoiceTypesSerializeSuccessfully(): void
{
$invoiceTypes = [
'F1' => 'createF1StandardInvoice',
'F2' => 'createF2SimplifiedInvoice',
'F3' => 'createF3SubstituteInvoice',
'R1' => 'createR1CorrectiveInvoice',
'R2' => 'createR2CorrectiveInvoice',
'R3' => 'createR3CorrectiveInvoice',
'R4' => 'createR4CorrectiveInvoice',
'R5' => 'createR5CorrectiveInvoice'
];
foreach ($invoiceTypes as $type => $method) {
$registro = $this->$method();
// Verify the invoice type is set correctly
$this->assertEquals($type, $registro->getTipoFactura());
// Verify the registro can be serialized without errors
try {
$response = $this->client->sendRegistroAlta($registro);
$this->assertNotNull($response, "Failed to serialize invoice type: $type");
} catch (\Exception $e) {
$this->fail("Exception occurred while serializing invoice type $type: " . $e->getMessage());
}
}
}
public function testInvoiceTypeValidation(): void
{
$validTypes = ['F1', 'F2', 'F3', 'R1', 'R2', 'R3', 'R4', 'R5'];
foreach ($validTypes as $type) {
$registro = new RegistroAlta();
$registro->setTipoFactura($type);
$this->assertEquals($type, $registro->getTipoFactura());
}
}
public function testCorrectiveInvoicesIncludeRectificationData(): void
{
$correctiveTypes = ['R1', 'R2', 'R3', 'R4', 'R5'];
foreach ($correctiveTypes as $type) {
$method = 'create' . $type . 'CorrectiveInvoice';
$registro = $this->$method();
$desglose = $registro->getDesglose();
$detalles = $desglose->getDetalleDesglose();
$this->assertNotEmpty($detalles, "Corrective invoice $type should have breakdown details");
foreach ($detalles as $detalle) {
if ($detalle->getDesgloseRectificacion()) {
$rectificacion = $detalle->getDesgloseRectificacion();
$this->assertNotNull($rectificacion->getBaseRectificada(),
"Corrective invoice $type should have rectified base");
$this->assertNotNull($rectificacion->getCuotaRectificada(),
"Corrective invoice $type should have rectified quota");
}
}
}
}
}

View File

@ -0,0 +1,85 @@
<?php
namespace Tests\Feature\Verifactu;
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 App\Services\EDocument\Standards\Verifactu;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
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 Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter;
use App\Services\EDocument\Standards\Verifactu\Types\RegFactuSistemaFacturacion;
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()
{
$document = file_get_contents(__DIR__ . '/invoice.xml');
$verifactu = new Verifactu($this->invoice);
$serializer = $verifactu->getSerializer();
$parent_class = SoapEnvelope::class;
$invoice = $serializer->deserialize($document, $parent_class, 'xml', [\Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer::SKIP_NULL_VALUES => true]);
$this->assertInstanceOf(SoapEnvelope::class, $invoice);
}
public function testSerializeXml()
{
$document = file_get_contents(__DIR__ . '/invoice.xml');
$verifactu = new Verifactu($this->invoice);
$serializer = $verifactu->getSerializer();
$parent_class = SoapEnvelope::class;
$invoice = $serializer->deserialize($document, $parent_class, 'xml', [\Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer::SKIP_NULL_VALUES => true]);
$xml = $verifactu->serializeXml($invoice);
// nlog($xml);
$this->assertStringContainsString('soapenv:Envelope', $xml);
}
}

View File

@ -0,0 +1,269 @@
<?php
namespace Tests\Feature\Verifactu;
use App\Services\EDocument\Standards\Verifactu\Types\Detalle;
use App\Services\EDocument\Standards\Verifactu\Types\DetalleDesglose;
use App\Services\EDocument\Standards\Verifactu\Types\Desglose;
use App\Services\EDocument\Standards\Verifactu\Types\DesgloseRectificacion;
use App\Services\EDocument\Standards\Verifactu\Types\Destinatarios;
use App\Services\EDocument\Standards\Verifactu\Types\Encadenamiento;
use App\Services\EDocument\Standards\Verifactu\Types\IDDestinatario;
use App\Services\EDocument\Standards\Verifactu\Types\IDFactura;
use App\Services\EDocument\Standards\Verifactu\Types\IDFacturaAR;
use App\Services\EDocument\Standards\Verifactu\Types\IDFacturaExpedida;
use App\Services\EDocument\Standards\Verifactu\Types\Incidencia;
use App\Services\EDocument\Standards\Verifactu\Types\ObligadoEmision;
use App\Services\EDocument\Standards\Verifactu\Types\OperacionExenta;
use App\Services\EDocument\Standards\Verifactu\Types\PersonaFisicaJuridica;
use App\Services\EDocument\Standards\Verifactu\Types\PersonaFisicaJuridicaES;
use App\Services\EDocument\Standards\Verifactu\Types\RechazoPrevio;
use App\Services\EDocument\Standards\Verifactu\Types\RegistroAlta;
use App\Services\EDocument\Standards\Verifactu\Types\RegistroFactura;
use App\Services\EDocument\Standards\Verifactu\Types\RegistroFacturacionAnulacion;
use App\Services\EDocument\Standards\Verifactu\Types\SistemaInformatico;
use App\Services\EDocument\Standards\Verifactu\Types\Subsanacion;
use PHPUnit\Framework\TestCase;
class VerifactuTest extends TestCase
{
public function testSubsanacionValidation()
{
$subsanacion = new Subsanacion();
$subsanacion->setNumRegistroAcuerdoFacturacion('12345')
->setFechaRegistroAcuerdoFacturacion('2024-03-20');
$this->assertEquals('12345', $subsanacion->getNumRegistroAcuerdoFacturacion());
$this->assertEquals('2024-03-20', $subsanacion->getFechaRegistroAcuerdoFacturacion());
$this->expectException(\InvalidArgumentException::class);
$subsanacion->setNumRegistroAcuerdoFacturacion(str_repeat('1', 16)); // Exceeds 15 chars
}
public function testRechazoPrevioValidation()
{
$rechazoPrevio = new RechazoPrevio();
$rechazoPrevio->setNumRegistroAcuerdoFacturacion('12345')
->setFechaRegistroAcuerdoFacturacion('2024-03-20')
->setMotivoRechazo('Motivo de prueba');
$this->assertEquals('12345', $rechazoPrevio->getNumRegistroAcuerdoFacturacion());
$this->assertEquals('2024-03-20', $rechazoPrevio->getFechaRegistroAcuerdoFacturacion());
$this->assertEquals('Motivo de prueba', $rechazoPrevio->getMotivoRechazo());
$this->expectException(\InvalidArgumentException::class);
$rechazoPrevio->setMotivoRechazo(str_repeat('a', 2001)); // Exceeds 2000 chars
}
public function testDetalleValidation()
{
$detalle = new Detalle();
$detalle->setImpuesto('IVA')
->setClaveRegimen('01')
->setBaseImponibleOimporteNoSujeto(1000.50)
->setTipoImpositivo(21.00);
$this->assertEquals('IVA', $detalle->getImpuesto());
$this->assertEquals('01', $detalle->getClaveRegimen());
$this->assertEquals(1000.50, $detalle->getBaseImponibleOimporteNoSujeto());
$this->assertEquals(21.00, $detalle->getTipoImpositivo());
$this->expectException(\InvalidArgumentException::class);
$detalle->setTipoImpositivo(101.00); // Exceeds 100%
}
public function testOperacionExentaValidation()
{
$operacionExenta = new OperacionExenta(OperacionExenta::E1);
$this->assertEquals(OperacionExenta::E1, $operacionExenta->getValue());
$this->expectException(\InvalidArgumentException::class);
new OperacionExenta('INVALID_VALUE');
}
public function testIDFacturaARValidation()
{
$idFactura = new IDFacturaAR();
$idFactura->setIDEmisorFactura('B12345678')
->setNumSerieFactura('FACT2024-001')
->setFechaExpedicionFactura('20-03-2024');
$this->assertEquals('B12345678', $idFactura->getIDEmisorFactura());
$this->assertEquals('FACT2024-001', $idFactura->getNumSerieFactura());
$this->assertEquals('20-03-2024', $idFactura->getFechaExpedicionFactura());
$this->expectException(\InvalidArgumentException::class);
$idFactura->setFechaExpedicionFactura('2024-03-20'); // Wrong format
}
public function testRegistroFacturaValidation()
{
$registroFactura = new RegistroFactura();
$registroAlta = new RegistroAlta();
$registroFactura->setRegistroAlta($registroAlta);
$this->assertInstanceOf(RegistroAlta::class, $registroFactura->getRegistroAlta());
$registroAnulacion = new RegistroFacturacionAnulacion();
$this->expectException(\InvalidArgumentException::class);
$registroFactura->setRegistroAnulacion($registroAnulacion); // Cannot set both
}
public function testObligadoEmisionValidation()
{
$obligado = new ObligadoEmision();
$obligado->setNIF('B12345678');
$obligado->setNombreRazon('Empresa Test');
$this->assertEquals('B12345678', $obligado->getNIF());
$this->assertEquals('Empresa Test', $obligado->getNombreRazon());
}
public function testObligadoEmisionEmptyNombreRazon()
{
$obligado = new ObligadoEmision();
$this->expectException(\InvalidArgumentException::class);
$obligado->setNombreRazon('');
}
public function testObligadoEmisionEmptyNIF()
{
$obligado = new ObligadoEmision();
$this->expectException(\InvalidArgumentException::class);
$obligado->setNIF('');
}
public function testObligadoEmisionInvalidNIF()
{
$obligado = new ObligadoEmision();
$this->expectException(\InvalidArgumentException::class);
$obligado->setNIF('invalid');
}
public function testCreateCompleteRegistroFactura()
{
// Create ObligadoEmision
$obligadoEmision = new ObligadoEmision();
$obligadoEmision->setNombreRazon('XXXXX')
->setNIF('A12345678');
// Create IDFactura
$idFactura = new IDFactura();
$idFactura->setIDEmisorFactura('B12345678')
->setNumSerieFactura('12345')
->setFechaExpedicionFactura('13-09-2024');
// Create Destinatario
$destinatario = new IDDestinatario();
$destinatario->setNombreRazon('YYYY')
->setNIF('C12345678');
// Create Destinatarios collection
$destinatarios = new Destinatarios();
$destinatarios->addIDDestinatario($destinatario);
// Create Detalles for Desglose
$detalle1 = new DetalleDesglose();
$detalle1->setClaveRegimen('01')
->setCalificacionOperacion('S1')
->setTipoImpositivo(4.0)
->setBaseImponibleOimporteNoSujeto(10.0)
->setCuotaRepercutida(0.4);
$detalle2 = new DetalleDesglose();
$detalle2->setClaveRegimen('01')
->setCalificacionOperacion('S1')
->setTipoImpositivo(21.0)
->setBaseImponibleOimporteNoSujeto(100.0)
->setCuotaRepercutida(21.0);
// Create Desglose
$desglose = new Desglose();
$desglose->addDetalleDesglose($detalle1);
$desglose->addDetalleDesglose($detalle2);
// Create RegistroAnterior for Encadenamiento
$registroAnterior = new IDFacturaAR();
$registroAnterior->setIDEmisorFactura('E12345678')
->setNumSerieFactura('44')
->setFechaExpedicionFactura('13-09-2024');
// Create Encadenamiento
$encadenamiento = new Encadenamiento();
$encadenamiento->setRegistroAnterior($registroAnterior)
->setHuellaRegistroAnterior('HuellaRegistroAnterior');
// Create SistemaInformatico
$sistemaInformatico = new SistemaInformatico();
$sistemaInformatico->setNombreRazon('SSSS')
->setNIF('D12345678')
->setNombreSistemaInformatico('NombreSistemaInformatico')
->setIdSistemaInformatico('77')
->setVersion('1.0.03')
->setNumeroInstalacion('383')
->setTipoUsoPosibleSoloVerifactu('N')
->setTipoUsoPosibleMultiOT('S')
->setIndicadorMultiplesOT('S');
// Create RegistroAlta
$registroAlta = new RegistroAlta();
$registroAlta->setIDVersion('1.0')
->setIDFactura($idFactura)
->setNombreRazonEmisor('XXXXX')
->setTipoFactura('F1')
->setDescripcionOperacion('Descripc')
->setDestinatarios($destinatarios)
->setDesglose($desglose)
->setCuotaTotal(21.4)
->setImporteTotal(131.4)
->setEncadenamiento($encadenamiento)
->setSistemaInformatico($sistemaInformatico)
->setFechaHoraHusoGenRegistro('2024-09-13T19:20:30+01:00')
->setTipoHuella('01')
->setHuella('Huella');
// Create RegistroFactura
$registroFactura = new RegistroFactura();
$registroFactura->setRegistroAlta($registroAlta);
// Assertions
$this->assertEquals('1.0', $registroAlta->getIDVersion());
$this->assertEquals('XXXXX', $registroAlta->getNombreRazonEmisor());
$this->assertEquals('F1', $registroAlta->getTipoFactura());
$this->assertEquals('Descripc', $registroAlta->getDescripcionOperacion());
$this->assertEquals(21.4, $registroAlta->getCuotaTotal());
$this->assertEquals(131.4, $registroAlta->getImporteTotal());
$this->assertEquals('01', $registroAlta->getTipoHuella());
$this->assertEquals('Huella', $registroAlta->getHuella());
// Test nested objects
$this->assertEquals('B12345678', $registroAlta->getIDFactura()->getIDEmisorFactura());
$this->assertEquals('12345', $registroAlta->getIDFactura()->getNumSerieFactura());
$this->assertEquals('13-09-2024', $registroAlta->getIDFactura()->getFechaExpedicionFactura());
// Test Destinatarios
$destinatarios = $registroAlta->getDestinatarios();
$this->assertEquals('YYYY', $destinatarios->getIDDestinatario()[0]->getNombreRazon());
$this->assertEquals('C12345678', $destinatarios->getIDDestinatario()[0]->getNIF());
// Test Desglose
$detalles = $registroAlta->getDesglose()->getDetalleDesglose();
$this->assertEquals(4.0, $detalles[0]->getTipoImpositivo());
$this->assertEquals(10.0, $detalles[0]->getBaseImponibleOimporteNoSujeto());
$this->assertEquals(0.4, $detalles[0]->getCuotaRepercutida());
$this->assertEquals(21.0, $detalles[1]->getTipoImpositivo());
$this->assertEquals(100.0, $detalles[1]->getBaseImponibleOimporteNoSujeto());
$this->assertEquals(21.0, $detalles[1]->getCuotaRepercutida());
// Test SistemaInformatico
$this->assertEquals('SSSS', $registroAlta->getSistemaInformatico()->getNombreRazon());
$this->assertEquals('D12345678', $registroAlta->getSistemaInformatico()->getNIF());
$this->assertEquals('77', $registroAlta->getSistemaInformatico()->getIdSistemaInformatico());
}
}

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:sum="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd"
xmlns:sum1="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd"
xmlns:xd="http://www.w3.org/2000/09/xmldsig#">
<soapenv:Header />
<soapenv:Body>
<sum:RegFactuSistemaFacturacion>
<sum:Cabecera>
<sum1:ObligadoEmision>
<sum1:NombreRazon>B12345678</sum1:NombreRazon>
<sum1:NIF>B12345678</sum1:NIF>
</sum1:ObligadoEmision>
</sum:Cabecera>
<sum:RegistroFactura>
<sum1:RegistroAlta>
<sum1:IDVersion>1.0</sum1:IDVersion>
<sum1:IDFactura>
<sum1:IDEmisorFactura>C12345678</sum1:IDEmisorFactura>
<sum1:NumSerieFactura>12345</sum1:NumSerieFactura>
<sum1:FechaExpedicionFactura>13-09-2024</sum1:FechaExpedicionFactura>
</sum1:IDFactura>
<sum1:NombreRazonEmisor>XXXXX</sum1:NombreRazonEmisor>
<sum1:TipoFactura>F1</sum1:TipoFactura>
<sum1:DescripcionOperacion>Descripc</sum1:DescripcionOperacion>
<sum1:Destinatarios>
<sum1:IDDestinatario>
<sum1:NombreRazon>YYYY</sum1:NombreRazon>
<sum1:NIF>D12345678</sum1:NIF>
</sum1:IDDestinatario>
</sum1:Destinatarios>
<sum1:Desglose>
<sum1:DetalleDesglose>
<sum1:ClaveRegimen>01</sum1:ClaveRegimen>
<sum1:CalificacionOperacion>S1</sum1:CalificacionOperacion>
<sum1:TipoImpositivo>4</sum1:TipoImpositivo>
<sum1:BaseImponibleOimporteNoSujeto>10</sum1:BaseImponibleOimporteNoSujeto>
<sum1:CuotaRepercutida>0.4</sum1:CuotaRepercutida>
</sum1:DetalleDesglose>
<sum1:DetalleDesglose>
<sum1:ClaveRegimen>01</sum1:ClaveRegimen>
<sum1:CalificacionOperacion>S1</sum1:CalificacionOperacion>
<sum1:TipoImpositivo>21</sum1:TipoImpositivo>
<sum1:BaseImponibleOimporteNoSujeto>100</sum1:BaseImponibleOimporteNoSujeto>
<sum1:CuotaRepercutida>21</sum1:CuotaRepercutida>
</sum1:DetalleDesglose>
</sum1:Desglose>
<sum1:CuotaTotal>21.4</sum1:CuotaTotal>
<sum1:ImporteTotal>131.4</sum1:ImporteTotal>
<sum1:Encadenamiento>
<sum1:RegistroAnterior>
<sum1:IDEmisorFactura>D12345678</sum1:IDEmisorFactura>
<sum1:NumSerieFactura>44</sum1:NumSerieFactura>
<sum1:FechaExpedicionFactura>13-09-2024</sum1:FechaExpedicionFactura>
<sum1:Huella>HuellaRegistroAnterior</sum1:Huella>
</sum1:RegistroAnterior>
</sum1:Encadenamiento>
<sum1:SistemaInformatico>
<sum1:NombreRazon>SSSS</sum1:NombreRazon>
<sum1:NIF>E12345678</sum1:NIF>
<sum1:NombreSistemaInformatico>NombreSistemaInformatico</sum1:NombreSistemaInformatico>
<sum1:IdSistemaInformatico>77</sum1:IdSistemaInformatico>
<sum1:Version>1.0.03</sum1:Version>
<sum1:NumeroInstalacion>383</sum1:NumeroInstalacion>
<sum1:TipoUsoPosibleSoloVerifactu>N</sum1:TipoUsoPosibleSoloVerifactu>
<sum1:TipoUsoPosibleMultiOT>S</sum1:TipoUsoPosibleMultiOT>
<sum1:IndicadorMultiplesOT>S</sum1:IndicadorMultiplesOT>
</sum1:SistemaInformatico>
<sum1:FechaHoraHusoGenRegistro>2024-09-13T19:20:30+01:00</sum1:FechaHoraHusoGenRegistro>
<sum1:TipoHuella>01</sum1:TipoHuella>
<sum1:Huella>Huella</sum1:Huella>
</sum1:RegistroAlta>
</sum:RegistroFactura>
</sum:RegFactuSistemaFacturacion>
</soapenv:Body>
</soapenv:Envelope>