File size: 5,697 Bytes
c09f67c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | /**
* Custom error classes for recurring invoice processing.
* Provides typed errors with codes for better error handling and debugging.
*/
/**
* Error codes for recurring invoice failures
*/
export type RecurringInvoiceErrorCode =
| "CUSTOMER_NO_EMAIL"
| "CUSTOMER_NOT_FOUND"
| "CUSTOMER_DELETED"
| "TEMPLATE_INVALID"
| "INVOICE_EXISTS"
| "DATABASE_ERROR"
| "GENERATION_FAILED";
/**
* Typed error class for recurring invoice failures.
* Provides structured error information for logging and recovery.
*/
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;
}
/**
* Whether this error is recoverable (retrying might help)
*/
get isRecoverable(): boolean {
return this.code === "DATABASE_ERROR" || this.code === "GENERATION_FAILED";
}
/**
* Whether this error requires user intervention
*/
get requiresUserAction(): boolean {
return (
this.code === "CUSTOMER_NO_EMAIL" ||
this.code === "CUSTOMER_NOT_FOUND" ||
this.code === "CUSTOMER_DELETED" ||
this.code === "TEMPLATE_INVALID"
);
}
/**
* Get a user-friendly description of the error
*/
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;
}
}
/**
* Convert to a plain object for logging
*/
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,
};
}
}
/**
* Factory functions for creating specific error types
*/
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 },
);
},
};
|