diff --git a/app/Services/EDocument/Standards/Validation/VerifactuDocumentValidator.php b/app/Services/EDocument/Standards/Validation/VerifactuDocumentValidator.php
index c4c6842ed9..7990214e8b 100644
--- a/app/Services/EDocument/Standards/Validation/VerifactuDocumentValidator.php
+++ b/app/Services/EDocument/Standards/Validation/VerifactuDocumentValidator.php
@@ -119,30 +119,27 @@ class VerifactuDocumentValidator extends XsltDocumentValidator
*/
private function translateXsdError(string $message): string
{
+ // Handle missing child element error specifically
+ if (preg_match('/Missing child element\(s\)\. Expected is \( ([^)]+) \)/', $message, $matches)) {
+ $expectedElement = trim($matches[1]);
+ $message = "Missing required child element: $expectedElement";
+ }
+
// 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',
];
@@ -174,6 +171,7 @@ class VerifactuDocumentValidator extends XsltDocumentValidator
'FechaExpedicionFacturaEmisor' => 'FechaExpedicionFacturaEmisor (Emitter Invoice Issue Date)',
];
+ // Apply element translations
foreach ($elementTranslations as $element => $translation) {
$message = str_replace($element, $translation, $message);
}
@@ -450,4 +448,162 @@ class VerifactuDocumentValidator extends XsltDocumentValidator
{
return $this->getErrors();
}
+
+ /**
+ * Get detailed error information with suggestions for fixing common issues
+ *
+ * @return array Detailed error information with context and suggestions
+ */
+ public function getDetailedErrors(): array
+ {
+ $detailedErrors = [];
+
+ foreach ($this->errors as $errorType => $errors) {
+ foreach ($errors as $error) {
+ $detailedErrors[] = [
+ 'type' => $errorType,
+ 'message' => $error,
+ 'context' => $this->getErrorContext($error),
+ 'suggestion' => $this->getErrorSuggestion($error),
+ 'severity' => $this->getErrorSeverity($errorType)
+ ];
+ }
+ }
+
+ return $detailedErrors;
+ }
+
+ /**
+ * Get context information for an error
+ *
+ * @param string $error The error message
+ * @return string Context information
+ */
+ private function getErrorContext(string $error): string
+ {
+ if (strpos($error, 'Desglose') !== false) {
+ return 'The Desglose (Tax Breakdown) element requires a DetalleDesglose (Tax Detail) child element to specify the tax breakdown structure.';
+ }
+
+ if (strpos($error, 'TipoFactura') !== false) {
+ return 'The TipoFactura (Invoice Type) element specifies the type of invoice being processed (e.g., F1 for regular invoice, R1 for modification).';
+ }
+
+ if (strpos($error, 'DescripcionOperacion') !== false) {
+ return 'The DescripcionOperacion (Operation Description) element provides a description of the business operation being documented.';
+ }
+
+ if (strpos($error, 'ImporteTotal') !== false) {
+ return 'The ImporteTotal (Total Amount) element contains the total amount of the invoice including all taxes.';
+ }
+
+ if (strpos($error, 'FacturasRectificadas') !== false) {
+ return 'The FacturasRectificadas (Corrected Invoices) element is required for modification invoices to reference the original invoices being corrected.';
+ }
+
+ return 'This error indicates a structural issue with the XML document that prevents it from conforming to the Verifactu schema requirements.';
+ }
+
+ /**
+ * Get suggestions for fixing an error
+ *
+ * @param string $error The error message
+ * @return string Suggestion for fixing the error
+ */
+ private function getErrorSuggestion(string $error): string
+ {
+ if (strpos($error, 'Missing child element') !== false && strpos($error, 'DetalleDesglose') !== false) {
+ return 'Add a DetalleDesglose element within the Desglose element to specify the tax breakdown details. Example: 21100.0021.00';
+ }
+
+ if (strpos($error, 'TipoFactura') !== false) {
+ return 'Ensure the TipoFactura element contains a valid value: F1 (regular invoice), F2 (simplified invoice), F3 (modification), or R1 (modification).';
+ }
+
+ if (strpos($error, 'DescripcionOperacion') !== false) {
+ return 'Add a DescripcionOperacion element with a clear description of the business operation, such as "Venta de mercancías" or "Prestación de servicios".';
+ }
+
+ if (strpos($error, 'ImporteTotal') !== false) {
+ return 'Ensure the ImporteTotal element contains a valid numeric value representing the total invoice amount including taxes.';
+ }
+
+ if (strpos($error, 'FacturasRectificadas') !== false) {
+ return 'For modification invoices, add the FacturasRectificadas element with at least one IDFacturaRectificada containing the original invoice details.';
+ }
+
+ return 'Review the XML structure against the Verifactu schema requirements and ensure all required elements are present with valid content.';
+ }
+
+ /**
+ * Get error severity level
+ *
+ * @param string $errorType The type of error
+ * @return string Severity level
+ */
+ private function getErrorSeverity(string $errorType): string
+ {
+ return match($errorType) {
+ 'xsd' => 'high',
+ 'structure' => 'medium',
+ 'business' => 'low',
+ 'general' => 'medium',
+ default => 'medium'
+ };
+ }
+
+ /**
+ * Get a user-friendly summary of validation errors
+ *
+ * @return string Summary of validation errors
+ */
+ public function getErrorSummary(): string
+ {
+ if (empty($this->errors)) {
+ return 'Document validation passed successfully.';
+ }
+
+ $summary = [];
+ $totalErrors = 0;
+
+ foreach ($this->errors as $errorType => $errors) {
+ $count = count($errors);
+ $totalErrors += $count;
+
+ $typeLabel = match($errorType) {
+ 'xsd' => 'Schema Validation Errors',
+ 'structure' => 'Structural Errors',
+ 'business' => 'Business Rule Violations',
+ 'general' => 'General Errors',
+ default => ucfirst($errorType) . ' Errors'
+ };
+
+ $summary[] = "$typeLabel: $count";
+ }
+
+ $summaryText = "Validation failed with $totalErrors total error(s):\n";
+ $summaryText .= implode(', ', $summary);
+
+ return $summaryText;
+ }
+
+ /**
+ * Get errors formatted for display in logs or user interfaces
+ *
+ * @return array Formatted errors grouped by type
+ */
+ public function getFormattedErrors(): array
+ {
+ $formatted = [];
+
+ foreach ($this->errors as $errorType => $errors) {
+ $formatted[$errorType] = [
+ 'count' => count($errors),
+ 'messages' => $errors,
+ 'severity' => $this->getErrorSeverity($errorType)
+ ];
+ }
+
+ return $formatted;
+ }
}
\ No newline at end of file
diff --git a/tests/Feature/EInvoice/Verifactu/VerifactuDocumentValidatorTest.php b/tests/Feature/EInvoice/Verifactu/VerifactuDocumentValidatorTest.php
new file mode 100644
index 0000000000..0ea04a40d6
--- /dev/null
+++ b/tests/Feature/EInvoice/Verifactu/VerifactuDocumentValidatorTest.php
@@ -0,0 +1,119 @@
+line = 12;
+ $mockError->message = 'Element \'{https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd}Desglose\': Missing child element(s). Expected is ( {https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd}DetalleDesglose ).';
+
+ // Use reflection to test the private method
+ $validator = new VerifactuDocumentValidator('');
+ $reflection = new \ReflectionClass($validator);
+ $formatMethod = $reflection->getMethod('formatXsdError');
+ $formatMethod->setAccessible(true);
+
+ $formattedError = $formatMethod->invoke($validator, $mockError);
+
+ // The formatted error should be more readable
+ $this->assertStringContainsString('Line 12:', $formattedError);
+ $this->assertStringContainsString('Missing required child element:', $formattedError);
+ $this->assertStringContainsString('DetalleDesglose (Tax Detail)', $formattedError);
+ $this->assertStringNotContainsString('https://www2.agenciatributaria.gob.es', $formattedError);
+ }
+
+ /**
+ * Test that error context provides helpful information
+ */
+ public function test_error_context_provides_helpful_information()
+ {
+ $validator = new VerifactuDocumentValidator('');
+ $reflection = new \ReflectionClass($validator);
+ $contextMethod = $reflection->getMethod('getErrorContext');
+ $contextMethod->setAccessible(true);
+
+ $context = $contextMethod->invoke($validator, 'Missing child element: DetalleDesglose');
+
+ $this->assertStringContainsString('Desglose (Tax Breakdown)', $context);
+ $this->assertStringContainsString('DetalleDesglose (Tax Detail)', $context);
+ $this->assertStringContainsString('requires', $context);
+ }
+
+ /**
+ * Test that error suggestions provide actionable advice
+ */
+ public function test_error_suggestions_provide_actionable_advice()
+ {
+ $validator = new VerifactuDocumentValidator('');
+ $reflection = new \ReflectionClass($validator);
+ $suggestionMethod = $reflection->getMethod('getErrorSuggestion');
+ $suggestionMethod->setAccessible(true);
+
+ $suggestion = $suggestionMethod->invoke($validator, 'Missing child element: DetalleDesglose');
+
+ $this->assertStringContainsString('Add a DetalleDesglose element', $suggestion);
+ $this->assertStringContainsString('Example:', $suggestion);
+ $this->assertStringContainsString('', $suggestion);
+ }
+
+ /**
+ * Test error summary provides clear overview
+ */
+ public function test_error_summary_provides_clear_overview()
+ {
+ $validator = new VerifactuDocumentValidator('');
+
+ // Initially no errors
+ $summary = $validator->getErrorSummary();
+ $this->assertEquals('Document validation passed successfully.', $summary);
+
+ // Add some mock errors
+ $reflection = new \ReflectionClass($validator);
+ $errorsProperty = $reflection->getProperty('errors');
+ $errorsProperty->setAccessible(true);
+ $errorsProperty->setValue($validator, [
+ 'xsd' => ['Error 1', 'Error 2'],
+ 'structure' => ['Error 3']
+ ]);
+
+ $summary = $validator->getErrorSummary();
+ $this->assertStringContainsString('Validation failed with 3 total error(s):', $summary);
+ $this->assertStringContainsString('Schema Validation Errors: 2', $summary);
+ $this->assertStringContainsString('Structural Errors: 1', $summary);
+ }
+
+ /**
+ * Test formatted errors provide structured information
+ */
+ public function test_formatted_errors_provide_structured_information()
+ {
+ $validator = new VerifactuDocumentValidator('');
+
+ // Add some mock errors
+ $reflection = new \ReflectionClass($validator);
+ $errorsProperty = $reflection->getProperty('errors');
+ $errorsProperty->setAccessible(true);
+ $errorsProperty->setValue($validator, [
+ 'xsd' => ['Error 1', 'Error 2'],
+ 'business' => ['Error 3']
+ ]);
+
+ $formatted = $validator->getFormattedErrors();
+
+ $this->assertArrayHasKey('xsd', $formatted);
+ $this->assertArrayHasKey('business', $formatted);
+ $this->assertEquals(2, $formatted['xsd']['count']);
+ $this->assertEquals('high', $formatted['xsd']['severity']);
+ $this->assertEquals('low', $formatted['business']['severity']);
+ }
+}