Validation for verifactu documents
This commit is contained in:
parent
e8336c85d7
commit
e58f19f593
|
|
@ -98,6 +98,26 @@ class EntityLevel implements EntityLevelInterface
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$_invoice = (new \App\Services\EDocument\Standards\Verifactu\RegistroAlta($invoice))->run()->getInvoice();
|
||||||
|
$xml = $_invoice->toXmlString();
|
||||||
|
|
||||||
|
$xslt = new \App\Services\EDocument\Standards\Validation\VerifactuDocumentValidator($xml);
|
||||||
|
$xslt->validate();
|
||||||
|
$errors = $xslt->getVerifactuErrors();
|
||||||
|
nlog($errors);
|
||||||
|
|
||||||
|
if (isset($errors['stylesheet']) && count($errors['stylesheet']) > 0) {
|
||||||
|
$this->errors['invoice'] = array_merge($this->errors['invoice'], $errors['stylesheet']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($errors['general']) && count($errors['general']) > 0) {
|
||||||
|
$this->errors['invoice'] = array_merge($this->errors['invoice'], $errors['general']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($errors['xsd']) && count($errors['xsd']) > 0) {
|
||||||
|
$this->errors['invoice'] = array_merge($this->errors['invoice'], $errors['xsd']);
|
||||||
|
}
|
||||||
|
|
||||||
// $this->errors['invoice'][] = 'test error';
|
// $this->errors['invoice'][] = 'test error';
|
||||||
|
|
||||||
$this->errors['passes'] = count($this->errors['invoice']) === 0 && count($this->errors['company']) === 0; //no need to check client as we are using client level settings
|
$this->errors['passes'] = count($this->errors['invoice']) === 0 && count($this->errors['company']) === 0; //no need to check client as we are using client level settings
|
||||||
|
|
|
||||||
|
|
@ -79,11 +79,7 @@ class VerifactuDocumentValidator extends XsltDocumentValidator
|
||||||
libxml_clear_errors();
|
libxml_clear_errors();
|
||||||
|
|
||||||
foreach ($errors as $error) {
|
foreach ($errors as $error) {
|
||||||
$this->errors['xsd'][] = sprintf(
|
$this->errors['xsd'][] = $this->formatXsdError($error);
|
||||||
'Line %d: %s',
|
|
||||||
$error->line,
|
|
||||||
trim($error->message)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -91,6 +87,104 @@ class VerifactuDocumentValidator extends XsltDocumentValidator
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format XSD validation errors to be more human-readable
|
||||||
|
*
|
||||||
|
* @param \LibXMLError $error The libxml error object
|
||||||
|
* @return string Formatted error message
|
||||||
|
*/
|
||||||
|
private function formatXsdError(\LibXMLError $error): string
|
||||||
|
{
|
||||||
|
$message = trim($error->message);
|
||||||
|
$line = $error->line;
|
||||||
|
|
||||||
|
// Remove long namespace URLs to make errors more readable
|
||||||
|
$message = preg_replace(
|
||||||
|
'/\{https:\/\/www2\.agenciatributaria\.gob\.es\/static_files\/common\/internet\/dep\/aplicaciones\/es\/aeat\/tike\/cont\/ws\/[^}]+\}/',
|
||||||
|
'',
|
||||||
|
$message
|
||||||
|
);
|
||||||
|
|
||||||
|
// Clean up the message and make it more user-friendly
|
||||||
|
$message = $this->translateXsdError($message);
|
||||||
|
|
||||||
|
return sprintf('Line %d: %s', $line, $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate XSD error messages to more user-friendly Spanish/English descriptions
|
||||||
|
*
|
||||||
|
* @param string $message The original XSD error message
|
||||||
|
* @return string Translated and improved error message
|
||||||
|
*/
|
||||||
|
private function translateXsdError(string $message): string
|
||||||
|
{
|
||||||
|
// Common error patterns and their translations
|
||||||
|
$errorTranslations = [
|
||||||
|
// Missing child elements
|
||||||
|
'/Missing child element\(s\)\. Expected is \( ([^)]+) \)/' => 'Faltan elementos requeridos: $1',
|
||||||
|
'/Missing child element\(s\)\. Expected is \( ([^)]+) \)/' => 'Missing required elements: $1',
|
||||||
|
|
||||||
|
// Element not found
|
||||||
|
'/Element ([^:]+): ([^:]+) not found/' => 'Elemento no encontrado: $2',
|
||||||
|
'/Element ([^:]+): ([^:]+) not found/' => 'Element not found: $2',
|
||||||
|
|
||||||
|
// Invalid content
|
||||||
|
'/Element ([^:]+): ([^:]+) has invalid content/' => 'Contenido inválido en elemento: $2',
|
||||||
|
'/Element ([^:]+): ([^:]+) has invalid content/' => 'Invalid content in element: $2',
|
||||||
|
|
||||||
|
// Required attribute missing
|
||||||
|
'/The attribute ([^:]+) is required/' => 'Atributo requerido faltante: $1',
|
||||||
|
'/The attribute ([^:]+) is required/' => 'Required attribute missing: $1',
|
||||||
|
|
||||||
|
// Value not allowed
|
||||||
|
'/Value ([^:]+) is not allowed/' => 'Valor no permitido: $1',
|
||||||
|
'/Value ([^:]+) is not allowed/' => 'Value not allowed: $1',
|
||||||
|
|
||||||
|
// Pattern validation failed
|
||||||
|
'/Element ([^:]+): ([^:]+) is not a valid value of the atomic type/' => 'Valor inválido para el elemento: $2',
|
||||||
|
'/Element ([^:]+): ([^:]+) is not a valid value of the atomic type/' => 'Invalid value for element: $2',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Apply translations
|
||||||
|
foreach ($errorTranslations as $pattern => $replacement) {
|
||||||
|
if (preg_match($pattern, $message, $matches)) {
|
||||||
|
$message = preg_replace($pattern, $replacement, $message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up common element names and make them more readable
|
||||||
|
$elementTranslations = [
|
||||||
|
'Desglose' => 'Desglose (Tax Breakdown)',
|
||||||
|
'DetalleDesglose' => 'DetalleDesglose (Tax Detail)',
|
||||||
|
'TipoFactura' => 'TipoFactura (Invoice Type)',
|
||||||
|
'DescripcionOperacion' => 'DescripcionOperacion (Operation Description)',
|
||||||
|
'ImporteTotal' => 'ImporteTotal (Total Amount)',
|
||||||
|
'RegistroAlta' => 'RegistroAlta (Registration Record)',
|
||||||
|
'RegistroAnulacion' => 'RegistroAnulacion (Cancellation Record)',
|
||||||
|
'FacturasRectificadas' => 'FacturasRectificadas (Corrected Invoices)',
|
||||||
|
'IDFacturaRectificada' => 'IDFacturaRectificada (Corrected Invoice ID)',
|
||||||
|
'IDEmisorFactura' => 'IDEmisorFactura (Invoice Emitter ID)',
|
||||||
|
'NumSerieFactura' => 'NumSerieFactura (Invoice Series Number)',
|
||||||
|
'FechaExpedicionFactura' => 'FechaExpedicionFactura (Invoice Issue Date)',
|
||||||
|
'Impuestos' => 'Impuestos (Taxes)',
|
||||||
|
'DetalleIVA' => 'DetalleIVA (VAT Detail)',
|
||||||
|
'CuotaRepercutida' => 'CuotaRepercutida (Recharged Tax Amount)',
|
||||||
|
'FechaExpedicionFacturaEmisor' => 'FechaExpedicionFacturaEmisor (Emitter Invoice Issue Date)',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($elementTranslations as $element => $translation) {
|
||||||
|
$message = str_replace($element, $translation, $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove extra whitespace and clean up the message
|
||||||
|
$message = preg_replace('/\s+/', ' ', $message);
|
||||||
|
$message = trim($message);
|
||||||
|
|
||||||
|
return $message;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detect the type of Verifactu document
|
* Detect the type of Verifactu document
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -201,23 +201,25 @@ class RegistroAlta
|
||||||
|
|
||||||
$client_country_code = $this->invoice->client->country->iso_3166_2;
|
$client_country_code = $this->invoice->client->country->iso_3166_2;
|
||||||
|
|
||||||
|
/** By Default we assume a Spanish transaction */
|
||||||
$impuesto = 'S2';
|
$impuesto = 'S2';
|
||||||
$clave_regimen = '08';
|
$clave_regimen = '08';
|
||||||
$calificacion = 'S1';
|
$calificacion = 'S1';
|
||||||
|
|
||||||
$br = new \App\DataMapper\Tax\BaseRule();
|
$br = new \App\DataMapper\Tax\BaseRule();
|
||||||
|
|
||||||
|
/** EU B2B */
|
||||||
if (in_array($client_country_code, $br->eu_country_codes) && $this->invoice->client->classification != 'individual') {
|
if (in_array($client_country_code, $br->eu_country_codes) && $this->invoice->client->classification != 'individual') {
|
||||||
$impuesto = '05';
|
$impuesto = '05';
|
||||||
$clave_regimen = '05';
|
$clave_regimen = '05';
|
||||||
$calificacion = 'N2';
|
$calificacion = 'N2';
|
||||||
}
|
} /** EU B2C */
|
||||||
elseif (in_array($client_country_code, $br->eu_country_codes) && $this->invoice->client->classification == 'individual') {
|
elseif (in_array($client_country_code, $br->eu_country_codes) && $this->invoice->client->classification == 'individual') {
|
||||||
$impuesto = '08';
|
$impuesto = '08';
|
||||||
$clave_regimen = '05';
|
$clave_regimen = '05';
|
||||||
$calificacion = 'N2';
|
$calificacion = 'N2';
|
||||||
}
|
}
|
||||||
else{ //Non-EU
|
else { /** Non-EU */
|
||||||
$impuesto = '05';
|
$impuesto = '05';
|
||||||
$clave_regimen = '05';
|
$clave_regimen = '05';
|
||||||
$calificacion = 'N2';
|
$calificacion = 'N2';
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue