225 lines
7.6 KiB
TypeScript
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 }));
|
|
}; |