Files
lti-web-client/src/services/api/expense.ts
T

767 lines
19 KiB
TypeScript

import axios from 'axios';
import { BaseApiService } from '@/services/api/base';
import { BaseApiResponse, GroupedApprovals } from '@/types/api/api-general';
import {
BulkApproveExpensePayload,
CreateExpensePayload,
CreateExpenseRealizationPayload,
Expense,
UpdateExpensePayload,
} from '@/types/api/expense';
import { httpClient } from '@/services/http/client';
import { formatDate } from '@/lib/helper';
export class ExpenseApiService extends BaseApiService<
Expense,
FormData,
FormData
> {
constructor(basePath: string) {
super(basePath);
}
async create(
payload: FormData
): Promise<BaseApiResponse<Expense> | undefined> {
try {
const createExpenseRequestRes = await httpClient<
BaseApiResponse<Expense>
>(this.basePath, {
method: 'POST',
body: payload,
});
return createExpenseRequestRes;
} catch (error) {
if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
return error.response?.data;
}
return undefined;
}
}
async createRealization(
id: number,
payload: FormData
): Promise<BaseApiResponse<Expense> | undefined> {
try {
const createExpenseRealizationRes = await httpClient<
BaseApiResponse<Expense>
>(`${this.basePath}/${id}/realizations`, {
method: 'POST',
body: payload,
});
return createExpenseRealizationRes;
} catch (error) {
if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
return error.response?.data;
}
return undefined;
}
}
async update(
id: number,
payload: FormData,
deletedDocumentIds?: number[]
): Promise<BaseApiResponse<Expense> | undefined> {
try {
for (const deletedDocumentId of deletedDocumentIds ?? []) {
await this.deleteExpenseRequestDocument(id, deletedDocumentId);
}
const updateExpenseRequestRes = await httpClient<
BaseApiResponse<Expense>
>(`${this.basePath}/${id}`, {
method: 'PATCH',
body: payload,
});
return updateExpenseRequestRes;
} catch (error) {
if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
return error.response?.data;
}
return undefined;
}
}
async updateRealization(
id: number,
payload: FormData
): Promise<BaseApiResponse<Expense> | undefined> {
try {
const updateExpenseRealizationRes = await httpClient<
BaseApiResponse<Expense>
>(`${this.basePath}/${id}/realizations`, {
method: 'PATCH',
body: payload,
});
return updateExpenseRealizationRes;
} catch (error) {
if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
return error.response?.data;
}
return undefined;
}
}
async uploadRequestDocuments(
id: number,
files: File[]
): Promise<BaseApiResponse<Expense> | undefined> {
try {
const updateExpenseRequestDocumentsFormData = new FormData();
// files (multiple "documents" keys)
files.forEach((file) => {
updateExpenseRequestDocumentsFormData.append('documents', file);
});
const updateExpenseRealizationRes = await httpClient<
BaseApiResponse<Expense>
>(`${this.basePath}/${id}`, {
method: 'PATCH',
body: updateExpenseRequestDocumentsFormData,
});
return updateExpenseRealizationRes;
} catch (error) {
if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
return error.response?.data;
}
return undefined;
}
}
async uploadRealizationDocuments(
id: number,
files: File[]
): Promise<BaseApiResponse<Expense> | undefined> {
try {
const updateExpenseRealizationDocumentsFormData = new FormData();
// files (multiple "documents" keys)
files.forEach((file) => {
updateExpenseRealizationDocumentsFormData.append('documents', file);
});
const updateExpenseRealizationRes = await httpClient<
BaseApiResponse<Expense>
>(`${this.basePath}/${id}/realizations`, {
method: 'PATCH',
body: updateExpenseRealizationDocumentsFormData,
});
return updateExpenseRealizationRes;
} catch (error) {
if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
return error.response?.data;
}
return undefined;
}
}
async approveHeadArea(
id: number,
notes?: string
): Promise<BaseApiResponse<Expense> | undefined> {
try {
const approveRes = await httpClient<BaseApiResponse<Expense>>(
`${this.basePath}/approvals/head-area`,
{
method: 'POST',
body: {
action: 'APPROVED',
approvable_ids: [id],
notes: notes,
},
}
);
return approveRes;
} catch (error) {
if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
return error.response?.data;
}
return undefined;
}
}
async bulkApproveHeadArea(
ids: number[],
notes?: string
): Promise<BaseApiResponse<Expense> | undefined> {
try {
const bulkApproveRes = await httpClient<BaseApiResponse<Expense>>(
`${this.basePath}/approvals/head-area`,
{
method: 'POST',
body: {
action: 'APPROVED',
approvable_ids: ids,
notes: notes,
},
}
);
return bulkApproveRes;
} catch (error) {
if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
return error.response?.data;
}
return undefined;
}
}
async approveUnitVicePresident(
id: number,
notes?: string
): Promise<BaseApiResponse<Expense> | undefined> {
try {
const approveRes = await httpClient<BaseApiResponse<Expense>>(
`${this.basePath}/approvals/unit-vice-president`,
{
method: 'POST',
body: {
action: 'APPROVED',
approvable_ids: [id],
notes: notes,
},
}
);
return approveRes;
} catch (error) {
if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
return error.response?.data;
}
return undefined;
}
}
async bulkApproveUnitVicePresident(
ids: number[],
notes?: string
): Promise<BaseApiResponse<Expense> | undefined> {
try {
const bulkApproveRes = await httpClient<BaseApiResponse<Expense>>(
`${this.basePath}/approvals/unit-vice-president`,
{
method: 'POST',
body: {
action: 'APPROVED',
approvable_ids: ids,
notes: notes,
},
}
);
return bulkApproveRes;
} catch (error) {
if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
return error.response?.data;
}
return undefined;
}
}
async approveFinance(
id: number,
notes?: string
): Promise<BaseApiResponse<Expense> | undefined> {
try {
const approveRes = await httpClient<BaseApiResponse<Expense>>(
`${this.basePath}/approvals/finance`,
{
method: 'POST',
body: {
action: 'APPROVED',
approvable_ids: [id],
notes: notes,
},
}
);
return approveRes;
} catch (error) {
if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
return error.response?.data;
}
return undefined;
}
}
async bulkApproveFinance(
ids: number[],
notes?: string
): Promise<BaseApiResponse<Expense> | undefined> {
try {
const bulkApproveRes = await httpClient<BaseApiResponse<Expense>>(
`${this.basePath}/approvals/finance`,
{
method: 'POST',
body: {
action: 'APPROVED',
approvable_ids: ids,
notes: notes,
},
}
);
return bulkApproveRes;
} catch (error) {
if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
return error.response?.data;
}
return undefined;
}
}
async bulkApproveToStatus(
payload: BulkApproveExpensePayload
): Promise<BaseApiResponse<Expense | Expense[]> | undefined> {
try {
return await httpClient<BaseApiResponse<Expense | Expense[]>>(
`${this.basePath}/approvals/bulk`,
{
method: 'POST',
body: payload,
}
);
} catch (error) {
if (axios.isAxiosError<BaseApiResponse<Expense | Expense[]>>(error)) {
return error.response?.data;
}
return undefined;
}
}
async bulkApprovals(
ids: number[],
status: BulkApproveExpensePayload['status'] | 'SELESAI',
date?: string,
notes?: string
): Promise<BaseApiResponse<Expense | Expense[]> | undefined> {
if (status === 'SELESAI') {
const responses = await Promise.all(ids.map((id) => this.complete(id)));
const failedResponse = responses.find(
(response) => response?.status !== 'success'
);
if (failedResponse) {
return failedResponse;
}
const completedExpenses = responses.flatMap((response) =>
response?.status === 'success' ? [response.data] : []
);
return {
code: 200,
status: 'success',
message:
completedExpenses.length === 1
? 'Submit expense approval successfully'
: 'Submit expense approvals successfully',
data: completedExpenses,
};
}
return this.bulkApproveToStatus({
approvable_ids: ids,
status,
date: date || undefined,
notes: notes || undefined,
});
}
async rejectHeadArea(
id: number,
notes?: string
): Promise<BaseApiResponse<Expense> | undefined> {
try {
const rejectRes = await httpClient<BaseApiResponse<Expense>>(
`${this.basePath}/approvals/head-area`,
{
method: 'POST',
body: {
action: 'REJECTED',
approvable_ids: [id],
notes: notes,
},
}
);
return rejectRes;
} catch (error) {
if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
return error.response?.data;
}
return undefined;
}
}
async bulkRejectHeadArea(
ids: number[],
notes?: string
): Promise<BaseApiResponse<Expense> | undefined> {
try {
const bulkRejectRes = await httpClient<BaseApiResponse<Expense>>(
`${this.basePath}/approvals/head-area`,
{
method: 'POST',
body: {
action: 'REJECTED',
approvable_ids: ids,
notes: notes,
},
}
);
return bulkRejectRes;
} catch (error) {
if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
return error.response?.data;
}
return undefined;
}
}
async rejectUnitVicePresident(
id: number,
notes?: string
): Promise<BaseApiResponse<Expense> | undefined> {
try {
const rejectRes = await httpClient<BaseApiResponse<Expense>>(
`${this.basePath}/approvals/unit-vice-president`,
{
method: 'POST',
body: {
action: 'REJECTED',
approvable_ids: [id],
notes: notes,
},
}
);
return rejectRes;
} catch (error) {
if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
return error.response?.data;
}
return undefined;
}
}
async bulkRejectUnitVicePresident(
ids: number[],
notes?: string
): Promise<BaseApiResponse<Expense> | undefined> {
try {
const bulkRejectRes = await httpClient<BaseApiResponse<Expense>>(
`${this.basePath}/approvals/unit-vice-president`,
{
method: 'POST',
body: {
action: 'REJECTED',
approvable_ids: ids,
notes: notes,
},
}
);
return bulkRejectRes;
} catch (error) {
if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
return error.response?.data;
}
return undefined;
}
}
async rejectFinance(
id: number,
notes?: string
): Promise<BaseApiResponse<Expense> | undefined> {
try {
const rejectRes = await httpClient<BaseApiResponse<Expense>>(
`${this.basePath}/approvals/finance`,
{
method: 'POST',
body: {
action: 'REJECTED',
approvable_ids: [id],
notes: notes,
},
}
);
return rejectRes;
} catch (error) {
if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
return error.response?.data;
}
return undefined;
}
}
async bulkRejectFinance(
ids: number[],
notes?: string
): Promise<BaseApiResponse<Expense> | undefined> {
try {
const bulkRejectRes = await httpClient<BaseApiResponse<Expense>>(
`${this.basePath}/approvals/finance`,
{
method: 'POST',
body: {
action: 'REJECTED',
approvable_ids: ids,
notes: notes,
},
}
);
return bulkRejectRes;
} catch (error) {
if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
return error.response?.data;
}
return undefined;
}
}
async complete(id: number): Promise<BaseApiResponse<Expense> | undefined> {
try {
const completeRes = await httpClient<BaseApiResponse<Expense>>(
`${this.basePath}/${id}/complete`,
{
method: 'POST',
}
);
return completeRes;
} catch (error) {
if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
return error.response?.data;
}
return undefined;
}
}
async deleteExpenseRequestDocument(
expenseId: number,
documentId: number
): Promise<BaseApiResponse | undefined> {
try {
const deleteExpenseRequestDocument = await httpClient<BaseApiResponse>(
`${this.basePath}/${expenseId}/documents/${documentId}`,
{
method: 'DELETE',
}
);
return deleteExpenseRequestDocument;
} catch (error) {
if (axios.isAxiosError<BaseApiResponse>(error)) {
return error.response?.data;
}
return undefined;
}
}
async deleteExpenseRealizationDocument(
expenseId: number,
documentId: number
): Promise<BaseApiResponse | undefined> {
try {
const deleteExpenseRealizationDocument =
await httpClient<BaseApiResponse>(
`${this.basePath}/${expenseId}/realization-documents/${documentId}`,
{
method: 'DELETE',
}
);
return deleteExpenseRealizationDocument;
} catch (error) {
if (axios.isAxiosError<BaseApiResponse>(error)) {
return error.response?.data;
}
return undefined;
}
}
async getApprovalHistory(
expenseId: number,
group: boolean = true,
page: number = 1,
limit: number = 10
) {
try {
const approvalHistoryRes = await httpClient<GroupedApprovals>(
'/approvals',
{
query: {
module_name: 'EXPENSES',
module_id: expenseId,
group_step_number: group ? 'true' : 'false',
page,
limit,
},
}
);
return approvalHistoryRes;
} catch (error) {
if (axios.isAxiosError<GroupedApprovals>(error)) {
return error.response?.data;
}
return undefined;
}
}
convertExpenseRequestPayloadToFormData = (payload: CreateExpensePayload) => {
const formData = new FormData();
formData.append('category', payload.category);
formData.append('location_id', String(payload.location_id));
formData.append('transaction_date', payload.transaction_date);
formData.append('supplier_id', String(payload.supplier_id));
// files (multiple "documents" keys)
payload.documents.forEach((file) => {
formData.append('documents', file);
});
formData.append(
'expense_nonstocks',
JSON.stringify(payload.expense_nonstocks)
);
return formData;
};
convertExpenseRequestUpdatePayloadToFormData = (
payload: UpdateExpensePayload
) => {
const formData = new FormData();
formData.append('category', payload.category);
formData.append('location_id', String(payload.location_id));
formData.append('transaction_date', payload.transaction_date);
formData.append('supplier_id', String(payload.supplier_id));
// files (multiple "documents" keys)
payload.documents.forEach((file) => {
formData.append('documents', file);
});
formData.append(
'expense_nonstocks',
JSON.stringify(payload.expense_nonstocks)
);
return formData;
};
convertExpenseRealizationPayloadToFormData = (
payload: CreateExpenseRealizationPayload
) => {
const formData = new FormData();
formData.append('realization_date', payload.realization_date);
// files (multiple "documents" keys)
payload.documents.forEach((file) => {
formData.append('documents', file);
});
formData.append('realizations', JSON.stringify(payload.realizations));
return formData;
};
async exportToExcel(initialQueryString: string) {
const params = new URLSearchParams(initialQueryString);
params.set('export', 'excel');
params.set('type', 'all');
params.set('page', '1');
params.set('limit', '99999999999');
const queryString = `?${params.toString()}`;
const res = await httpClient<Blob>(`${this.basePath}${queryString}`, {
method: 'GET',
responseType: 'blob',
});
const url = window.URL.createObjectURL(new Blob([res]));
const link = document.createElement('a');
link.href = url;
const fileName = `BOP-${formatDate(Date.now(), 'DD-MM-YYYY')}.xlsx`;
link.setAttribute('download', fileName);
document.body.appendChild(link);
link.click();
link.remove();
}
async exportInputProgressToExcel(startDate: string, endDate: string) {
const params = new URLSearchParams();
params.set('export', 'excel');
params.set('type', 'progress');
params.set('start_date', formatDate(startDate, 'YYYY-MM-DD'));
params.set('end_date', formatDate(endDate, 'YYYY-MM-DD'));
const queryString = `?${params.toString()}`;
const res = await httpClient<Blob>(`${this.basePath}${queryString}`, {
method: 'GET',
responseType: 'blob',
});
const url = window.URL.createObjectURL(new Blob([res]));
const link = document.createElement('a');
link.href = url;
const fileName = `input-progres-BOP-${formatDate(startDate, 'DD-MM-YYYY')}-ke-${formatDate(endDate, 'DD-MM-YYYY')}.xlsx`;
link.setAttribute('download', fileName);
document.body.appendChild(link);
link.click();
link.remove();
}
}
export const ExpenseApi = new ExpenseApiService('/expenses');