feat(validation): Implement EN16931 compliance validation types and VAT categories

- Added validation types for EN16931 compliance in `validation.types.ts`, including interfaces for `ValidationResult`, `ValidationOptions`, and `ValidationReport`.
- Introduced `VATCategoriesValidator` in `vat-categories.validator.ts` to validate VAT categories according to EN16931 rules, including detailed checks for standard, zero-rated, exempt, reverse charge, intra-community, export, and out-of-scope services.
- Enhanced `IEInvoiceMetadata` interface in `en16931-metadata.ts` to include additional fields required for full standards compliance, such as delivery information, payment information, allowances, and charges.
- Implemented helper methods for VAT calculations and validation logic to ensure accurate compliance with EN16931 standards.
This commit is contained in:
2025-08-11 12:25:32 +00:00
parent 01c6e8daad
commit 10e14af85b
53 changed files with 11315 additions and 17 deletions

View File

@@ -27,6 +27,14 @@ import { PDFExtractor } from './formats/pdf/pdf.extractor.js';
// Import format detector
import { FormatDetector } from './formats/utils/format.detector.js';
// Import enhanced validators
import { EN16931BusinessRulesValidator } from './formats/validation/en16931.business-rules.validator.js';
import { CodeListValidator } from './formats/validation/codelist.validator.js';
import type { ValidationOptions } from './formats/validation/validation.types.js';
// Import EN16931 metadata interface
import type { IEInvoiceMetadata } from './interfaces/en16931-metadata.js';
/**
* Main class for working with electronic invoices.
* Supports various invoice formats including Factur-X, ZUGFeRD, UBL, and XRechnung
@@ -169,13 +177,7 @@ export class EInvoice implements TInvoice {
}
// EInvoice specific properties
public metadata?: {
format?: InvoiceFormat;
version?: string;
profile?: string;
customizationId?: string;
extensions?: Record<string, any>;
};
public metadata?: IEInvoiceMetadata;
private xmlString: string = '';
private detectedFormat: InvoiceFormat = InvoiceFormat.UNKNOWN;
@@ -430,17 +432,64 @@ export class EInvoice implements TInvoice {
* @param level The validation level to use
* @returns The validation result
*/
public async validate(level: ValidationLevel = ValidationLevel.BUSINESS): Promise<ValidationResult> {
public async validate(level: ValidationLevel = ValidationLevel.BUSINESS, options?: ValidationOptions): Promise<ValidationResult> {
try {
const format = this.detectedFormat || InvoiceFormat.UNKNOWN;
if (format === InvoiceFormat.UNKNOWN) {
throw new EInvoiceValidationError('Cannot validate: format unknown', []);
}
const validator = ValidatorFactory.createValidator(this.xmlString);
const result = validator.validate(level);
// For programmatically created invoices without XML, skip XML-based validation
let result: ValidationResult;
if (this.xmlString && this.detectedFormat !== InvoiceFormat.UNKNOWN) {
// Use existing validator for XML-based validation
const validator = ValidatorFactory.createValidator(this.xmlString);
result = validator.validate(level);
} else {
// Create a basic result for programmatically created invoices
result = {
valid: true,
errors: [],
warnings: [],
level: level
};
}
// Enhanced validation with feature flags
if (options?.featureFlags?.includes('EN16931_BUSINESS_RULES')) {
const businessRulesValidator = new EN16931BusinessRulesValidator();
const businessResults = businessRulesValidator.validate(this, options);
// Merge results
result.errors = result.errors.concat(
businessResults
.filter(r => r.severity === 'error')
.map(r => ({ code: r.ruleId, message: r.message, field: r.field }))
);
// Add warnings if not in report-only mode
if (!options.reportOnly) {
result.warnings = (result.warnings || []).concat(
businessResults
.filter(r => r.severity === 'warning')
.map(r => ({ code: r.ruleId, message: r.message, field: r.field }))
);
}
}
// Code list validation with feature flag
if (options?.featureFlags?.includes('CODE_LIST_VALIDATION')) {
const codeListValidator = new CodeListValidator();
const codeListResults = codeListValidator.validate(this);
// Merge results
result.errors = result.errors.concat(
codeListResults
.filter(r => r.severity === 'error')
.map(r => ({ code: r.ruleId, message: r.message, field: r.field }))
);
}
// Update validation status
this.validationErrors = result.errors;
result.valid = result.errors.length === 0 || options?.reportOnly === true;
return result;
} catch (error) {
if (error instanceof EInvoiceError) {