targo-frontend/src/modules/timesheets/models/expense-validation.models.ts
Nicolas Drolet 33061ef2ab BREAKING(timesheet): Overhaul timesheet UI, refactor to increase efficiency, complete OIDC login
Change timesheet UI to better fit current app model and avoid adding extra clicks and interactions to add new shifts and expenses. Also refactoring calls to backend to be more efficient and use recently-finalized OIDC implementation and integration.
2025-10-22 08:59:40 -04:00

79 lines
2.7 KiB
TypeScript

import { type Expense, EXPENSE_TYPE, type ExpenseType } from "src/modules/timesheets/models/expense.models";
import type { Normalizer } from "src/utils/normalize-object";
export interface ApiErrorPayload {
status_code: number;
error_code?: string;
message?: string;
context?: Record<string, unknown>;
};
export abstract class ApiError extends Error {
status_code: number;
error_code?: string;
context?: Record<string, unknown>;
constructor(payload: ApiErrorPayload, defaultMessage: string) {
super(payload.message || defaultMessage);
this.status_code = payload.status_code;
this.error_code = payload.error_code ?? "unknown";
this.context = payload.context ?? {'unknown': 'unknown error has occured', };
}
};
export class GenericApiError extends ApiError {
constructor(payload: ApiErrorPayload) {
super(payload, 'Encountered an error processing request');
this.name = 'GenericApiError';
}
};
export class ExpensesValidationError extends ApiError {
constructor(payload: ApiErrorPayload) {
super(payload, 'Invalid expense payload');
this.name = 'ExpensesValidationError';
}
};
export class ExpensesApiError extends ApiError {
constructor(payload: ApiErrorPayload) {
super(payload, 'Request failed');
this.name = 'ExpensesApiError';
}
};
export const expense_validation_schema: Normalizer<Expense> = {
id: v => typeof v === 'number' ? v : -1,
date: v => typeof v === 'string' ? v.trim() : '1970-01-01',
type: v => EXPENSE_TYPE.includes(v as ExpenseType) ? v as ExpenseType : "EXPENSES",
amount: v => typeof v === "number" ? v : -1,
mileage: v => typeof v === "number" ? v : undefined,
comment: v => typeof v === 'string' ? v.trim() : '',
supervisor_comment: v => typeof v === 'string' ? v.trim() : '',
is_approved: v => !!v,
};
export function toExpensesError(err: unknown): ExpensesValidationError | ExpensesApiError {
if (err instanceof ExpensesValidationError || err instanceof ExpensesApiError) {
return err;
}
if (typeof err === 'object' && err !== null && 'status_code' in err) {
const payload = err as ApiErrorPayload;
// Don't know how to differentiate both types of errors, can be updated here
if (payload.error_code?.startsWith('API_')) {
return new ExpensesApiError(payload);
}
return new ExpensesValidationError(payload);
}
// Fallback with ValidationError as default
return new ExpensesValidationError({
status_code: 500,
message: err instanceof Error ? err.message : 'Unknown error',
context: { original: err }
});
}