@casoon/invoice-generator
Version:
Generate PDF invoices, XRechnung XML, and ZUGFeRD PDF/A-3 from JSON data with TypeScript support
137 lines (113 loc) • 3.96 kB
text/typescript
import { Invoice } from '../types/invoice.types';
import fs from 'fs';
import path from 'path';
export interface ValidationError {
field: string;
message: string;
value?: any;
}
export interface ValidationResult {
isValid: boolean;
errors: ValidationError[];
}
/**
* Validates invoice data against business rules
*/
export function validateInvoice(invoice: Invoice): ValidationResult {
const errors: ValidationError[] = [];
// Required fields validation
if (!invoice.sender?.name) {
errors.push({ field: 'sender.name', message: 'Sender name is required' });
}
if (!invoice.sender?.address?.street) {
errors.push({ field: 'sender.address.street', message: 'Sender street is required' });
}
if (!invoice.sender?.contactInfo?.email) {
errors.push({ field: 'sender.contactInfo.email', message: 'Sender email is required' });
}
if (!invoice.recipient?.name) {
errors.push({ field: 'recipient.name', message: 'Recipient name is required' });
}
if (!invoice.details?.invoiceNumber) {
errors.push({ field: 'details.invoiceNumber', message: 'Invoice number is required' });
}
if (!invoice.details?.invoiceDate) {
errors.push({ field: 'details.invoiceDate', message: 'Invoice date is required' });
}
// Items validation
if (!invoice.items || invoice.items.length === 0) {
errors.push({ field: 'items', message: 'At least one item is required' });
} else {
invoice.items.forEach((item, index) => {
if (!item.description) {
errors.push({ field: `items[${index}].description`, message: 'Item description is required' });
}
if (item.quantity <= 0) {
errors.push({ field: `items[${index}].quantity`, message: 'Item quantity must be greater than 0' });
}
if (item.unitPrice <= 0) {
errors.push({ field: `items[${index}].unitPrice`, message: 'Item unit price must be greater than 0' });
}
});
}
// Totals validation
if (!invoice.totals?.grossAmount || invoice.totals.grossAmount <= 0) {
errors.push({ field: 'totals.grossAmount', message: 'Gross amount must be greater than 0' });
}
// Payment info validation
if (!invoice.paymentInfo?.iban) {
errors.push({ field: 'paymentInfo.iban', message: 'IBAN is required' });
}
if (!invoice.paymentInfo?.bic) {
errors.push({ field: 'paymentInfo.bic', message: 'BIC is required' });
}
// VAT validation for German invoices
if (invoice.sender?.vatId && !invoice.sender.vatId.startsWith('DE')) {
errors.push({ field: 'sender.vatId', message: 'German VAT ID must start with DE' });
}
return {
isValid: errors.length === 0,
errors
};
}
/**
* Validates email format
*/
export function validateEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
/**
* Validates IBAN format (basic validation)
*/
export function validateIBAN(iban: string): boolean {
// Remove spaces and convert to uppercase
const cleanIBAN = iban.replace(/\s/g, '').toUpperCase();
// Basic IBAN format validation
const ibanRegex = /^[A-Z]{2}[0-9]{2}[A-Z0-9]{4}[0-9]{7}([A-Z0-9]?){0,16}$/;
return ibanRegex.test(cleanIBAN);
}
/**
* Validates BIC/SWIFT format
*/
export function validateBIC(bic: string): boolean {
const bicRegex = /^[A-Z]{6}[A-Z2-9][A-NP-Z0-9]([A-Z0-9]{3})?$/;
return bicRegex.test(bic.toUpperCase());
}
/**
* Loads and validates invoice from JSON file
*/
export function loadAndValidateInvoice(jsonPath: string): { invoice: Invoice; validation: ValidationResult } {
if (!fs.existsSync(jsonPath)) {
throw new Error(`Invoice file not found: ${jsonPath}`);
}
const jsonContent = fs.readFileSync(jsonPath, 'utf8');
let invoice: Invoice;
try {
invoice = JSON.parse(jsonContent);
} catch (error) {
throw new Error(`Invalid JSON format: ${error}`);
}
const validation = validateInvoice(invoice);
return { invoice, validation };
}