/** * Billing View Model * * View model for sponsor billing data with UI-specific transformations. */ export class BillingViewModel { paymentMethods: PaymentMethodViewModel[]; invoices: InvoiceViewModel[]; stats: BillingStatsViewModel; constructor(data: { paymentMethods: any[]; invoices: any[]; stats: any; }) { this.paymentMethods = data.paymentMethods.map(pm => new PaymentMethodViewModel(pm)); this.invoices = data.invoices.map(inv => new InvoiceViewModel(inv)); this.stats = new BillingStatsViewModel(data.stats); } } export class PaymentMethodViewModel { id: string; type: 'card' | 'bank' | 'sepa'; last4: string; brand?: string; isDefault: boolean; expiryMonth?: number; expiryYear?: number; bankName?: string; constructor(data: any) { this.id = data.id; this.type = data.type; this.last4 = data.last4; this.brand = data.brand; this.isDefault = data.isDefault; this.expiryMonth = data.expiryMonth; this.expiryYear = data.expiryYear; this.bankName = data.bankName; } get displayLabel(): string { if (this.type === 'sepa' && this.bankName) { return `${this.bankName} •••• ${this.last4}`; } return `${this.brand} •••• ${this.last4}`; } get expiryDisplay(): string | null { if (this.expiryMonth && this.expiryYear) { return `${String(this.expiryMonth).padStart(2, '0')}/${this.expiryYear}`; } return null; } } export class InvoiceViewModel { id: string; invoiceNumber: string; date: Date; dueDate: Date; amount: number; vatAmount: number; totalAmount: number; status: 'paid' | 'pending' | 'overdue' | 'failed'; description: string; sponsorshipType: 'league' | 'team' | 'driver' | 'race' | 'platform'; pdfUrl: string; constructor(data: any) { this.id = data.id; this.invoiceNumber = data.invoiceNumber; this.date = new Date(data.date); this.dueDate = new Date(data.dueDate); this.amount = data.amount; this.vatAmount = data.vatAmount; this.totalAmount = data.totalAmount; this.status = data.status; this.description = data.description; this.sponsorshipType = data.sponsorshipType; this.pdfUrl = data.pdfUrl; } get formattedTotalAmount(): string { return `€${this.totalAmount.toLocaleString('de-DE', { minimumFractionDigits: 2 })}`; } get formattedVatAmount(): string { return `€${this.vatAmount.toLocaleString('de-DE', { minimumFractionDigits: 2 })}`; } get formattedDate(): string { return this.date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); } get isOverdue(): boolean { return this.status === 'overdue' || (this.status === 'pending' && new Date() > this.dueDate); } } export class BillingStatsViewModel { totalSpent: number; pendingAmount: number; nextPaymentDate: Date; nextPaymentAmount: number; activeSponsorships: number; averageMonthlySpend: number; constructor(data: any) { this.totalSpent = data.totalSpent; this.pendingAmount = data.pendingAmount; this.nextPaymentDate = new Date(data.nextPaymentDate); this.nextPaymentAmount = data.nextPaymentAmount; this.activeSponsorships = data.activeSponsorships; this.averageMonthlySpend = data.averageMonthlySpend; } get formattedTotalSpent(): string { return `€${this.totalSpent.toLocaleString('de-DE')}`; } get formattedPendingAmount(): string { return `€${this.pendingAmount.toLocaleString('de-DE', { minimumFractionDigits: 2 })}`; } get formattedNextPaymentAmount(): string { return `€${this.nextPaymentAmount.toLocaleString('de-DE', { minimumFractionDigits: 2 })}`; } get formattedAverageMonthlySpend(): string { return `€${this.averageMonthlySpend.toLocaleString('de-DE')}`; } get formattedNextPaymentDate(): string { return this.nextPaymentDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); } }