targo-frontend/src/modules/timesheets/composables/api/use-expense-api.ts

225 lines
7.6 KiB
TypeScript

import { api } from "src/boot/axios";
import { isProxy, toRaw } from "vue";
import { normalizeExpense, validateExpenseUI } from "../../utils/expenses-validators";
import type { ExpenseType } from "../../types/expense.types";
import { ExpensesApiError } from "../../types/expense-validation.interface";
import type {
ExpensePayload,
PayPeriodExpenses,
TimesheetExpense,
UpsertExpenseResult,
UpsertExpensesBody,
UpsertExpensesResponse
} from "../../types/expense.interfaces";
/* eslint-disable */
const toPlain = <T extends object>(obj:T): T => {
const raw = isProxy(obj) ? toRaw(obj) : obj;
if( typeof (globalThis as any).structuredClone === 'function') {
return (globalThis as any).structuredClone(raw);
}
return JSON.parse(JSON.stringify(raw));
};
const normalizePayload = (expense: ExpensePayload): ExpensePayload => {
const exp = normalizeExpense(expense as unknown as TimesheetExpense);
const out: ExpensePayload = {
date: exp.date,
type: exp.type as ExpenseType,
comment: exp.comment || '',
};
if(typeof exp.amount === 'number') out.amount = exp.amount;
if(typeof exp.mileage === 'number') out.mileage = exp.mileage;
return out;
}
//GET by email, year and period no
export const getPayPeriodExpenses = async (
email: string,
pay_year: number,
pay_period_no: number
) : Promise<PayPeriodExpenses> => {
const encoded_email = encodeURIComponent(email);
const encoded_year = encodeURIComponent(String(pay_year));
const encoded_pay_period_no = encodeURIComponent(String(pay_period_no));
try {
const { data } = await api.get<PayPeriodExpenses>(`/expenses/${encoded_email}/${encoded_year}/${encoded_pay_period_no}`);
const items = Array.isArray(data.expenses) ? data.expenses.map(normalizeExpense) : [];
return {
...data,
expenses: items,
};
} catch(err:any) {
const status_code: number = err?.response?.status ?? 500;
const data = err?.response?.data ?? {};
throw new ExpensesApiError({
status_code,
error_code: data.error_code,
message: data.message || data.error || err.message,
context: data.context,
});
}
};
//PUT by email, year and period no
export const putPayPeriodExpenses = async (
email: string,
pay_year: number,
pay_period_no: number,
expenses: TimesheetExpense[]
): Promise<PayPeriodExpenses> => {
const encoded_email = encodeURIComponent(email);
const encoded_year = encodeURIComponent(String(pay_year));
const encoded_pay_period_no = encodeURIComponent(String(pay_period_no));
const plain = Array.isArray(expenses) ? expenses.map(toPlain): [];
const normalized: ExpensePayload[] = plain.map((exp) => {
const norm = normalizeExpense(exp as TimesheetExpense);
validateExpenseUI(norm, 'expense_item');
return normalizePayload(norm as unknown as ExpensePayload);
});
const body: UpsertExpensesBody = {expenses: normalized};
try {
const { data } = await api.put<UpsertExpensesResponse>(
`/expenses/${encoded_email}/${encoded_year}/${encoded_pay_period_no}`,
body,
{ headers: {'Content-Type': 'application/json'}}
);
const items = Array.isArray(data?.data?.expenses)
? data.data.expenses.map(normalizeExpense)
: [];
return {
...(data?.data ?? {
pay_period_no,
pay_year,
employee_email: email,
is_approved: false,
expenses: [],
totals: {amount: 0, mileage: 0},
}),
expenses: items,
};
} catch (err: any) {
const status_code: number = err?.response?.status ?? 500;
const data = err?.response?.data ?? {};
throw new ExpensesApiError({
status_code,
error_code: data.error_code,
message: data.message || data.error || err.message,
context: data.context,
});
}
};
export const postPayPeriodExpenses = async (
email: string,
pay_year: number,
pay_period_no: number,
new_expenses: TimesheetExpense[]
): Promise<PayPeriodExpenses> => {
const encoded_email = encodeURIComponent(email);
const encoded_year = encodeURIComponent(String(pay_year));
const encoded_pp = encodeURIComponent(String(pay_period_no));
const plain = Array.isArray(new_expenses) ? new_expenses.map(toPlain) : [];
const normalized: ExpensePayload[] = plain.map((exp) => {
const norm = normalizeExpense(exp as TimesheetExpense);
validateExpenseUI(norm, 'expense_item');
return normalizePayload(norm as unknown as ExpensePayload);
});
const body: UpsertExpensesBody = { expenses: normalized };
try {
const { data } = await api.post<UpsertExpensesResponse>(
`/expenses/${encoded_email}/${encoded_year}/${encoded_pp}`,
body,
{ headers: { 'content-type': 'application/json' } }
);
const items = Array.isArray(data?.data?.expenses)
? data.data.expenses.map(normalizeExpense)
: [];
return {
...(data?.data ?? {
pay_period_no,
pay_year,
employee_email: email,
is_approved: false,
expenses: [],
totals: { amount: 0, mileage: 0 },
}),
expenses: items,
};
} catch (err: any) {
const status_code: number = err?.response?.status ?? 500;
const data = err?.response?.data ?? {};
throw new ExpensesApiError({
status_code,
error_code: data.error_code,
message: data.message || data.error || err.message,
context: data.context,
});
}
};
const resolveDateISO = (a?: ExpensePayload, b?: ExpensePayload): string => {
const d = a?.date || b?.date;
if(!d) throw new Error('date is required in payload');
return d;
};
const makeBody = (obj: {
old_expense?: ExpensePayload;
new_expense?: ExpensePayload;
}) => obj;
const postUpsert = async (email: string, date: string, body: {
old_expense?: ExpensePayload;
new_expense?: ExpensePayload;
}): Promise<UpsertExpenseResult> => {
try {
const url = `/expenses/upsert/${encodeURIComponent(email)}/${date}`;
const { data } = await api.post<UpsertExpenseResult>(url, body, {
headers: { 'Content-Type': 'application/json'},
});
return data;
} catch(err:any) {
const status_code: number = err?.response?.status ?? 500;
const data = err?.response?.data ?? {};
throw new ExpensesApiError({
status_code,
error_code: data.error_code,
message: data.message || data.error || err.message,
context: data.context,
});
}
};
//create a new expense
export const createExpenseByDate = async ( email: string, payload: ExpensePayload): Promise<UpsertExpenseResult> => {
const new_expense = normalizePayload(payload);
const date = resolveDateISO(new_expense);
return postUpsert(email, date, makeBody({ new_expense: new_expense }));
};
//update an expense
export const updateExpenseByDate = async ( email: string, old_expense: ExpensePayload, new_expense: ExpensePayload): Promise<UpsertExpenseResult> => {
const old_exp = normalizePayload(old_expense);
const new_exp = normalizePayload(new_expense);
const date = resolveDateISO(new_exp, old_exp);
return postUpsert(email, date, makeBody({ old_expense: old_exp,new_expense: new_exp }));
};
//delete an expense
export const deleteExpenseByDate = async (email: string, old_expense: ExpensePayload): Promise<UpsertExpenseResult> => {
const old = normalizePayload(old_expense);
const date = resolveDateISO(undefined, old);
return postUpsert(email, date, makeBody({ old_expense: old }));
};