UNPKG

@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
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 }; }