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['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();
|
||||
|
||||
foreach ($errors as $error) {
|
||||
$this->errors['xsd'][] = sprintf(
|
||||
'Line %d: %s',
|
||||
$error->line,
|
||||
trim($error->message)
|
||||
);
|
||||
$this->errors['xsd'][] = $this->formatXsdError($error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -91,6 +87,104 @@ class VerifactuDocumentValidator extends XsltDocumentValidator
|
|||
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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -201,23 +201,25 @@ class RegistroAlta
|
|||
|
||||
$client_country_code = $this->invoice->client->country->iso_3166_2;
|
||||
|
||||
/** By Default we assume a Spanish transaction */
|
||||
$impuesto = 'S2';
|
||||
$clave_regimen = '08';
|
||||
$calificacion = 'S1';
|
||||
|
||||
$br = new \App\DataMapper\Tax\BaseRule();
|
||||
|
||||
/** EU B2B */
|
||||
if (in_array($client_country_code, $br->eu_country_codes) && $this->invoice->client->classification != 'individual') {
|
||||
$impuesto = '05';
|
||||
$clave_regimen = '05';
|
||||
$calificacion = 'N2';
|
||||
}
|
||||
} /** EU B2C */
|
||||
elseif (in_array($client_country_code, $br->eu_country_codes) && $this->invoice->client->classification == 'individual') {
|
||||
$impuesto = '08';
|
||||
$clave_regimen = '05';
|
||||
$calificacion = 'N2';
|
||||
}
|
||||
else{ //Non-EU
|
||||
else { /** Non-EU */
|
||||
$impuesto = '05';
|
||||
$clave_regimen = '05';
|
||||
$calificacion = 'N2';
|
||||
|
|
|
|||
Loading…
Reference in New Issue