341 lines
20 KiB
PHP
341 lines
20 KiB
PHP
<?php
|
|
|
|
namespace App\Services\EDocument\Standards\Verifactu\Models;
|
|
|
|
/**
|
|
* InvoiceModification - Complete Invoice Modification Container
|
|
*
|
|
* This class represents the complete modification structure required for Verifactu e-invoicing
|
|
* modification operations. It contains both the cancellation record and the modification record.
|
|
*/
|
|
class InvoiceModification extends BaseXmlModel implements XmlModelInterface
|
|
{
|
|
protected RegistroAnulacion $registroAnulacion;
|
|
protected RegistroModificacion $registroModificacion;
|
|
protected SistemaInformatico $sistemaInformatico;
|
|
|
|
// @todo - in the UI we'll need additional logic to support these codes
|
|
private array $motivo_anulacion_codes = [
|
|
'1' => "Sustitución por otra factura", // Replacement by another invoice
|
|
'2' => "Error en facturación", // Billing error
|
|
'3' => "Anulación por devolución", // Cancellation due to return
|
|
'4' => "Anulación por insolvencia" // Cancellation due to insolvency
|
|
];
|
|
|
|
public function __construct()
|
|
{
|
|
$this->registroAnulacion = new RegistroAnulacion();
|
|
$this->registroModificacion = new RegistroModificacion();
|
|
$this->sistemaInformatico = new SistemaInformatico();
|
|
}
|
|
|
|
public function getRegistroAnulacion(): RegistroAnulacion
|
|
{
|
|
return $this->registroAnulacion;
|
|
}
|
|
|
|
public function setRegistroAnulacion(RegistroAnulacion $registroAnulacion): self
|
|
{
|
|
$this->registroAnulacion = $registroAnulacion;
|
|
return $this;
|
|
}
|
|
|
|
public function getRegistroModificacion(): RegistroModificacion
|
|
{
|
|
return $this->registroModificacion;
|
|
}
|
|
|
|
public function setRegistroModificacion(RegistroModificacion $registroModificacion): self
|
|
{
|
|
$this->registroModificacion = $registroModificacion;
|
|
return $this;
|
|
}
|
|
|
|
public function getSistemaInformatico(): SistemaInformatico
|
|
{
|
|
return $this->sistemaInformatico;
|
|
}
|
|
|
|
public function setSistemaInformatico(SistemaInformatico $sistemaInformatico): self
|
|
{
|
|
$this->sistemaInformatico = $sistemaInformatico;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Create a modification from an existing invoice
|
|
*/
|
|
public static function createFromInvoice(Invoice $originalInvoice, Invoice $modifiedInvoice): self
|
|
{
|
|
$modification = new self();
|
|
|
|
// Set up cancellation record
|
|
$cancellation = new RegistroAnulacion();
|
|
$cancellation
|
|
->setIdEmisorFactura($originalInvoice->getTercero()?->getNif() ?? 'B12345678')
|
|
->setNumSerieFactura($originalInvoice->getIdFactura())
|
|
->setFechaExpedicionFactura($originalInvoice->getFechaExpedicionFactura())
|
|
->setMotivoAnulacion('1'); // Sustitución por otra factura
|
|
|
|
$modification->setRegistroAnulacion($cancellation);
|
|
|
|
// Set up modification record
|
|
$modificationRecord = new RegistroModificacion();
|
|
$modificationRecord
|
|
->setIdVersion($modifiedInvoice->getIdVersion())
|
|
->setIdFactura($modifiedInvoice->getIdFactura())
|
|
->setRefExterna($modifiedInvoice->getRefExterna())
|
|
->setNombreRazonEmisor($modifiedInvoice->getNombreRazonEmisor())
|
|
->setSubsanacion($modifiedInvoice->getSubsanacion())
|
|
->setRechazoPrevio($modifiedInvoice->getRechazoPrevio())
|
|
->setTipoFactura('R1') // always R1 for rectification
|
|
->setTipoRectificativa('S') // always S for rectification
|
|
->setFacturasRectificadas($modifiedInvoice->getFacturasRectificadas())
|
|
->setFacturasSustituidas($modifiedInvoice->getFacturasSustituidas())
|
|
->setImporteRectificacion($modifiedInvoice->getImporteRectificacion())
|
|
->setFechaOperacion($modifiedInvoice->getFechaOperacion())
|
|
->setDescripcionOperacion($modifiedInvoice->getDescripcionOperacion())
|
|
->setFacturaSimplificadaArt7273($modifiedInvoice->getFacturaSimplificadaArt7273())
|
|
->setFacturaSinIdentifDestinatarioArt61d($modifiedInvoice->getFacturaSinIdentifDestinatarioArt61d())
|
|
->setMacrodato($modifiedInvoice->getMacrodato())
|
|
->setEmitidaPorTerceroODestinatario($modifiedInvoice->getEmitidaPorTerceroODestinatario())
|
|
->setTercero($modifiedInvoice->getTercero())
|
|
->setDestinatarios($modifiedInvoice->getDestinatarios())
|
|
->setCupon($modifiedInvoice->getCupon())
|
|
->setDesglose($modifiedInvoice->getDesglose())
|
|
->setCuotaTotal($modifiedInvoice->getCuotaTotal())
|
|
->setImporteTotal($modifiedInvoice->getImporteTotal())
|
|
->setEncadenamiento($modifiedInvoice->getEncadenamiento())
|
|
->setSistemaInformatico($modifiedInvoice->getSistemaInformatico())
|
|
->setFechaHoraHusoGenRegistro($modifiedInvoice->getFechaHoraHusoGenRegistro())
|
|
->setNumRegistroAcuerdoFacturacion($modifiedInvoice->getNumRegistroAcuerdoFacturacion())
|
|
->setIdAcuerdoSistemaInformatico($modifiedInvoice->getIdAcuerdoSistemaInformatico())
|
|
->setTipoHuella($modifiedInvoice->getTipoHuella())
|
|
->setHuella($modifiedInvoice->getHuella());
|
|
|
|
$modification->setRegistroModificacion($modificationRecord);
|
|
|
|
// Set up sistema informatico for the modification
|
|
$modification->setSistemaInformatico($modifiedInvoice->getSistemaInformatico());
|
|
|
|
return $modification;
|
|
}
|
|
|
|
public function toSoapEnvelope(): string
|
|
{
|
|
// Create the SOAP document
|
|
$soapDoc = new \DOMDocument('1.0', 'UTF-8');
|
|
$soapDoc->preserveWhiteSpace = false;
|
|
$soapDoc->formatOutput = true;
|
|
|
|
// Create SOAP envelope with namespaces
|
|
$envelope = $soapDoc->createElementNS('http://schemas.xmlsoap.org/soap/envelope/', 'soapenv:Envelope');
|
|
$envelope->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:soapenv', 'http://schemas.xmlsoap.org/soap/envelope/');
|
|
$envelope->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:lr', 'https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd');
|
|
$envelope->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:si', 'https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd');
|
|
|
|
$soapDoc->appendChild($envelope);
|
|
|
|
// Create Header
|
|
$header = $soapDoc->createElementNS('http://schemas.xmlsoap.org/soap/envelope/', 'soapenv:Header');
|
|
$envelope->appendChild($header);
|
|
|
|
// Create Body
|
|
$body = $soapDoc->createElementNS('http://schemas.xmlsoap.org/soap/envelope/', 'soapenv:Body');
|
|
$envelope->appendChild($body);
|
|
|
|
// Create RegFactuSistemaFacturacion
|
|
$regFactu = $soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd', 'lr:RegFactuSistemaFacturacion');
|
|
$body->appendChild($regFactu);
|
|
|
|
// Create Cabecera
|
|
$cabecera = $soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd', 'lr:Cabecera');
|
|
$regFactu->appendChild($cabecera);
|
|
|
|
// Add IDVersion
|
|
$cabecera->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'si:IDVersion', '1.0'));
|
|
|
|
// Create ObligadoEmision
|
|
$obligadoEmision = $soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'si:ObligadoEmision');
|
|
$cabecera->appendChild($obligadoEmision);
|
|
|
|
// Add ObligadoEmision content
|
|
$obligadoEmision->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'si:NombreRazon', $this->sistemaInformatico->getNombreRazon()));
|
|
$obligadoEmision->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'si:NIF', $this->sistemaInformatico->getNif()));
|
|
|
|
// Create RegistroFactura
|
|
$registroFactura = $soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd', 'lr:RegistroFactura');
|
|
$regFactu->appendChild($registroFactura);
|
|
|
|
// Create DatosFactura
|
|
$datosFactura = $soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'si:DatosFactura');
|
|
$registroFactura->appendChild($datosFactura);
|
|
|
|
// Add TipoFactura (R1 for rectificativa)
|
|
$datosFactura->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'si:TipoFactura', 'R1'));
|
|
|
|
// Add DescripcionOperacion
|
|
$datosFactura->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'si:DescripcionOperacion', $this->registroModificacion->getDescripcionOperacion()));
|
|
|
|
// Create ModificacionFactura with correct namespace
|
|
$modificacionFactura = $soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'si:ModificacionFactura');
|
|
$datosFactura->appendChild($modificacionFactura);
|
|
|
|
// Add TipoRectificativa (S for sustitutiva)
|
|
$modificacionFactura->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'si:TipoRectificativa', 'S'));
|
|
|
|
// Create FacturasRectificadas
|
|
$facturasRectificadas = $soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'si:FacturasRectificadas');
|
|
$modificacionFactura->appendChild($facturasRectificadas);
|
|
|
|
// Add Factura (the original invoice being rectified)
|
|
$factura = $soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'si:Factura');
|
|
$facturasRectificadas->appendChild($factura);
|
|
|
|
// Add original invoice details
|
|
$factura->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'si:NumSerieFacturaEmisor', $this->registroAnulacion->getNumSerieFactura()));
|
|
$factura->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'si:FechaExpedicionFacturaEmisor', $this->registroAnulacion->getFechaExpedicionFactura()));
|
|
|
|
// Add ImporteTotal
|
|
$datosFactura->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'si:ImporteTotal', $this->registroModificacion->getImporteTotal()));
|
|
|
|
// Create Impuestos
|
|
$impuestos = $soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'si:Impuestos');
|
|
$datosFactura->appendChild($impuestos);
|
|
|
|
// Create DetalleIVA
|
|
$detalleIVA = $soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'si:DetalleIVA');
|
|
$impuestos->appendChild($detalleIVA);
|
|
|
|
// Add tax details
|
|
$detalleIVA->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'si:TipoImpositivo', '21'));
|
|
$detalleIVA->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'si:BaseImponible', '200.00'));
|
|
$detalleIVA->appendChild($soapDoc->createElementNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'si:CuotaRepercutida', $this->registroModificacion->getCuotaTotal()));
|
|
|
|
return $soapDoc->saveXML();
|
|
}
|
|
|
|
public function toXmlString(): string
|
|
{
|
|
$doc = new \DOMDocument('1.0', 'UTF-8');
|
|
$doc->preserveWhiteSpace = false;
|
|
$doc->formatOutput = true;
|
|
|
|
// Create ModificacionFactura root
|
|
$root = $doc->createElementNS(self::XML_NAMESPACE, self::XML_NAMESPACE_PREFIX . ':ModificacionFactura');
|
|
$doc->appendChild($root);
|
|
|
|
// Add RegistroAnulacion
|
|
$registroAnulacionElement = $this->registroAnulacion->toXml($doc);
|
|
$root->appendChild($registroAnulacionElement);
|
|
|
|
// Add RegistroModificacion
|
|
$registroModificacionElement = $this->registroModificacion->toXml($doc);
|
|
$root->appendChild($registroModificacionElement);
|
|
|
|
return $doc->saveXML();
|
|
}
|
|
|
|
public function toXml(\DOMDocument $doc): \DOMElement
|
|
{
|
|
// Create ModificacionFactura root
|
|
$root = $doc->createElementNS(self::XML_NAMESPACE, self::XML_NAMESPACE_PREFIX . ':ModificacionFactura');
|
|
|
|
// Add RegistroAnulacion
|
|
$registroAnulacionElement = $this->registroAnulacion->toXml($doc);
|
|
$root->appendChild($registroAnulacionElement);
|
|
|
|
// Add RegistroModificacion
|
|
$registroModificacionElement = $this->registroModificacion->toXml($doc);
|
|
$root->appendChild($registroModificacionElement);
|
|
|
|
return $root;
|
|
}
|
|
|
|
public static function fromDOMElement(\DOMElement $element): self
|
|
{
|
|
$modification = new self();
|
|
|
|
// Handle RegistroAnulacion
|
|
$registroAnulacionElement = $element->getElementsByTagNameNS(self::XML_NAMESPACE, 'RegistroAnulacion')->item(0);
|
|
if ($registroAnulacionElement) {
|
|
$registroAnulacion = RegistroAnulacion::fromDOMElement($registroAnulacionElement);
|
|
$modification->setRegistroAnulacion($registroAnulacion);
|
|
}
|
|
|
|
// Handle RegistroModificacion
|
|
$registroModificacionElement = $element->getElementsByTagNameNS(self::XML_NAMESPACE, 'RegistroModificacion')->item(0);
|
|
if ($registroModificacionElement) {
|
|
$registroModificacion = RegistroModificacion::fromDOMElement($registroModificacionElement);
|
|
$modification->setRegistroModificacion($registroModificacion);
|
|
}
|
|
|
|
return $modification;
|
|
}
|
|
|
|
/**
|
|
* Create a proper RegistroAlta structure from the RegistroModificacion data
|
|
*/
|
|
private function createRegistroAltaFromModificacion(\DOMDocument $doc): \DOMElement
|
|
{
|
|
$registroAlta = $doc->createElementNS(self::XML_NAMESPACE, self::XML_NAMESPACE_PREFIX . ':RegistroAlta');
|
|
|
|
// Add IDVersion
|
|
$registroAlta->appendChild($this->createElement($doc, 'IDVersion', $this->registroModificacion->getIdVersion()));
|
|
|
|
// Create IDFactura structure
|
|
$idFactura = $doc->createElementNS(self::XML_NAMESPACE, self::XML_NAMESPACE_PREFIX . ':IDFactura');
|
|
$idFactura->appendChild($this->createElement($doc, 'IDEmisorFactura', $this->registroModificacion->getTercero()?->getNif() ?? 'B12345678'));
|
|
$idFactura->appendChild($this->createElement($doc, 'NumSerieFactura', $this->registroModificacion->getIdFactura()));
|
|
$idFactura->appendChild($this->createElement($doc, 'FechaExpedicionFactura', '2025-01-01'));
|
|
$registroAlta->appendChild($idFactura);
|
|
|
|
// Add other required elements
|
|
if ($this->registroModificacion->getRefExterna()) {
|
|
$registroAlta->appendChild($this->createElement($doc, 'RefExterna', $this->registroModificacion->getRefExterna()));
|
|
}
|
|
|
|
$registroAlta->appendChild($this->createElement($doc, 'NombreRazonEmisor', $this->registroModificacion->getNombreRazonEmisor()));
|
|
$registroAlta->appendChild($this->createElement($doc, 'TipoFactura', $this->registroModificacion->getTipoFactura()));
|
|
$registroAlta->appendChild($this->createElement($doc, 'DescripcionOperacion', $this->registroModificacion->getDescripcionOperacion()));
|
|
|
|
// Add Desglose
|
|
$desglose = $doc->createElementNS(self::XML_NAMESPACE, self::XML_NAMESPACE_PREFIX . ':Desglose');
|
|
$desgloseFactura = $doc->createElementNS(self::XML_NAMESPACE, self::XML_NAMESPACE_PREFIX . ':DesgloseFactura');
|
|
$desgloseFactura->appendChild($this->createElement($doc, 'Impuesto', '01'));
|
|
$desgloseFactura->appendChild($this->createElement($doc, 'ClaveRegimen', '01'));
|
|
$desgloseFactura->appendChild($this->createElement($doc, 'CalificacionOperacion', 'S1'));
|
|
$desgloseFactura->appendChild($this->createElement($doc, 'TipoImpositivo', '21'));
|
|
$desgloseFactura->appendChild($this->createElement($doc, 'BaseImponibleOimporteNoSujeto', '100.00'));
|
|
$desgloseFactura->appendChild($this->createElement($doc, 'CuotaRepercutida', '21.00'));
|
|
$desglose->appendChild($desgloseFactura);
|
|
$registroAlta->appendChild($desglose);
|
|
|
|
$registroAlta->appendChild($this->createElement($doc, 'CuotaTotal', $this->registroModificacion->getCuotaTotal()));
|
|
$registroAlta->appendChild($this->createElement($doc, 'ImporteTotal', $this->registroModificacion->getImporteTotal()));
|
|
|
|
// Add Encadenamiento
|
|
$encadenamiento = $doc->createElementNS(self::XML_NAMESPACE, self::XML_NAMESPACE_PREFIX . ':Encadenamiento');
|
|
$encadenamiento->appendChild($this->createElement($doc, 'PrimerRegistro', 'S'));
|
|
$registroAlta->appendChild($encadenamiento);
|
|
|
|
// Add SistemaInformatico
|
|
$sistemaInformatico = $doc->createElementNS(self::XML_NAMESPACE, self::XML_NAMESPACE_PREFIX . ':SistemaInformatico');
|
|
$sistemaInformatico->appendChild($this->createElement($doc, 'NombreRazon', 'Test System'));
|
|
$sistemaInformatico->appendChild($this->createElement($doc, 'NIF', 'B12345678'));
|
|
$sistemaInformatico->appendChild($this->createElement($doc, 'NombreSistemaInformatico', 'Test Software'));
|
|
$sistemaInformatico->appendChild($this->createElement($doc, 'IdSistemaInformatico', '01'));
|
|
$sistemaInformatico->appendChild($this->createElement($doc, 'Version', '1.0'));
|
|
$sistemaInformatico->appendChild($this->createElement($doc, 'NumeroInstalacion', '001'));
|
|
$sistemaInformatico->appendChild($this->createElement($doc, 'TipoUsoPosibleSoloVerifactu', 'S'));
|
|
$sistemaInformatico->appendChild($this->createElement($doc, 'TipoUsoPosibleMultiOT', 'S'));
|
|
$sistemaInformatico->appendChild($this->createElement($doc, 'IndicadorMultiplesOT', 'S'));
|
|
$registroAlta->appendChild($sistemaInformatico);
|
|
|
|
$registroAlta->appendChild($this->createElement($doc, 'FechaHoraHusoGenRegistro', $this->registroModificacion->getFechaHoraHusoGenRegistro()));
|
|
$registroAlta->appendChild($this->createElement($doc, 'TipoHuella', $this->registroModificacion->getTipoHuella()));
|
|
$registroAlta->appendChild($this->createElement($doc, 'Huella', $this->registroModificacion->getHuella()));
|
|
|
|
return $registroAlta;
|
|
}
|
|
}
|