From 334202569c4005bfca55176d5f90fffc9b53ba8e Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Tue, 25 Nov 2025 09:24:12 +0700 Subject: [PATCH] chore(FE-199,207): integrate ExpenseApiService to API --- src/services/api/expense.ts | 1514 +++++++++++------------------------ 1 file changed, 477 insertions(+), 1037 deletions(-) diff --git a/src/services/api/expense.ts b/src/services/api/expense.ts index 24931316..337730e6 100644 --- a/src/services/api/expense.ts +++ b/src/services/api/expense.ts @@ -1,738 +1,15 @@ import axios from 'axios'; import { sleep } from '@/lib/helper'; import { BaseApiService } from '@/services/api/base'; -import { BaseApiResponse, CreatedUser } from '@/types/api/api-general'; -import { CreateExpensePayload, Expense } from '@/types/api/expense'; +import { BaseApiResponse, GroupedApprovals } from '@/types/api/api-general'; +import { + CreateExpensePayload, + CreateExpenseRealizationPayload, + Expense, + UpdateExpensePayload, +} from '@/types/api/expense'; import { httpClient } from '@/services/http/client'; -import { BaseArea } from '@/types/api/master-data/area'; -import { BaseLocation, Location } from '@/types/api/master-data/location'; -import { Kandang } from '@/types/api/master-data/kandang'; -import { BaseSupplier, Supplier } from '@/types/api/master-data/supplier'; -import { Nonstock } from '@/types/api/master-data/nonstock'; -import { resourceUsage } from 'process'; - -// Shared base objects -const adminUser: CreatedUser = { - id: 1, - id_user: 1, - email: 'admin@example.com', - name: 'Admin User', -}; - -const managerAreaUser: CreatedUser = { - id: 200, - id_user: 200, - email: 'manager.area@example.com', - name: 'Manager Area', -}; - -const headFinanceUser: CreatedUser = { - id: 300, - id_user: 300, - email: 'head.finance@example.com', - name: 'Head Finance', -}; - -const financeStaffUser: CreatedUser = { - id: 400, - id_user: 400, - email: 'finance.staff@example.com', - name: 'Finance Staff', -}; - -const auditUser: CreatedUser = { - id: 500, - id_user: 500, - email: 'internal.audit@example.com', - name: 'Internal Audit', -}; - -const areaUtara: BaseArea = { - id: 1, - name: 'Area Utara', -}; - -const areaSelatan: BaseArea = { - id: 2, - name: 'Area Selatan', -}; - -const baseLocationA: BaseLocation = { - id: 1, - name: 'Singaparna', - address: 'Jl. Kebun Raya 12', - area: areaUtara, -}; - -const baseLocationB: BaseLocation = { - id: 2, - name: 'Cikaum', - address: 'Jl. Melati 20', - area: areaSelatan, -}; - -const locationA: Location = { - ...baseLocationA, - created_user: adminUser, - created_at: '2025-01-01T00:00:00Z', - updated_at: '2025-01-01T00:00:00Z', -}; - -const locationB: Location = { - ...baseLocationB, - created_user: adminUser, - created_at: '2025-01-05T00:00:00Z', - updated_at: '2025-01-05T00:00:00Z', -}; - -const kandangA1: Kandang = { - id: 1, - name: 'Singaparna 1', - status: 'ACTIVE', - capacity: 5000, - location: baseLocationA, - pic: { - id: 101, - id_user: 101, - email: 'abkA1@example.com', - name: 'ABK A1', - }, - created_user: adminUser, - created_at: '2024-12-10T00:00:00Z', - updated_at: '2024-12-10T00:00:00Z', -}; - -const kandangA2: Kandang = { - id: 2, - name: 'Singaparna 2', - status: 'ACTIVE', - capacity: 4500, - location: baseLocationA, - pic: { - id: 102, - id_user: 102, - email: 'abkA2@example.com', - name: 'ABK A2', - }, - created_user: adminUser, - created_at: '2024-12-12T00:00:00Z', - updated_at: '2024-12-12T00:00:00Z', -}; - -const kandangB1: Kandang = { - id: 21, - name: 'Kandang B1', - status: 'ACTIVE', - capacity: 3800, - location: baseLocationB, - pic: { - id: 201, - id_user: 201, - email: 'abkB1@example.com', - name: 'ABK B1', - }, - created_user: adminUser, - created_at: '2024-12-15T00:00:00Z', - updated_at: '2024-12-15T00:00:00Z', -}; - -const baseSupplierPakan: BaseSupplier = { - id: 1, - name: 'PT CHAROEN POKPHAND INDONESIA Tbk', - alias: 'PPJ', - pic: 'Budi', - type: 'Pakan', - category: 'PAKAN', - hatchery: '-', - phone: '08121234567', - email: 'pakan@example.com', - address: 'Jl. Raya Pakan 88', - npwp: '1234567890', - account_number: '111-222-333', - due_date: 30, - balance: 5000000, -}; - -const baseSupplierObat: BaseSupplier = { - id: 502, - name: 'CV Obat Sehat', - alias: 'COS', - pic: 'Susi', - type: 'Obat', - category: 'OBAT', - hatchery: '-', - phone: '085612345678', - email: 'cos@example.com', - address: 'Jl. Obat Raya 10', - npwp: '987654321', - account_number: '222-333-444', - due_date: 14, -}; - -const supplierPakan: Supplier = { - ...baseSupplierPakan, - created_user: adminUser, - created_at: '2025-01-01T00:00:00Z', - updated_at: '2025-01-01T00:00:00Z', -}; - -const supplierObat: Supplier = { - ...baseSupplierObat, - created_user: adminUser, - created_at: '2025-01-02T00:00:00Z', - updated_at: '2025-01-02T00:00:00Z', -}; - -const nonstockPakanStarter: Nonstock = { - id: 3001, - name: 'Pakan Ayam Starter', - uom_id: 1, - uom: { id: 1, name: 'KG' }, - suppliers: [baseSupplierPakan], - flags: ['PAKAN', 'STARTER'], - created_user: adminUser, - created_at: '2025-01-10T00:00:00Z', - updated_at: '2025-01-10T00:00:00Z', -}; - -const nonstockPakanFinisher: Nonstock = { - id: 3002, - name: 'Pakan Ayam Finisher', - uom_id: 1, - uom: { id: 1, name: 'KG' }, - suppliers: [baseSupplierPakan], - flags: ['PAKAN', 'FINISHER'], - created_user: adminUser, - created_at: '2025-01-11T00:00:00Z', - updated_at: '2025-01-11T00:00:00Z', -}; - -const nonstockObat: Nonstock = { - id: 3101, - name: 'Obat Antibiotik A', - uom_id: 2, - uom: { id: 2, name: 'BOTOL' }, - suppliers: [baseSupplierObat], - flags: ['OBAT'], - created_user: adminUser, - created_at: '2025-01-12T00:00:00Z', - updated_at: '2025-01-12T00:00:00Z', -}; - -const nonstockVitamin: Nonstock = { - id: 3102, - name: 'Vitamin Ayam B-Complex', - uom_id: 2, - uom: { id: 2, name: 'BOTOL' }, - suppliers: [baseSupplierObat], - flags: ['VITAMIN'], - created_user: adminUser, - created_at: '2025-01-13T00:00:00Z', - updated_at: '2025-01-13T00:00:00Z', -}; - -export const DUMMY_EXPENSE: Expense[] = [ - // STEP 1 - Pengajuan (OK) - { - id: 1, - reference_number: 'REF-EXP-0001', - po_number: 'PO-STEP1-OK', - created_user: adminUser, - created_at: '2025-02-10T08:00:00Z', - updated_at: '2025-02-10T08:00:00Z', - - location: locationA, - transaction_date: '2025-02-10', - - kandangs: [kandangA1, kandangA2], - vendor: supplierPakan, - request_documents: [ - { - name: 'pengajuan_step1_ok.pdf', - url: 'https://example.com/pengajuan_step1_ok.pdf', - }, - ], - kandang_expenses: [ - { - kandang: kandangA1, - expenses: [ - { - nonstock: nonstockPakanStarter, - total_quantity: 100, - total_expense: 1500000, - }, - ], - }, - { - kandang: kandangA2, - expenses: [ - { - nonstock: nonstockPakanFinisher, - total_quantity: 80, - total_expense: 1200000, - }, - ], - }, - ], - nominal: 2700000, - paid: 0, - remaining_cost: 2700000, - - approval: { - step_number: 1, - step_name: 'Pengajuan', - action: 'SUBMITTED', - notes: 'Pengajuan pakan untuk kandang A1 dan A2.', - action_by: adminUser, - action_at: '2025-02-10T08:05:00Z', - }, - }, - - // STEP 1 - Pengajuan (REJECTED) - { - id: 2, - reference_number: 'REF-EXP-0002', - po_number: 'PO-STEP1-REJECT', - created_user: adminUser, - created_at: '2025-02-11T09:00:00Z', - updated_at: '2025-02-11T09:15:00Z', - - location: locationA, - transaction_date: '2025-02-11', - - kandangs: [kandangA1], - vendor: supplierPakan, - request_documents: [], - kandang_expenses: [ - { - kandang: kandangA1, - expenses: [ - { - nonstock: nonstockPakanFinisher, - total_quantity: 300, - total_expense: 4500000, - }, - ], - }, - ], - nominal: 4500000, - paid: 0, - remaining_cost: 4500000, - - approval: { - step_number: 1, - step_name: 'Pengajuan', - action: 'REJECTED', - notes: 'Jumlah terlalu besar untuk pengajuan awal.', - action_by: managerAreaUser, - action_at: '2025-02-11T09:15:00Z', - }, - }, - - // STEP 2 - Approval Manager Area (APPROVED) - { - id: 3, - reference_number: 'REF-EXP-0003', - po_number: 'PO-STEP2-OK', - created_user: adminUser, - created_at: '2025-02-12T07:30:00Z', - updated_at: '2025-02-12T08:30:00Z', - - location: locationA, - transaction_date: '2025-02-12', - - kandangs: [kandangA1, kandangA2], - vendor: supplierPakan, - request_documents: [ - { - name: 'pengajuan_step2_ok.pdf', - url: 'https://example.com/pengajuan_step2_ok.pdf', - }, - ], - kandang_expenses: [ - { - kandang: kandangA1, - expenses: [ - { - nonstock: nonstockPakanStarter, - total_quantity: 120, - total_expense: 1800000, - notes: - 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Non eveniet quos aspernatur magnam mollitia consequatur dolore natus amet libero enim?', - }, - { - nonstock: nonstockPakanFinisher, - total_quantity: 50, - total_expense: 750000, - }, - ], - }, - { - kandang: kandangA2, - expenses: [ - { - nonstock: nonstockPakanStarter, - total_quantity: 60, - total_expense: 900000, - }, - ], - }, - ], - nominal: 3450000, - paid: 0, - remaining_cost: 3450000, - - approval: { - step_number: 2, - step_name: 'Approval Manager Area', - action: 'APPROVED', - notes: 'Disetujui, kebutuhan pakan sesuai rencana.', - action_by: managerAreaUser, - action_at: '2025-02-12T08:30:00Z', - }, - }, - - // STEP 2 - Approval Manager Area (REJECTED) - { - id: 4, - reference_number: 'REF-EXP-0004', - created_user: adminUser, - created_at: '2025-02-13T10:00:00Z', - updated_at: '2025-02-13T10:20:00Z', - - location: locationB, - transaction_date: '2025-02-13', - - kandangs: [kandangB1], - vendor: supplierPakan, - request_documents: [], - kandang_expenses: [ - { - kandang: kandangB1, - expenses: [ - { - nonstock: nonstockPakanFinisher, - total_quantity: 400, - total_expense: 6000000, - }, - ], - }, - ], - nominal: 6000000, - paid: 0, - remaining_cost: 6000000, - - approval: { - step_number: 2, - step_name: 'Approval Manager Area', - action: 'REJECTED', - notes: 'Tidak sesuai rencana kebutuhan area Selatan.', - action_by: managerAreaUser, - action_at: '2025-02-13T10:20:00Z', - }, - }, - - // STEP 3 - Approval Finance (APPROVED) - { - id: 5, - reference_number: 'REF-EXP-0005', - po_number: 'PO-STEP3-OK', - created_user: adminUser, - created_at: '2025-02-14T09:00:00Z', - updated_at: '2025-02-14T09:30:00Z', - - location: locationB, - transaction_date: '2025-02-14', - - kandangs: [kandangB1], - vendor: supplierObat, - request_documents: [ - { - name: 'pengajuan_step3_ok.pdf', - url: 'https://example.com/pengajuan_step3_ok.pdf', - }, - ], - kandang_expenses: [ - { - kandang: kandangB1, - expenses: [ - { - nonstock: nonstockObat, - total_quantity: 10, - total_expense: 700000, - }, - { - nonstock: nonstockVitamin, - total_quantity: 5, - total_expense: 250000, - }, - ], - }, - ], - nominal: 950000, - paid: 0, - remaining_cost: 950000, - - approval: { - step_number: 3, - step_name: 'Approval Finance', - action: 'APPROVED', - notes: 'Budget tersedia untuk obat & vitamin.', - action_by: headFinanceUser, - action_at: '2025-02-14T09:30:00Z', - }, - }, - - // STEP 3 - Approval Finance (REJECTED) - { - id: 6, - reference_number: 'REF-EXP-0006', - created_user: adminUser, - created_at: '2025-02-15T11:00:00Z', - updated_at: '2025-02-15T11:30:00Z', - - location: locationB, - transaction_date: '2025-02-15', - - kandangs: [kandangB1], - vendor: supplierObat, - request_documents: [], - kandang_expenses: [ - { - kandang: kandangB1, - expenses: [ - { - nonstock: nonstockObat, - total_quantity: 60, - total_expense: 4200000, - }, - ], - }, - ], - nominal: 4200000, - paid: 0, - remaining_cost: 4200000, - - approval: { - step_number: 3, - step_name: 'Approval Finance', - action: 'REJECTED', - notes: 'Melebihi plafon budget obat bulan ini.', - action_by: headFinanceUser, - action_at: '2025-02-15T11:30:00Z', - }, - }, - - // STEP 4 - Realisasi (IN_PROGRESS) - { - id: 7, - reference_number: 'REF-EXP-0007', - po_number: 'PO-STEP4-OK', - created_user: adminUser, - created_at: '2025-02-16T08:00:00Z', - updated_at: '2025-02-16T12:00:00Z', - - location: locationA, - transaction_date: '2025-02-16', - realization_date: '2025-02-17', - - kandangs: [kandangA1, kandangA2], - vendor: supplierPakan, - request_documents: [ - { - name: 'do_step4_ok.pdf', - url: 'https://example.com/do_step4_ok.pdf', - }, - ], - kandang_expenses: [ - { - kandang: kandangA1, - expenses: [ - { - nonstock: nonstockPakanStarter, - total_quantity: 70, - total_expense: 1050000, - }, - ], - }, - { - kandang: kandangA2, - expenses: [ - { - nonstock: nonstockPakanFinisher, - total_quantity: 40, - total_expense: 600000, - }, - ], - }, - ], - nominal: 1650000, - paid: 500000, - remaining_cost: 1150000, - - approval: { - step_number: 4, - step_name: 'Realisasi', - action: 'IN_PROGRESS', - notes: 'Barang diterima, pembayaran sebagian.', - action_by: financeStaffUser, - action_at: '2025-02-16T12:00:00Z', - }, - }, - - // STEP 4 - Realisasi (REJECTED) - { - id: 8, - reference_number: 'REF-EXP-0008', - created_user: adminUser, - created_at: '2025-02-17T09:00:00Z', - updated_at: '2025-02-17T09:45:00Z', - - location: locationA, - transaction_date: '2025-02-17', - - kandangs: [kandangA1], - vendor: supplierPakan, - request_documents: [ - { - name: 'invoice_step4_reject.pdf', - url: 'https://example.com/invoice_step4_reject.pdf', - }, - ], - kandang_expenses: [ - { - kandang: kandangA1, - expenses: [ - { - nonstock: nonstockPakanStarter, - total_quantity: 50, - total_expense: 750000, - }, - ], - }, - ], - nominal: 750000, - paid: 0, - remaining_cost: 750000, - - approval: { - step_number: 4, - step_name: 'Realisasi', - action: 'REJECTED', - notes: 'Dokumen realisasi tidak sesuai PO.', - action_by: financeStaffUser, - action_at: '2025-02-17T09:45:00Z', - }, - }, - - // STEP 5 - Selesai (DONE) - { - id: 9, - reference_number: 'REF-EXP-0009', - po_number: 'PO-STEP5-OK', - created_user: adminUser, - created_at: '2025-02-18T08:00:00Z', - updated_at: '2025-02-20T15:00:00Z', - - location: locationB, - transaction_date: '2025-02-18', - realization_date: '2025-02-19', - - kandangs: [kandangB1], - vendor: supplierObat, - request_documents: [ - { - name: 'invoice_step5_ok.pdf', - url: 'https://example.com/invoice_step5_ok.pdf', - }, - { - name: 'bukti_transfer_step5_ok.pdf', - url: 'https://example.com/bukti_transfer_step5_ok.pdf', - }, - ], - kandang_expenses: [ - { - kandang: kandangB1, - expenses: [ - { - nonstock: nonstockObat, - total_quantity: 20, - total_expense: 1400000, - }, - ], - }, - ], - nominal: 1400000, - paid: 1400000, - remaining_cost: 0, - - approval: { - step_number: 5, - step_name: 'Selesai', - action: 'DONE', - notes: 'Proses selesai, sudah lunas.', - action_by: financeStaffUser, - action_at: '2025-02-20T15:00:00Z', - }, - }, - - // STEP 5 - Selesai (REJECTED by audit) - { - id: 10, - reference_number: 'REF-EXP-0010', - created_user: adminUser, - created_at: '2025-02-19T09:00:00Z', - updated_at: '2025-02-21T10:30:00Z', - - location: locationA, - transaction_date: '2025-02-19', - - kandangs: [kandangA1, kandangA2], - vendor: supplierPakan, - request_documents: [ - { - name: 'invoice_step5_recheck.pdf', - url: 'https://example.com/invoice_step5_recheck.pdf', - }, - ], - kandang_expenses: [ - { - kandang: kandangA1, - expenses: [ - { - nonstock: nonstockPakanStarter, - total_quantity: 60, - total_expense: 900000, - }, - ], - }, - { - kandang: kandangA2, - expenses: [ - { - nonstock: nonstockPakanFinisher, - total_quantity: 40, - total_expense: 600000, - }, - ], - }, - ], - nominal: 1500000, - paid: 1500000, - remaining_cost: 0, - - approval: { - step_number: 5, - step_name: 'Selesai', - action: 'REJECTED', - notes: 'Dibatalkan saat audit akhir, terdapat perbedaan kuantitas.', - action_by: auditUser, - action_at: '2025-02-21T10:30:00Z', - }, - }, -]; - export class ExpenseApiService extends BaseApiService< Expense, FormData, @@ -742,134 +19,404 @@ export class ExpenseApiService extends BaseApiService< super(basePath); } - // TODO: remove dummy data and integrate to real API - override async getAllFetcher( - endpoint: string - ): Promise> { - // return await httpClientFetcher>(endpoint); - - await sleep(750); - - return { - code: 200, - status: 'success', - message: 'Successfully get all expense data!', - meta: { - page: 1, - limit: 10, - total_pages: 1, - total_results: 8, - }, - data: DUMMY_EXPENSE, - }; - } - - // TODO: remove this and integrate to real API - async getSingle(id: number): Promise | undefined> { - await sleep(1000); - - return { - code: 200, - status: 'success', - message: 'Successfully get expense data!', - meta: { - page: 1, - limit: 10, - total_pages: 1, - total_results: 8, - }, - data: DUMMY_EXPENSE[id - 1], - }; - } - - // TODO: remove this and integrate to real API async create( payload: FormData ): Promise | undefined> { - await sleep(750); + try { + const createExpenseRequestRes = await httpClient< + BaseApiResponse + >(this.basePath, { + method: 'POST', + body: payload, + }); - const sentPayload = new Map(); - for (const pair of payload.entries()) { - sentPayload.set(pair[0], pair[1]); + return createExpenseRequestRes; + } catch (error) { + if (axios.isAxiosError>(error)) { + return error.response?.data; + } + + return undefined; } - - console.log({ sentPayload }); - - return { - code: 200, - status: 'success', - message: 'Berhasil membuat pengajuan biaya operasional!', - data: DUMMY_EXPENSE[0], - }; } - // TODO: remove this and integrate to real API - async update( + async createRealization( id: number, payload: FormData ): Promise | undefined> { - await sleep(750); + try { + const createExpenseRealizationRes = await httpClient< + BaseApiResponse + >(`${this.basePath}/${id}/realizations`, { + method: 'POST', + body: payload, + }); - const sentPayload = new Map(); - for (const pair of payload.entries()) { - sentPayload.set(pair[0], pair[1]); + return createExpenseRealizationRes; + } catch (error) { + if (axios.isAxiosError>(error)) { + return error.response?.data; + } + + return undefined; } - - console.log({ sentPayload }); - - return { - code: 200, - status: 'success', - message: 'Berhasil mengubah pengajuan biaya operasional!', - data: DUMMY_EXPENSE[0], - }; } - // TODO: remove this and integrate to real API - async delete(id: number): Promise { - await sleep(1000); + async update( + id: number, + payload: FormData, + deletedDocumentIds?: number[] + ): Promise | undefined> { + try { + for (const deletedDocumentId of deletedDocumentIds ?? []) { + await this.deleteExpenseRequestDocument(id, deletedDocumentId); + } - return { - code: 200, - status: 'success', - message: 'Successfully delete expense data!', - data: { - id, - }, - }; + const updateExpenseRequestRes = await httpClient< + BaseApiResponse + >(`${this.basePath}/${id}`, { + method: 'PATCH', + body: payload, + }); + + return updateExpenseRequestRes; + } catch (error) { + if (axios.isAxiosError>(error)) { + return error.response?.data; + } + + return undefined; + } + } + + async updateRealization( + id: number, + payload: FormData + ): Promise | undefined> { + try { + const updateExpenseRealizationRes = await httpClient< + BaseApiResponse + >(`${this.basePath}/${id}/realizations`, { + method: 'PATCH', + body: payload, + }); + + return updateExpenseRealizationRes; + } catch (error) { + if (axios.isAxiosError>(error)) { + return error.response?.data; + } + + return undefined; + } } - // TODO: remove this and integrate to real API async uploadRequestDocuments( id: number, files: File[] + ): Promise | undefined> { + try { + const updateExpenseRequestDocumentsFormData = new FormData(); + + // files (multiple "documents" keys) + files.forEach((file) => { + updateExpenseRequestDocumentsFormData.append('documents', file); + }); + + const updateExpenseRealizationRes = await httpClient< + BaseApiResponse + >(`${this.basePath}/${id}`, { + method: 'PATCH', + body: updateExpenseRequestDocumentsFormData, + }); + + return updateExpenseRealizationRes; + } catch (error) { + if (axios.isAxiosError>(error)) { + return error.response?.data; + } + + return undefined; + } + } + + async uploadRealizationDocuments( + id: number, + files: File[] + ): Promise | undefined> { + try { + const updateExpenseRealizationDocumentsFormData = new FormData(); + + // files (multiple "documents" keys) + files.forEach((file) => { + updateExpenseRealizationDocumentsFormData.append('documents', file); + }); + + const updateExpenseRealizationRes = await httpClient< + BaseApiResponse + >(`${this.basePath}/${id}/realizations`, { + method: 'PATCH', + body: updateExpenseRealizationDocumentsFormData, + }); + + return updateExpenseRealizationRes; + } catch (error) { + if (axios.isAxiosError>(error)) { + return error.response?.data; + } + + return undefined; + } + } + + async approveManager( + id: number, + notes?: string + ): Promise | undefined> { + try { + const approveRes = await httpClient>( + `${this.basePath}/approvals/manager`, + { + method: 'POST', + body: { + action: 'APPROVED', + approvable_ids: [id], + notes: notes, + }, + } + ); + + return approveRes; + } catch (error) { + if (axios.isAxiosError>(error)) { + return error.response?.data; + } + + return undefined; + } + } + + async bulkApproveManager( + ids: number[], + notes?: string + ): Promise | undefined> { + try { + const bulkApproveRes = await httpClient>( + `${this.basePath}/approvals/manager`, + { + method: 'POST', + body: { + action: 'APPROVED', + approvable_ids: ids, + notes: notes, + }, + } + ); + + return bulkApproveRes; + } catch (error) { + if (axios.isAxiosError>(error)) { + return error.response?.data; + } + + return undefined; + } + } + + async approveFinance( + id: number, + notes?: string + ): Promise | undefined> { + try { + const approveRes = await httpClient>( + `${this.basePath}/approvals/finance`, + { + method: 'POST', + body: { + action: 'APPROVED', + approvable_ids: [id], + notes: notes, + }, + } + ); + + return approveRes; + } catch (error) { + if (axios.isAxiosError>(error)) { + return error.response?.data; + } + + return undefined; + } + } + + async bulkApproveFinance( + ids: number[], + notes?: string + ): Promise | undefined> { + try { + const bulkApproveRes = await httpClient>( + `${this.basePath}/approvals/finance`, + { + method: 'POST', + body: { + action: 'APPROVED', + approvable_ids: ids, + notes: notes, + }, + } + ); + + return bulkApproveRes; + } catch (error) { + if (axios.isAxiosError>(error)) { + return error.response?.data; + } + + return undefined; + } + } + + async rejectManager( + id: number, + notes?: string + ): Promise | undefined> { + try { + const rejectRes = await httpClient>( + `${this.basePath}/approvals/manager`, + { + method: 'POST', + body: { + action: 'REJECTED', + approvable_ids: [id], + notes: notes, + }, + } + ); + + return rejectRes; + } catch (error) { + if (axios.isAxiosError>(error)) { + return error.response?.data; + } + + return undefined; + } + } + + async bulkRejectManager( + ids: number[], + notes?: string + ): Promise | undefined> { + try { + const bulkRejectRes = await httpClient>( + `${this.basePath}/approvals/manager`, + { + method: 'POST', + body: { + action: 'REJECTED', + approvable_ids: ids, + notes: notes, + }, + } + ); + + return bulkRejectRes; + } catch (error) { + if (axios.isAxiosError>(error)) { + return error.response?.data; + } + + return undefined; + } + } + + async rejectFinance( + id: number, + notes?: string + ): Promise | undefined> { + try { + const rejectRes = await httpClient>( + `${this.basePath}/approvals/finance`, + { + method: 'POST', + body: { + action: 'REJECTED', + approvable_ids: [id], + notes: notes, + }, + } + ); + + return rejectRes; + } catch (error) { + if (axios.isAxiosError>(error)) { + return error.response?.data; + } + + return undefined; + } + } + + async bulkRejectFinance( + ids: number[], + notes?: string + ): Promise | undefined> { + try { + const bulkRejectRes = await httpClient>( + `${this.basePath}/approvals/finance`, + { + method: 'POST', + body: { + action: 'REJECTED', + approvable_ids: ids, + notes: notes, + }, + } + ); + + return bulkRejectRes; + } catch (error) { + if (axios.isAxiosError>(error)) { + return error.response?.data; + } + + return undefined; + } + } + + async complete(id: number): Promise | undefined> { + try { + const completeRes = await httpClient>( + `${this.basePath}/${id}/complete`, + { + method: 'POST', + } + ); + + return completeRes; + } catch (error) { + if (axios.isAxiosError>(error)) { + return error.response?.data; + } + + return undefined; + } + } + + async deleteExpenseRequestDocument( + expenseId: number, + documentId: number ): Promise { try { - // const requestDocumentsFormData = new FormData(); + const deleteExpenseRequestDocument = await httpClient( + `${this.basePath}/${expenseId}/documents/${documentId}`, + { + method: 'DELETE', + } + ); - // // request_documents (array of File) - // files.forEach((file, index) => { - // requestDocumentsFormData.append(`request_documents[${index}]`, file); - // }); - - // const uploadRequestDocumentsRes = await httpClient( - // this.basePath, - // { - // method: 'POST', - // body: requestDocumentsFormData, - // } - // ); - - // return uploadRequestDocumentsRes; - - await sleep(1000); - - return { - code: 200, - status: 'success', - message: 'Berhasil menambahkan dokumen pengajuan!', - data: null, - }; + return deleteExpenseRequestDocument; } catch (error) { if (axios.isAxiosError(error)) { return error.response?.data; @@ -879,42 +426,22 @@ export class ExpenseApiService extends BaseApiService< } } - // TODO: integrate to real API - async approve( - id: number, - notes?: string - ): Promise | undefined> { + async deleteExpenseRealizationDocument( + expenseId: number, + documentId: number + ): Promise { try { - // const approveRes = await httpClient>( - // `${this.basePath}/approvals`, - // { - // method: 'POST', - // body: { - // action: 'APPROVED', - // approvable_ids: [id], - // notes: notes, - // }, - // } - // ); - // - // return approveRes; + const deleteExpenseRealizationDocument = + await httpClient( + `${this.basePath}/${expenseId}/realization-documents/${documentId}`, + { + method: 'DELETE', + } + ); - await sleep(1000); - - return { - code: 200, - status: 'success', - message: 'Successfully approve expense data!', - meta: { - page: 1, - limit: 10, - total_pages: 1, - total_results: 1, - }, - data: DUMMY_EXPENSE[id - 1], - }; + return deleteExpenseRealizationDocument; } catch (error) { - if (axios.isAxiosError>(error)) { + if (axios.isAxiosError(error)) { return error.response?.data; } @@ -922,181 +449,94 @@ export class ExpenseApiService extends BaseApiService< } } - // TODO: integrate to real API - async bulkApprove( - ids: number[], - notes?: string - ): Promise | undefined> { + async getApprovalHistory( + expenseId: number, + group: boolean = true, + page: number = 1, + limit: number = 10 + ) { try { - // const approveRes = await httpClient>( - // `${this.basePath}/approvals`, - // { - // method: 'POST', - // body: { - // action: 'APPROVED', - // approvable_ids: ids, - // notes: notes, - // }, - // } - // ); - // - // return approveRes; - - await sleep(1000); - - return { - code: 200, - status: 'success', - message: 'Successfully bulk approve expense data!', - meta: { - page: 1, - limit: 10, - total_pages: 1, - total_results: 1, - }, - data: DUMMY_EXPENSE[ids[0] - 1], - }; - } catch (error) { - if (axios.isAxiosError>(error)) { - return error.response?.data; - } - - return undefined; - } - } - - // TODO: integrate to real API - async reject( - id: number, - notes?: string - ): Promise | undefined> { - try { - // const rejectRes = await httpClient>( - // `${this.basePath}/approvals`, - // { - // method: 'POST', - // body: { - // action: 'REJECTED', - // approvable_ids: [id], - // notes: notes, - // }, - // } - // ); - // - // return rejectRes; - - await sleep(1000); - - return { - code: 200, - status: 'success', - message: 'Successfully reject expense data!', - meta: { - page: 1, - limit: 10, - total_pages: 1, - total_results: 1, - }, - data: DUMMY_EXPENSE[id - 1], - }; - } catch (error) { - if (axios.isAxiosError>(error)) { - return error.response?.data; - } - - return undefined; - } - } - - // TODO: integrate to real API - async bulkReject( - ids: number[], - notes?: string - ): Promise | undefined> { - try { - // const rejectRes = await httpClient>( - // `${this.basePath}/approvals`, - // { - // method: 'POST', - // body: { - // action: 'REJECTED', - // approvable_ids: ids, - // notes: notes, - // }, - // } - // ); - // - // return rejectRes; - - await sleep(1000); - - return { - code: 200, - status: 'success', - message: 'Successfully bulk reject expense data!', - meta: { - page: 1, - limit: 10, - total_pages: 1, - total_results: 1, - }, - data: DUMMY_EXPENSE[ids[0] - 1], - }; - } catch (error) { - if (axios.isAxiosError>(error)) { - return error.response?.data; - } - - return undefined; - } - } - - convertPayloadToFormData = (payload: CreateExpensePayload) => { - const formData = new FormData(); - - formData.append('locationId', String(payload.locationId)); - formData.append('transaction_date', payload.transaction_date); - formData.append('vendorId', String(payload.vendorId)); - - // kandangIds (array) - payload.kandangIds.forEach((id, index) => { - formData.append(`kandangIds[${index}]`, String(id)); - }); - - // request_documents (array of File) - payload.request_documents.forEach((file, index) => { - formData.append(`request_documents[${index}]`, file); - }); - - // kandang_expenses (nested array) - payload.kandang_expenses.forEach((kandangExpense, kandangIndex) => { - formData.append( - `kandang_expenses[${kandangIndex}][kandangId]`, - String(kandangExpense.kandangId) + const approvalHistoryRes = await httpClient( + '/approvals', + { + query: { + module_name: 'EXPENSES', + module_id: expenseId, + group_step_number: group ? 'true' : 'false', + page, + limit, + }, + } ); - kandangExpense.expenses.forEach((expenseItem, expenseIndex) => { - formData.append( - `kandang_expenses[${kandangIndex}][expenses][${expenseIndex}][nonstockId]`, - String(expenseItem.nonstockId) - ); - formData.append( - `kandang_expenses[${kandangIndex}][expenses][${expenseIndex}][total_quantity]`, - String(expenseItem.total_quantity) - ); - formData.append( - `kandang_expenses[${kandangIndex}][expenses][${expenseIndex}][total_expense]`, - String(expenseItem.total_expense) - ); - formData.append( - `kandang_expenses[${kandangIndex}][expenses][${expenseIndex}][notes]`, - expenseItem.notes ?? '' - ); - }); + return approvalHistoryRes; + } catch (error) { + if (axios.isAxiosError(error)) { + return error.response?.data; + } + + return undefined; + } + } + + convertExpenseRequestPayloadToFormData = (payload: CreateExpensePayload) => { + const formData = new FormData(); + + formData.append('category', payload.category); + 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( + 'cost_per_kandangs', + JSON.stringify(payload.cost_per_kandangs) + ); + + return formData; + }; + + convertExpenseRequestUpdatePayloadToFormData = ( + payload: UpdateExpensePayload + ) => { + const formData = new FormData(); + + formData.append('category', payload.category); + 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( + 'cost_per_kandang', + JSON.stringify(payload.cost_per_kandang) + ); + + 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; }; } -export const ExpenseApi = new ExpenseApiService('/expense'); +export const ExpenseApi = new ExpenseApiService('/expenses');