mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 05:22:02 +00:00
767 lines
19 KiB
TypeScript
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');
|