| | |
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | export type RecurringInvoiceErrorCode = |
| | | "CUSTOMER_NO_EMAIL" |
| | | "CUSTOMER_NOT_FOUND" |
| | | "CUSTOMER_DELETED" |
| | | "TEMPLATE_INVALID" |
| | | "INVOICE_EXISTS" |
| | | "DATABASE_ERROR" |
| | | "GENERATION_FAILED"; |
| |
|
| | |
| | |
| | |
| | |
| | export class RecurringInvoiceError extends Error { |
| | public readonly code: RecurringInvoiceErrorCode; |
| | public readonly recurringId: string; |
| | public readonly teamId?: string; |
| | public readonly customerId?: string; |
| |
|
| | constructor( |
| | code: RecurringInvoiceErrorCode, |
| | recurringId: string, |
| | message: string, |
| | options?: { |
| | teamId?: string; |
| | customerId?: string; |
| | cause?: Error; |
| | }, |
| | ) { |
| | super(message, { cause: options?.cause }); |
| | this.name = "RecurringInvoiceError"; |
| | this.code = code; |
| | this.recurringId = recurringId; |
| | this.teamId = options?.teamId; |
| | this.customerId = options?.customerId; |
| | } |
| |
|
| | |
| | |
| | |
| | get isRecoverable(): boolean { |
| | return this.code === "DATABASE_ERROR" || this.code === "GENERATION_FAILED"; |
| | } |
| |
|
| | |
| | |
| | |
| | get requiresUserAction(): boolean { |
| | return ( |
| | this.code === "CUSTOMER_NO_EMAIL" || |
| | this.code === "CUSTOMER_NOT_FOUND" || |
| | this.code === "CUSTOMER_DELETED" || |
| | this.code === "TEMPLATE_INVALID" |
| | ); |
| | } |
| |
|
| | |
| | |
| | |
| | getUserMessage(): string { |
| | switch (this.code) { |
| | case "CUSTOMER_NO_EMAIL": |
| | return "The customer associated with this recurring invoice series does not have an email address. Please update the customer profile."; |
| | case "CUSTOMER_NOT_FOUND": |
| | return "The customer associated with this recurring invoice series was not found. They may have been deleted."; |
| | case "CUSTOMER_DELETED": |
| | return "The customer associated with this recurring invoice series has been deleted. Please assign a new customer or cancel the series."; |
| | case "TEMPLATE_INVALID": |
| | return "The invoice template data is invalid or corrupted. Please recreate the recurring invoice series."; |
| | case "INVOICE_EXISTS": |
| | return "An invoice was already generated for this period. Skipping duplicate generation."; |
| | case "DATABASE_ERROR": |
| | return "A database error occurred. The system will retry automatically."; |
| | case "GENERATION_FAILED": |
| | return "Invoice generation failed. The system will retry automatically."; |
| | default: |
| | return this.message; |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | toJSON(): Record<string, unknown> { |
| | return { |
| | name: this.name, |
| | code: this.code, |
| | message: this.message, |
| | recurringId: this.recurringId, |
| | teamId: this.teamId, |
| | customerId: this.customerId, |
| | isRecoverable: this.isRecoverable, |
| | requiresUserAction: this.requiresUserAction, |
| | stack: this.stack, |
| | }; |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | export const RecurringInvoiceErrors = { |
| | customerNoEmail( |
| | recurringId: string, |
| | customerName: string | null, |
| | teamId?: string, |
| | ): RecurringInvoiceError { |
| | return new RecurringInvoiceError( |
| | "CUSTOMER_NO_EMAIL", |
| | recurringId, |
| | `Cannot generate recurring invoice: Customer ${customerName || "unknown"} has no email address. Please update the customer profile.`, |
| | { teamId }, |
| | ); |
| | }, |
| |
|
| | customerNotFound( |
| | recurringId: string, |
| | customerId: string, |
| | teamId?: string, |
| | ): RecurringInvoiceError { |
| | return new RecurringInvoiceError( |
| | "CUSTOMER_NOT_FOUND", |
| | recurringId, |
| | `Cannot generate recurring invoice: Customer ${customerId} not found. They may have been deleted.`, |
| | { teamId, customerId }, |
| | ); |
| | }, |
| |
|
| | customerDeleted( |
| | recurringId: string, |
| | customerName: string | null, |
| | teamId?: string, |
| | ): RecurringInvoiceError { |
| | return new RecurringInvoiceError( |
| | "CUSTOMER_DELETED", |
| | recurringId, |
| | `Cannot generate recurring invoice: The customer${customerName ? ` "${customerName}"` : ""} has been deleted. Please assign a new customer or cancel the series.`, |
| | { teamId }, |
| | ); |
| | }, |
| |
|
| | templateInvalid( |
| | recurringId: string, |
| | reason: string, |
| | teamId?: string, |
| | ): RecurringInvoiceError { |
| | return new RecurringInvoiceError( |
| | "TEMPLATE_INVALID", |
| | recurringId, |
| | `Recurring series has invalid template data: ${reason}`, |
| | { teamId }, |
| | ); |
| | }, |
| |
|
| | invoiceExists( |
| | recurringId: string, |
| | invoiceId: string, |
| | teamId?: string, |
| | ): RecurringInvoiceError { |
| | return new RecurringInvoiceError( |
| | "INVOICE_EXISTS", |
| | recurringId, |
| | `Invoice ${invoiceId} already exists for this recurring series period`, |
| | { teamId }, |
| | ); |
| | }, |
| |
|
| | databaseError( |
| | recurringId: string, |
| | cause: Error, |
| | teamId?: string, |
| | ): RecurringInvoiceError { |
| | return new RecurringInvoiceError( |
| | "DATABASE_ERROR", |
| | recurringId, |
| | `Database error during recurring invoice generation: ${cause.message}`, |
| | { teamId, cause }, |
| | ); |
| | }, |
| |
|
| | generationFailed( |
| | recurringId: string, |
| | cause: Error, |
| | teamId?: string, |
| | ): RecurringInvoiceError { |
| | return new RecurringInvoiceError( |
| | "GENERATION_FAILED", |
| | recurringId, |
| | `Failed to generate recurring invoice: ${cause.message}`, |
| | { teamId, cause }, |
| | ); |
| | }, |
| | }; |
| |
|