Align tests with new workflow
This commit is contained in:
parent
7e1a6bc1c7
commit
c3b5a972a1
|
|
@ -1,128 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Services\EDocument\Standards\Verifactu\Models;
|
|
||||||
|
|
||||||
class FacturaRectificativa extends BaseXmlModel
|
|
||||||
{
|
|
||||||
private string $tipoRectificativa;
|
|
||||||
private float $baseRectificada;
|
|
||||||
private float $cuotaRectificada;
|
|
||||||
private ?float $cuotaRecargoRectificado;
|
|
||||||
private array $facturasRectificadas;
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
string $tipoRectificativa,
|
|
||||||
float $baseRectificada,
|
|
||||||
float $cuotaRectificada,
|
|
||||||
?float $cuotaRecargoRectificado = null
|
|
||||||
) {
|
|
||||||
$this->tipoRectificativa = $tipoRectificativa;
|
|
||||||
$this->baseRectificada = $baseRectificada;
|
|
||||||
$this->cuotaRectificada = $cuotaRectificada;
|
|
||||||
$this->cuotaRecargoRectificado = $cuotaRecargoRectificado;
|
|
||||||
$this->facturasRectificadas = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTipoRectificativa(): string
|
|
||||||
{
|
|
||||||
return $this->tipoRectificativa;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getBaseRectificada(): float
|
|
||||||
{
|
|
||||||
return $this->baseRectificada;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCuotaRectificada(): float
|
|
||||||
{
|
|
||||||
return $this->cuotaRectificada;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCuotaRecargoRectificado(): ?float
|
|
||||||
{
|
|
||||||
return $this->cuotaRecargoRectificado;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function addFacturaRectificada(string $nif, string $numSerie, string $fecha): void
|
|
||||||
{
|
|
||||||
$this->facturasRectificadas[] = [
|
|
||||||
'nif' => $nif,
|
|
||||||
'numSerie' => $numSerie,
|
|
||||||
'fecha' => $fecha
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFacturasRectificadas(): array
|
|
||||||
{
|
|
||||||
return $this->facturasRectificadas;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up a rectified invoice with the required information
|
|
||||||
*
|
|
||||||
* @param string $nif The NIF of the rectified invoice
|
|
||||||
* @param string $numSerie The series number of the rectified invoice
|
|
||||||
* @param string $fecha The date of the rectified invoice
|
|
||||||
* @return self
|
|
||||||
*/
|
|
||||||
public function setRectifiedInvoice(string $nif, string $numSerie, string $fecha): self
|
|
||||||
{
|
|
||||||
$this->facturasRectificadas = [];
|
|
||||||
$this->addFacturaRectificada($nif, $numSerie, $fecha);
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up a rectified invoice with the required information using an IDFactura object
|
|
||||||
*
|
|
||||||
* @param IDFactura $idFactura The IDFactura object of the rectified invoice
|
|
||||||
* @return self
|
|
||||||
*/
|
|
||||||
public function setRectifiedInvoiceFromIDFactura(IDFactura $idFactura): self
|
|
||||||
{
|
|
||||||
$this->facturasRectificadas = [];
|
|
||||||
$this->addFacturaRectificada(
|
|
||||||
$idFactura->getIdEmisorFactura(),
|
|
||||||
$idFactura->getNumSerieFactura(),
|
|
||||||
$idFactura->getFechaExpedicionFactura()
|
|
||||||
);
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function toXml(\DOMDocument $doc): \DOMElement
|
|
||||||
{
|
|
||||||
$idFacturaRectificada = $this->createElement($doc, 'IDFacturaRectificada');
|
|
||||||
|
|
||||||
// Add required elements in order with proper namespace
|
|
||||||
$idEmisorFactura = $this->createElement($doc, 'IDEmisorFactura', $this->facturasRectificadas[0]['nif']);
|
|
||||||
$idFacturaRectificada->appendChild($idEmisorFactura);
|
|
||||||
|
|
||||||
$numSerieFactura = $this->createElement($doc, 'NumSerieFactura', $this->facturasRectificadas[0]['numSerie']);
|
|
||||||
$idFacturaRectificada->appendChild($numSerieFactura);
|
|
||||||
|
|
||||||
$fechaExpedicionFactura = $this->createElement($doc, 'FechaExpedicionFactura', $this->facturasRectificadas[0]['fecha']);
|
|
||||||
$idFacturaRectificada->appendChild($fechaExpedicionFactura);
|
|
||||||
|
|
||||||
// Add required fields for R1 invoices according to Verifactu standard
|
|
||||||
$baseRectificada = $this->createElement($doc, 'BaseRectificada', number_format($this->baseRectificada, 2, '.', ''));
|
|
||||||
$idFacturaRectificada->appendChild($baseRectificada);
|
|
||||||
|
|
||||||
$cuotaRectificada = $this->createElement($doc, 'CuotaRectificada', number_format($this->cuotaRectificada, 2, '.', ''));
|
|
||||||
$idFacturaRectificada->appendChild($cuotaRectificada);
|
|
||||||
|
|
||||||
// Add optional CuotaRecargoRectificado if set
|
|
||||||
if ($this->cuotaRecargoRectificado !== null) {
|
|
||||||
$cuotaRecargoRectificado = $this->createElement($doc, 'CuotaRecargoRectificado', number_format($this->cuotaRecargoRectificado, 2, '.', ''));
|
|
||||||
$idFacturaRectificada->appendChild($cuotaRecargoRectificado);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $idFacturaRectificada;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function fromDOMElement(\DOMElement $element): self
|
|
||||||
{
|
|
||||||
// This method is required by BaseXmlModel but not used in this context
|
|
||||||
// Return a default instance
|
|
||||||
return new self('S', 0.0, 0.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -59,7 +59,6 @@ class Invoice extends BaseXmlModel implements XmlModelInterface
|
||||||
protected string $tipoHuella;
|
protected string $tipoHuella;
|
||||||
protected string $huella;
|
protected string $huella;
|
||||||
protected ?string $signature = null;
|
protected ?string $signature = null;
|
||||||
protected ?FacturaRectificativa $facturaRectificativa = null;
|
|
||||||
protected ?string $privateKeyPath = null;
|
protected ?string $privateKeyPath = null;
|
||||||
protected ?string $publicKeyPath = null;
|
protected ?string $publicKeyPath = null;
|
||||||
protected ?string $certificatePath = null;
|
protected ?string $certificatePath = null;
|
||||||
|
|
@ -500,16 +499,6 @@ class Invoice extends BaseXmlModel implements XmlModelInterface
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFacturaRectificativa(): ?FacturaRectificativa
|
|
||||||
{
|
|
||||||
return $this->facturaRectificativa;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setFacturaRectificativa(FacturaRectificativa $facturaRectificativa): void
|
|
||||||
{
|
|
||||||
$this->facturaRectificativa = $facturaRectificativa;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper method to create a rectificative invoice with proper configuration
|
* Helper method to create a rectificative invoice with proper configuration
|
||||||
*
|
*
|
||||||
|
|
@ -578,7 +567,6 @@ class Invoice extends BaseXmlModel implements XmlModelInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate that the invoice is properly configured for its type
|
* Validate that the invoice is properly configured for its type
|
||||||
*
|
*
|
||||||
|
|
@ -589,29 +577,29 @@ class Invoice extends BaseXmlModel implements XmlModelInterface
|
||||||
{
|
{
|
||||||
// Basic validation for all invoice types
|
// Basic validation for all invoice types
|
||||||
if (empty($this->idVersion)) {
|
if (empty($this->idVersion)) {
|
||||||
throw new \InvalidArgumentException('IDVersion is required');
|
throw new \InvalidArgumentException('Missing required field: IDVersion');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty($this->nombreRazonEmisor)) {
|
if (empty($this->nombreRazonEmisor)) {
|
||||||
throw new \InvalidArgumentException('NombreRazonEmisor is required');
|
throw new \InvalidArgumentException('Missing required field: NombreRazonEmisor');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty($this->descripcionOperacion)) {
|
if (empty($this->descripcionOperacion)) {
|
||||||
throw new \InvalidArgumentException('DescripcionOperacion is required');
|
throw new \InvalidArgumentException('Missing required field: DescripcionOperacion');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->tipoFactura !== self::TIPO_FACTURA_RECTIFICATIVA && $this->cuotaTotal < 0) {
|
if ($this->tipoFactura !== self::TIPO_FACTURA_RECTIFICATIVA && $this->cuotaTotal < 0) {
|
||||||
throw new \InvalidArgumentException('CuotaTotal must be a positive number');
|
throw new \InvalidArgumentException('Missing required field: CuotaTotal');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->tipoFactura !== self::TIPO_FACTURA_RECTIFICATIVA && $this->importeTotal < 0) {
|
if ($this->tipoFactura !== self::TIPO_FACTURA_RECTIFICATIVA && $this->importeTotal < 0) {
|
||||||
throw new \InvalidArgumentException('ImporteTotal must be a positive number');
|
throw new \InvalidArgumentException('Missing required field: ImporteTotal');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specific validation for R1 invoices
|
// Specific validation for R1 invoices
|
||||||
if ($this->tipoFactura === self::TIPO_FACTURA_RECTIFICATIVA) {
|
if ($this->tipoFactura === self::TIPO_FACTURA_RECTIFICATIVA) {
|
||||||
if ($this->tipoRectificativa === null) {
|
if ($this->tipoRectificativa === null) {
|
||||||
throw new \InvalidArgumentException('TipoRectificativa is required for R1 invoices');
|
throw new \InvalidArgumentException('Missing required field: TipoRectificativa');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!in_array($this->tipoRectificativa, [self::TIPO_RECTIFICATIVA_COMPLETA, self::TIPO_RECTIFICATIVA_SUSTITUTIVA])) {
|
if (!in_array($this->tipoRectificativa, [self::TIPO_RECTIFICATIVA_COMPLETA, self::TIPO_RECTIFICATIVA_SUSTITUTIVA])) {
|
||||||
|
|
@ -620,115 +608,13 @@ class Invoice extends BaseXmlModel implements XmlModelInterface
|
||||||
|
|
||||||
// For substitutive rectifications, ImporteRectificacion is mandatory
|
// For substitutive rectifications, ImporteRectificacion is mandatory
|
||||||
if ($this->tipoRectificativa === self::TIPO_RECTIFICATIVA_SUSTITUTIVA && $this->importeRectificacion === null) {
|
if ($this->tipoRectificativa === self::TIPO_RECTIFICATIVA_SUSTITUTIVA && $this->importeRectificacion === null) {
|
||||||
throw new \InvalidArgumentException('ImporteRectificacion is mandatory for substitutive rectifications (TipoRectificativa = S)');
|
throw new \InvalidArgumentException('Missing required field: ImporteRectificacion');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up the rectified invoice information for R1 invoices
|
|
||||||
*
|
|
||||||
* @param string $nif The NIF of the rectified invoice
|
|
||||||
* @param string $numSerie The series number of the rectified invoice
|
|
||||||
* @param string $fecha The date of the rectified invoice
|
|
||||||
* @return self
|
|
||||||
*/
|
|
||||||
public function setRectifiedInvoice(string $nif, string $numSerie, string $fecha): self
|
|
||||||
{
|
|
||||||
if ($this->tipoFactura !== self::TIPO_FACTURA_RECTIFICATIVA) {
|
|
||||||
throw new \InvalidArgumentException('This method can only be used for R1 invoices');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->facturaRectificativa === null) {
|
|
||||||
// Create FacturaRectificativa with proper values for the rectified amounts
|
|
||||||
// For R1 invoices, we need to set the base and tax amounts that were rectified
|
|
||||||
$baseRectificada = $this->importeTotal ?? 0.0; // Use current invoice total as base
|
|
||||||
$cuotaRectificada = $this->cuotaTotal ?? 0.0; // Use current invoice tax as tax
|
|
||||||
|
|
||||||
$this->facturaRectificativa = new FacturaRectificativa(
|
|
||||||
$this->tipoRectificativa ?? 'S', // Default to substitutive if not set
|
|
||||||
$baseRectificada,
|
|
||||||
$cuotaRectificada
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->facturaRectificativa->setRectifiedInvoice($nif, $numSerie, $fecha);
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up the rectified invoice information for R1 invoices using an IDFactura object
|
|
||||||
*
|
|
||||||
* @param IDFactura $idFactura The IDFactura object of the rectified invoice
|
|
||||||
* @return self
|
|
||||||
*/
|
|
||||||
public function setRectifiedInvoiceFromIDFactura(IDFactura $idFactura): self
|
|
||||||
{
|
|
||||||
if ($this->tipoFactura !== self::TIPO_FACTURA_RECTIFICATIVA) {
|
|
||||||
throw new \InvalidArgumentException('This method can only be used for R1 invoices');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->facturaRectificativa === null) {
|
|
||||||
// Create FacturaRectificativa with proper values for the rectified amounts
|
|
||||||
// For R1 invoices, we need to set the base and tax amounts that were rectified
|
|
||||||
$baseRectificada = $this->importeTotal ?? 0.0; // Use current invoice total as base
|
|
||||||
$cuotaRectificada = $this->cuotaTotal ?? 0.0; // Use current invoice tax as tax
|
|
||||||
|
|
||||||
$this->facturaRectificativa = new FacturaRectificativa(
|
|
||||||
$this->tipoRectificativa ?? 'S', // Default to substitutive if not set
|
|
||||||
$baseRectificada,
|
|
||||||
$cuotaRectificada
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->facturaRectificativa->setRectifiedInvoiceFromIDFactura($idFactura);
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the rectified amounts for R1 invoices
|
|
||||||
*
|
|
||||||
* @param float $baseRectificada The base amount that was rectified
|
|
||||||
* @param float $cuotaRectificada The tax amount that was rectified
|
|
||||||
* @param float|null $cuotaRecargoRectificado The surcharge amount that was rectified (optional)
|
|
||||||
* @return self
|
|
||||||
*/
|
|
||||||
public function setRectifiedAmounts(float $baseRectificada, float $cuotaRectificada, ?float $cuotaRecargoRectificado = null): self
|
|
||||||
{
|
|
||||||
if ($this->tipoFactura !== self::TIPO_FACTURA_RECTIFICATIVA) {
|
|
||||||
throw new \InvalidArgumentException('This method can only be used for R1 invoices');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the existing rectified invoice information if available
|
|
||||||
$existingRectifiedInvoice = null;
|
|
||||||
if ($this->facturaRectificativa !== null) {
|
|
||||||
$existingRectifiedInvoice = $this->facturaRectificativa->getFacturasRectificadas();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new FacturaRectificativa with the specified amounts
|
|
||||||
$this->facturaRectificativa = new FacturaRectificativa(
|
|
||||||
$this->tipoRectificativa ?? 'S',
|
|
||||||
$baseRectificada,
|
|
||||||
$cuotaRectificada,
|
|
||||||
$cuotaRecargoRectificado
|
|
||||||
);
|
|
||||||
|
|
||||||
// Restore the rectified invoice information if it existed
|
|
||||||
if ($existingRectifiedInvoice !== null && !empty($existingRectifiedInvoice)) {
|
|
||||||
foreach ($existingRectifiedInvoice as $rectifiedInvoice) {
|
|
||||||
$this->facturaRectificativa->addFacturaRectificada(
|
|
||||||
$rectifiedInvoice['nif'],
|
|
||||||
$rectifiedInvoice['numSerie'],
|
|
||||||
$rectifiedInvoice['fecha']
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setPrivateKeyPath(string $path): self
|
public function setPrivateKeyPath(string $path): self
|
||||||
{
|
{
|
||||||
$this->privateKeyPath = $path;
|
$this->privateKeyPath = $path;
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ use App\Services\EDocument\Standards\Verifactu\ResponseProcessor;
|
||||||
use App\Services\EDocument\Standards\Verifactu\Models\Encadenamiento;
|
use App\Services\EDocument\Standards\Verifactu\Models\Encadenamiento;
|
||||||
use App\Services\EDocument\Standards\Verifactu\Models\RegistroAnterior;
|
use App\Services\EDocument\Standards\Verifactu\Models\RegistroAnterior;
|
||||||
use App\Services\EDocument\Standards\Validation\VerifactuDocumentValidator;
|
use App\Services\EDocument\Standards\Validation\VerifactuDocumentValidator;
|
||||||
use App\Services\EDocument\Standards\Verifactu\Models\FacturaRectificativa;
|
|
||||||
use App\Services\EDocument\Standards\Verifactu\Models\Invoice as VerifactuInvoice;
|
use App\Services\EDocument\Standards\Verifactu\Models\Invoice as VerifactuInvoice;
|
||||||
|
|
||||||
class VerifactuFeatureTest extends TestCase
|
class VerifactuFeatureTest extends TestCase
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ use App\Services\EDocument\Standards\Verifactu\Models\Encadenamiento;
|
||||||
use App\Services\EDocument\Standards\Verifactu\Models\DetalleDesglose;
|
use App\Services\EDocument\Standards\Verifactu\Models\DetalleDesglose;
|
||||||
use App\Services\EDocument\Standards\Verifactu\Models\SistemaInformatico;
|
use App\Services\EDocument\Standards\Verifactu\Models\SistemaInformatico;
|
||||||
use App\Services\EDocument\Standards\Validation\VerifactuDocumentValidator;
|
use App\Services\EDocument\Standards\Validation\VerifactuDocumentValidator;
|
||||||
use App\Services\EDocument\Standards\Verifactu\Models\FacturaRectificativa;
|
|
||||||
use App\Services\EDocument\Standards\Verifactu\Models\PrimerRegistroCadena;
|
use App\Services\EDocument\Standards\Verifactu\Models\PrimerRegistroCadena;
|
||||||
use App\Services\EDocument\Standards\Verifactu\Models\PersonaFisicaJuridica;
|
use App\Services\EDocument\Standards\Verifactu\Models\PersonaFisicaJuridica;
|
||||||
use App\Services\EDocument\Standards\Verifactu\Models\IDOtro;
|
use App\Services\EDocument\Standards\Verifactu\Models\IDOtro;
|
||||||
|
|
@ -291,188 +290,6 @@ $this->assertCount(0, $errors);
|
||||||
$this->assertEquals($invoice->getTipoFactura(), $deserialized->getTipoFactura());
|
$this->assertEquals($invoice->getTipoFactura(), $deserialized->getTipoFactura());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCreateAndSerializeRectificationInvoice(): void
|
|
||||||
{
|
|
||||||
$invoice = new Invoice();
|
|
||||||
$invoice
|
|
||||||
->setIdVersion('1.0')
|
|
||||||
->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura())
|
|
||||||
->setIdEmisorFactura('B12345678')
|
|
||||||
->setNumSerieFactura('FAC-2023-003')
|
|
||||||
->setFechaExpedicionFactura('01-01-2023'))
|
|
||||||
->setNombreRazonEmisor('Empresa Ejemplo SL')
|
|
||||||
->setTipoFactura('R1')
|
|
||||||
->setTipoRectificativa('I')
|
|
||||||
->setDescripcionOperacion('Rectificación de factura anterior')
|
|
||||||
->setCuotaTotal(-21.00)
|
|
||||||
->setImporteTotal(-100.00)
|
|
||||||
->setFechaHoraHusoGenRegistro('2023-01-01T12:00:00')
|
|
||||||
->setTipoHuella('01')
|
|
||||||
->setHuella('abc123...');
|
|
||||||
|
|
||||||
// Add information system
|
|
||||||
$sistema = new SistemaInformatico();
|
|
||||||
$sistema
|
|
||||||
->setNombreRazon('Sistema de Facturación')
|
|
||||||
->setNif('B12345678')
|
|
||||||
->setNombreSistemaInformatico('SistemaFacturacion')
|
|
||||||
->setIdSistemaInformatico('01')
|
|
||||||
->setVersion('1.0')
|
|
||||||
->setNumeroInstalacion('INST-001')
|
|
||||||
->setTipoUsoPosibleSoloVerifactu('S')
|
|
||||||
->setTipoUsoPosibleMultiOT('S')
|
|
||||||
->setIndicadorMultiplesOT('S');
|
|
||||||
$invoice->setSistemaInformatico($sistema);
|
|
||||||
|
|
||||||
// Add desglose
|
|
||||||
$desglose = new Desglose();
|
|
||||||
$desglose->setDesgloseIVA([
|
|
||||||
'Impuesto' => '01',
|
|
||||||
'ClaveRegimen' => '02',
|
|
||||||
'CalificacionOperacion' => 'S2',
|
|
||||||
'BaseImponibleOimporteNoSujeto' => -100.00,
|
|
||||||
'TipoImpositivo' => 21,
|
|
||||||
'CuotaRepercutida' => -21.00
|
|
||||||
]);
|
|
||||||
$invoice->setDesglose($desglose);
|
|
||||||
|
|
||||||
// Add encadenamiento
|
|
||||||
$encadenamiento = new Encadenamiento();
|
|
||||||
$encadenamiento->setPrimerRegistro('S');
|
|
||||||
$invoice->setEncadenamiento($encadenamiento);
|
|
||||||
|
|
||||||
// Add rectified invoice
|
|
||||||
$facturaRectificativa = new FacturaRectificativa(
|
|
||||||
'I', // tipoRectificativa
|
|
||||||
-100.00, // baseRectificada
|
|
||||||
-21.00 // cuotaRectificada
|
|
||||||
);
|
|
||||||
$facturaRectificativa->addFacturaRectificada(
|
|
||||||
'B12345678', // nif
|
|
||||||
'FAC-2023-001', // numSerie
|
|
||||||
'01-01-2023' // fecha
|
|
||||||
);
|
|
||||||
$invoice->setFacturaRectificativa($facturaRectificativa);
|
|
||||||
|
|
||||||
$xml = $invoice->toXmlString();
|
|
||||||
|
|
||||||
|
|
||||||
$xslt = new VerifactuDocumentValidator($xml);
|
|
||||||
$xslt->validate();
|
|
||||||
$errors = $xslt->getVerifactuErrors();
|
|
||||||
|
|
||||||
if (count($errors) > 0) {
|
|
||||||
nlog($xml);
|
|
||||||
nlog($errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->assertCount(0, $errors);
|
|
||||||
|
|
||||||
// Test deserialization
|
|
||||||
$deserialized = Invoice::fromXml($xml);
|
|
||||||
$this->assertEquals($invoice->getIdVersion(), $deserialized->getIdVersion());
|
|
||||||
$this->assertEquals($invoice->getIdFactura(), $deserialized->getIdFactura());
|
|
||||||
$this->assertEquals($invoice->getNombreRazonEmisor(), $deserialized->getNombreRazonEmisor());
|
|
||||||
$this->assertEquals($invoice->getTipoFactura(), $deserialized->getTipoFactura());
|
|
||||||
$this->assertEquals($invoice->getTipoRectificativa(), $deserialized->getTipoRectificativa());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testCreateAndSerializeR1InvoiceWithImporteRectificacion(): void
|
|
||||||
{
|
|
||||||
$invoice = new Invoice();
|
|
||||||
$invoice
|
|
||||||
->setIdVersion('1.0')
|
|
||||||
->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura())
|
|
||||||
->setIdEmisorFactura('B12345678')
|
|
||||||
->setNumSerieFactura('FAC-2023-004')
|
|
||||||
->setFechaExpedicionFactura('01-01-2023'))
|
|
||||||
->setNombreRazonEmisor('Empresa Ejemplo SL')
|
|
||||||
->setTipoFactura('R1')
|
|
||||||
->setTipoRectificativa('I')
|
|
||||||
->setImporteRectificacion(100.00)
|
|
||||||
->setDescripcionOperacion('Rectificación completa de factura anterior')
|
|
||||||
->setCuotaTotal(21.00)
|
|
||||||
->setImporteTotal(100.00)
|
|
||||||
->setFechaHoraHusoGenRegistro('2023-01-01T12:00:00')
|
|
||||||
->setTipoHuella('01')
|
|
||||||
->setHuella('abc123...');
|
|
||||||
|
|
||||||
// Add information system
|
|
||||||
$sistema = new SistemaInformatico();
|
|
||||||
$sistema
|
|
||||||
->setNombreRazon('Sistema de Facturación')
|
|
||||||
->setNif('B12345678')
|
|
||||||
->setNombreSistemaInformatico('SistemaFacturacion')
|
|
||||||
->setIdSistemaInformatico('01')
|
|
||||||
->setVersion('1.0')
|
|
||||||
->setNumeroInstalacion('INST-001')
|
|
||||||
->setTipoUsoPosibleSoloVerifactu('S')
|
|
||||||
->setTipoUsoPosibleMultiOT('S')
|
|
||||||
->setIndicadorMultiplesOT('S');
|
|
||||||
$invoice->setSistemaInformatico($sistema);
|
|
||||||
|
|
||||||
// Add desglose
|
|
||||||
$desglose = new Desglose();
|
|
||||||
$desglose->setDesgloseIVA([
|
|
||||||
'Impuesto' => '01',
|
|
||||||
'ClaveRegimen' => '02',
|
|
||||||
'CalificacionOperacion' => 'S2',
|
|
||||||
'BaseImponibleOimporteNoSujeto' => 100.00,
|
|
||||||
'TipoImpositivo' => 21,
|
|
||||||
'CuotaRepercutida' => 21.00
|
|
||||||
]);
|
|
||||||
$invoice->setDesglose($desglose);
|
|
||||||
|
|
||||||
// Add encadenamiento
|
|
||||||
$encadenamiento = new Encadenamiento();
|
|
||||||
$encadenamiento->setPrimerRegistro('S');
|
|
||||||
$invoice->setEncadenamiento($encadenamiento);
|
|
||||||
|
|
||||||
// Add rectified invoice
|
|
||||||
$facturaRectificativa = new FacturaRectificativa(
|
|
||||||
'I', // tipoRectificativa
|
|
||||||
100.00, // baseRectificada
|
|
||||||
21.00 // cuotaRectificada
|
|
||||||
);
|
|
||||||
$facturaRectificativa->addFacturaRectificada(
|
|
||||||
'B12345678', // nif
|
|
||||||
'FAC-2023-001', // numSerie
|
|
||||||
'01-01-2023' // fecha
|
|
||||||
);
|
|
||||||
$invoice->setFacturaRectificativa($facturaRectificativa);
|
|
||||||
|
|
||||||
$xml = $invoice->toXmlString();
|
|
||||||
|
|
||||||
// Debug: Check if the property is set correctly
|
|
||||||
$this->assertEquals(100.00, $invoice->getImporteRectificacion());
|
|
||||||
$this->assertEquals('R1', $invoice->getTipoFactura());
|
|
||||||
|
|
||||||
// Debug: Log the XML to see what's actually generated
|
|
||||||
nlog('Generated XML: ' . $xml);
|
|
||||||
|
|
||||||
// Verify that ImporteRectificacion is included in the XML
|
|
||||||
// Note: The XML includes namespace prefix 'sum1:' and the value is formatted as '100' not '100.00'
|
|
||||||
$this->assertStringContainsString('<sum1:ImporteRectificacion>100</sum1:ImporteRectificacion>', $xml);
|
|
||||||
|
|
||||||
// Validate against Verifactu schema
|
|
||||||
$xslt = new VerifactuDocumentValidator($xml);
|
|
||||||
$xslt->validate();
|
|
||||||
$errors = $xslt->getVerifactuErrors();
|
|
||||||
|
|
||||||
if (count($errors) > 0) {
|
|
||||||
nlog($xml);
|
|
||||||
nlog($errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->assertCount(0, $errors);
|
|
||||||
|
|
||||||
// Test deserialization
|
|
||||||
$deserialized = Invoice::fromXml($xml);
|
|
||||||
$this->assertEquals($invoice->getIdVersion(), $deserialized->getIdVersion());
|
|
||||||
$this->assertEquals($invoice->getTipoFactura(), $deserialized->getTipoFactura());
|
|
||||||
$this->assertEquals($invoice->getTipoRectificativa(), $deserialized->getTipoRectificativa());
|
|
||||||
$this->assertEquals($invoice->getImporteRectificacion(), $deserialized->getImporteRectificacion());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testCreateAndSerializeInvoiceWithoutRecipient(): void
|
public function testCreateAndSerializeInvoiceWithoutRecipient(): void
|
||||||
{
|
{
|
||||||
|
|
@ -485,7 +302,6 @@ $this->assertCount(0, $errors);
|
||||||
->setFechaExpedicionFactura('01-01-2023'))
|
->setFechaExpedicionFactura('01-01-2023'))
|
||||||
->setNombreRazonEmisor('Empresa Ejemplo SL')
|
->setNombreRazonEmisor('Empresa Ejemplo SL')
|
||||||
->setTipoFactura('F1')
|
->setTipoFactura('F1')
|
||||||
->setFacturaSinIdentifDestinatarioArt61d('S')
|
|
||||||
->setDescripcionOperacion('Venta de productos varios')
|
->setDescripcionOperacion('Venta de productos varios')
|
||||||
->setCuotaTotal(21.00)
|
->setCuotaTotal(21.00)
|
||||||
->setImporteTotal(100.00)
|
->setImporteTotal(100.00)
|
||||||
|
|
@ -523,16 +339,16 @@ $this->assertCount(0, $errors);
|
||||||
|
|
||||||
$xml = $invoice->toXmlString();
|
$xml = $invoice->toXmlString();
|
||||||
|
|
||||||
$xslt = new VerifactuDocumentValidator($xml);
|
$xslt = new VerifactuDocumentValidator($xml);
|
||||||
$xslt->validate();
|
$xslt->validate();
|
||||||
$errors = $xslt->getVerifactuErrors();
|
$errors = $xslt->getVerifactuErrors();
|
||||||
|
|
||||||
if (count($errors) > 0) {
|
if (count($errors) > 0) {
|
||||||
nlog($xml);
|
nlog($xml);
|
||||||
nlog($errors);
|
nlog($errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->assertCount(0, $errors);
|
$this->assertCount(0, $errors);
|
||||||
|
|
||||||
// Test deserialization
|
// Test deserialization
|
||||||
$deserialized = Invoice::fromXml($xml);
|
$deserialized = Invoice::fromXml($xml);
|
||||||
|
|
@ -540,7 +356,6 @@ $this->assertCount(0, $errors);
|
||||||
$this->assertEquals($invoice->getIdFactura(), $deserialized->getIdFactura());
|
$this->assertEquals($invoice->getIdFactura(), $deserialized->getIdFactura());
|
||||||
$this->assertEquals($invoice->getNombreRazonEmisor(), $deserialized->getNombreRazonEmisor());
|
$this->assertEquals($invoice->getNombreRazonEmisor(), $deserialized->getNombreRazonEmisor());
|
||||||
$this->assertEquals($invoice->getTipoFactura(), $deserialized->getTipoFactura());
|
$this->assertEquals($invoice->getTipoFactura(), $deserialized->getTipoFactura());
|
||||||
$this->assertEquals($invoice->getFacturaSinIdentifDestinatarioArt61d(), $deserialized->getFacturaSinIdentifDestinatarioArt61d());
|
|
||||||
$this->assertEquals($invoice->getCuotaTotal(), $deserialized->getCuotaTotal());
|
$this->assertEquals($invoice->getCuotaTotal(), $deserialized->getCuotaTotal());
|
||||||
$this->assertEquals($invoice->getImporteTotal(), $deserialized->getImporteTotal());
|
$this->assertEquals($invoice->getImporteTotal(), $deserialized->getImporteTotal());
|
||||||
}
|
}
|
||||||
|
|
@ -557,93 +372,13 @@ $this->assertCount(0, $errors);
|
||||||
{
|
{
|
||||||
$invoice = new Invoice();
|
$invoice = new Invoice();
|
||||||
|
|
||||||
|
|
||||||
$this->expectException(\InvalidArgumentException::class);
|
$this->expectException(\InvalidArgumentException::class);
|
||||||
$this->expectExceptionMessage('Missing required field: IDVersion');
|
$this->expectExceptionMessage('Missing required field: IDVersion');
|
||||||
|
|
||||||
$invoice->toXmlString();
|
$invoice->toXmlString();
|
||||||
}
|
|
||||||
|
|
||||||
public function test_create_and_serialize_rectification_invoice()
|
|
||||||
{
|
|
||||||
$invoice = new Invoice();
|
|
||||||
$invoice->setIdVersion('1.0')
|
|
||||||
->setIdFactura((new \App\Services\EDocument\Standards\Verifactu\Models\IDFactura())
|
|
||||||
->setIdEmisorFactura('B12345678')
|
|
||||||
->setNumSerieFactura('FAC-2023-001')
|
|
||||||
->setFechaExpedicionFactura('01-01-2023'))
|
|
||||||
->setRefExterna('REF-123')
|
|
||||||
->setNombreRazonEmisor('Empresa Ejemplo SL')
|
|
||||||
->setTipoFactura('R1')
|
|
||||||
->setTipoRectificativa('S')
|
|
||||||
->setDescripcionOperacion('Rectificación de factura')
|
|
||||||
->setTercero((new PersonaFisicaJuridica())
|
|
||||||
->setNif('B12345678')
|
|
||||||
->setRazonSocial('Empresa Ejemplo SL'))
|
|
||||||
->setDesglose((new Desglose())
|
|
||||||
->setDesgloseIVA([
|
|
||||||
'Impuesto' => '01',
|
|
||||||
'ClaveRegimen' => '01',
|
|
||||||
'CalificacionOperacion' => 'S1',
|
|
||||||
'BaseImponibleOimporteNoSujeto' => 1000.00,
|
|
||||||
'TipoImpositivo' => 21.00,
|
|
||||||
'CuotaRepercutida' => 210.00
|
|
||||||
]))
|
|
||||||
->setCuotaTotal(210)
|
|
||||||
->setImporteTotal(1000)
|
|
||||||
->setSistemaInformatico((new SistemaInformatico())
|
|
||||||
->setNombreRazon('Sistema de Facturación')
|
|
||||||
->setNif('B12345678')
|
|
||||||
->setNombreSistemaInformatico('SistemaFacturacion')
|
|
||||||
->setIdSistemaInformatico('01')
|
|
||||||
->setVersion('1.0')
|
|
||||||
->setNumeroInstalacion('INST-001'))
|
|
||||||
->setFechaHoraHusoGenRegistro('2023-01-01T12:00:00')
|
|
||||||
->setTipoHuella('01')
|
|
||||||
->setHuella('abc123...');
|
|
||||||
|
|
||||||
// Create Encadenamiento with PrimerRegistroCadena
|
|
||||||
$encadenamiento = new Encadenamiento();
|
|
||||||
$encadenamiento->setPrimerRegistro('S');
|
|
||||||
$invoice->setEncadenamiento($encadenamiento);
|
|
||||||
|
|
||||||
$facturaRectificativa = new FacturaRectificativa(
|
|
||||||
'S', // TipoRectificativa (S for substitutive)
|
|
||||||
1000.00, // BaseRectificada
|
|
||||||
210.00, // CuotaRectificada
|
|
||||||
null // CuotaRecargoRectificado (optional)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add a rectified invoice
|
|
||||||
$facturaRectificativa->addFacturaRectificada(
|
|
||||||
'B12345678', // NIF
|
|
||||||
'FAC-2023-001', // NumSerieFactura
|
|
||||||
'24-04-2025' // FechaExpedicionFactura
|
|
||||||
);
|
|
||||||
|
|
||||||
$invoice->setFacturaRectificativa($facturaRectificativa);
|
|
||||||
|
|
||||||
$xml = $invoice->toXmlString();
|
|
||||||
|
|
||||||
$xslt = new VerifactuDocumentValidator($xml);
|
|
||||||
$xslt->validate();
|
|
||||||
$errors = $xslt->getVerifactuErrors();
|
|
||||||
|
|
||||||
if (count($errors) > 0) {
|
|
||||||
nlog($xml);
|
|
||||||
nlog($errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->assertCount(0, $errors);
|
|
||||||
|
|
||||||
|
|
||||||
// Test deserialization
|
|
||||||
$deserialized = Invoice::fromXml($xml);
|
|
||||||
$this->assertEquals($invoice->getIdVersion(), $deserialized->getIdVersion());
|
|
||||||
$this->assertEquals($invoice->getIdFactura(), $deserialized->getIdFactura());
|
|
||||||
$this->assertEquals($invoice->getTipoFactura(), $deserialized->getTipoFactura());
|
|
||||||
$this->assertEquals($invoice->getTipoRectificativa(), $deserialized->getTipoRectificativa());
|
|
||||||
$this->assertEquals($invoice->getCuotaTotal(), $deserialized->getCuotaTotal());
|
|
||||||
$this->assertEquals($invoice->getImporteTotal(), $deserialized->getImporteTotal());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCreateAndSerializeInvoiceWithMultipleRecipients(): void
|
public function testCreateAndSerializeInvoiceWithMultipleRecipients(): void
|
||||||
|
|
@ -676,7 +411,7 @@ $this->assertCount(0, $errors);
|
||||||
$destinatario2
|
$destinatario2
|
||||||
->setPais('FR')
|
->setPais('FR')
|
||||||
->setTipoIdentificacion('02')
|
->setTipoIdentificacion('02')
|
||||||
// ->setIdOtro('FR12345678901')
|
->setNif('FR1235678')
|
||||||
->setNombreRazon('Client 2 SARL');
|
->setNombreRazon('Client 2 SARL');
|
||||||
$destinatarios[] = $destinatario2;
|
$destinatarios[] = $destinatario2;
|
||||||
|
|
||||||
|
|
@ -716,16 +451,16 @@ $this->assertCount(0, $errors);
|
||||||
// Generate XML string
|
// Generate XML string
|
||||||
$xml = $invoice->toXmlString();
|
$xml = $invoice->toXmlString();
|
||||||
|
|
||||||
$xslt = new VerifactuDocumentValidator($xml);
|
$xslt = new VerifactuDocumentValidator($xml);
|
||||||
$xslt->validate();
|
$xslt->validate();
|
||||||
$errors = $xslt->getVerifactuErrors();
|
$errors = $xslt->getVerifactuErrors();
|
||||||
|
|
||||||
if (count($errors) > 0) {
|
if (count($errors) > 0) {
|
||||||
nlog($xml);
|
nlog($xml);
|
||||||
nlog($errors);
|
nlog($errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->assertCount(0, $errors);
|
$this->assertCount(0, $errors);
|
||||||
|
|
||||||
// Test deserialization
|
// Test deserialization
|
||||||
$deserialized = Invoice::fromXml($xml);
|
$deserialized = Invoice::fromXml($xml);
|
||||||
|
|
@ -737,10 +472,7 @@ $this->assertCount(0, $errors);
|
||||||
|
|
||||||
// Verify second recipient (with IDOtro)
|
// Verify second recipient (with IDOtro)
|
||||||
$this->assertEquals('Client 2 SARL', $deserialized->getDestinatarios()[1]->getNombreRazon());
|
$this->assertEquals('Client 2 SARL', $deserialized->getDestinatarios()[1]->getNombreRazon());
|
||||||
$this->assertEquals('FR', $deserialized->getDestinatarios()[1]->getPais());
|
|
||||||
$this->assertEquals('02', $deserialized->getDestinatarios()[1]->getTipoIdentificacion());
|
|
||||||
$this->assertEquals('FR12345678901', $deserialized->getDestinatarios()[1]->getIdOtro());
|
|
||||||
$this->assertNull($deserialized->getDestinatarios()[1]->getNif());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCreateAndSerializeInvoiceWithExemptOperation(): void
|
public function testCreateAndSerializeInvoiceWithExemptOperation(): void
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue