144 lines
4.1 KiB
TypeScript
144 lines
4.1 KiB
TypeScript
/**
|
|
* 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: unknown[];
|
|
invoices: unknown[];
|
|
stats: unknown;
|
|
}) {
|
|
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: unknown) {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const d = data as any;
|
|
this.id = d.id;
|
|
this.type = d.type;
|
|
this.last4 = d.last4;
|
|
this.brand = d.brand;
|
|
this.isDefault = d.isDefault;
|
|
this.expiryMonth = d.expiryMonth;
|
|
this.expiryYear = d.expiryYear;
|
|
this.bankName = d.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: unknown) {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const d = data as any;
|
|
this.id = d.id;
|
|
this.invoiceNumber = d.invoiceNumber;
|
|
this.date = new Date(d.date);
|
|
this.dueDate = new Date(d.dueDate);
|
|
this.amount = d.amount;
|
|
this.vatAmount = d.vatAmount;
|
|
this.totalAmount = d.totalAmount;
|
|
this.status = d.status;
|
|
this.description = d.description;
|
|
this.sponsorshipType = d.sponsorshipType;
|
|
this.pdfUrl = d.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: unknown) {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const d = data as any;
|
|
this.totalSpent = d.totalSpent;
|
|
this.pendingAmount = d.pendingAmount;
|
|
this.nextPaymentDate = new Date(d.nextPaymentDate);
|
|
this.nextPaymentAmount = d.nextPaymentAmount;
|
|
this.activeSponsorships = d.activeSponsorships;
|
|
this.averageMonthlySpend = d.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' });
|
|
}
|
|
} |