Files
lti-web-client/src/services/api/expense.ts
T
2025-11-17 14:56:25 +07:00

1103 lines
25 KiB
TypeScript

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 { 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,
FormData
> {
constructor(basePath: string) {
super(basePath);
}
// TODO: remove dummy data and integrate to real API
override async getAllFetcher(
endpoint: string
): Promise<BaseApiResponse<Expense[]>> {
// return await httpClientFetcher<BaseApiResponse<T[]>>(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<BaseApiResponse<Expense> | 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<BaseApiResponse<Expense> | undefined> {
await sleep(750);
const sentPayload = new Map();
for (const pair of payload.entries()) {
sentPayload.set(pair[0], pair[1]);
}
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(
id: number,
payload: FormData
): Promise<BaseApiResponse<Expense> | undefined> {
await sleep(750);
const sentPayload = new Map();
for (const pair of payload.entries()) {
sentPayload.set(pair[0], pair[1]);
}
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<BaseApiResponse | undefined> {
await sleep(1000);
return {
code: 200,
status: 'success',
message: 'Successfully delete expense data!',
data: {
id,
},
};
}
// TODO: remove this and integrate to real API
async uploadRequestDocuments(
id: number,
files: File[]
): Promise<BaseApiResponse | undefined> {
try {
// const requestDocumentsFormData = new FormData();
// // request_documents (array of File)
// files.forEach((file, index) => {
// requestDocumentsFormData.append(`request_documents[${index}]`, file);
// });
// const uploadRequestDocumentsRes = await httpClient<BaseApiResponse>(
// this.basePath,
// {
// method: 'POST',
// body: requestDocumentsFormData,
// }
// );
// return uploadRequestDocumentsRes;
await sleep(1000);
return {
code: 200,
status: 'success',
message: 'Berhasil menambahkan dokumen pengajuan!',
data: null,
};
} catch (error) {
if (axios.isAxiosError<BaseApiResponse>(error)) {
return error.response?.data;
}
return undefined;
}
}
// TODO: integrate to real API
async approve(
id: number,
notes?: string
): Promise<BaseApiResponse<Expense> | undefined> {
try {
// const approveRes = await httpClient<BaseApiResponse<Expense>>(
// `${this.basePath}/approvals`,
// {
// method: 'POST',
// body: {
// action: 'APPROVED',
// approvable_ids: [id],
// notes: notes,
// },
// }
// );
//
// return approveRes;
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],
};
} catch (error) {
if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
return error.response?.data;
}
return undefined;
}
}
// TODO: integrate to real API
async bulkApprove(
ids: number[],
notes?: string
): Promise<BaseApiResponse<Expense> | undefined> {
try {
// const approveRes = await httpClient<BaseApiResponse<Expense>>(
// `${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<BaseApiResponse<Expense>>(error)) {
return error.response?.data;
}
return undefined;
}
}
// TODO: integrate to real API
async reject(
id: number,
notes?: string
): Promise<BaseApiResponse<Expense> | undefined> {
try {
// const rejectRes = await httpClient<BaseApiResponse<Expense>>(
// `${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<BaseApiResponse<Expense>>(error)) {
return error.response?.data;
}
return undefined;
}
}
// TODO: integrate to real API
async bulkReject(
ids: number[],
notes?: string
): Promise<BaseApiResponse<Expense> | undefined> {
try {
// const rejectRes = await httpClient<BaseApiResponse<Expense>>(
// `${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<BaseApiResponse<Expense>>(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)
);
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 formData;
};
}
export const ExpenseApi = new ExpenseApiService('/expense');