mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 21:41:57 +00:00
Merge branch 'feat/FE/US-391/TASK-435-436-expense-adjustment' into 'development'
[FEAT/FE][US#391/TASK-435-436] Expense Adjustment See merge request mbugroup/lti-web-client!122
This commit is contained in:
@@ -16,7 +16,7 @@ import {
|
||||
} from '@/components/pages/expense/form/ExpenseRequestForm.schema';
|
||||
import { ExpenseApi } from '@/services/api/expense';
|
||||
import { isResponseSuccess } from '@/lib/api-helper';
|
||||
import { ACCEPTED_FILE_TYPE } from '@/config/constant';
|
||||
import { ACCEPTED_FILE_TYPE, S3_PUBLIC_BASE_URL } from '@/config/constant';
|
||||
|
||||
interface ExpenseRealizationContentProps {
|
||||
initialValues?: Expense;
|
||||
@@ -103,24 +103,32 @@ const ExpenseRealizationContent = ({
|
||||
initialValues?.realization_docs.length > 0 && (
|
||||
<ul className='list-disc'>
|
||||
{initialValues?.realization_docs.map(
|
||||
(realizationDocument, realizationDocumentIdx) => (
|
||||
<li key={realizationDocumentIdx}>
|
||||
<Link
|
||||
href={realizationDocument.path}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className='text-blue-500 underline'
|
||||
>
|
||||
{realizationDocument.path}{' '}
|
||||
<Icon
|
||||
icon='cuida:open-in-new-tab-outline'
|
||||
width={12}
|
||||
height={12}
|
||||
className='inline'
|
||||
/>
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
(realizationDocument, realizationDocumentIdx) => {
|
||||
const path = realizationDocument.path.startsWith(
|
||||
'/'
|
||||
)
|
||||
? realizationDocument.path.slice(1)
|
||||
: realizationDocument.path;
|
||||
const documentUrl = `${S3_PUBLIC_BASE_URL}/${path}`;
|
||||
return (
|
||||
<li key={realizationDocumentIdx}>
|
||||
<Link
|
||||
href={documentUrl}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className='text-blue-500 underline'
|
||||
>
|
||||
{realizationDocument.path}{' '}
|
||||
<Icon
|
||||
icon='cuida:open-in-new-tab-outline'
|
||||
width={12}
|
||||
height={12}
|
||||
className='inline'
|
||||
/>
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
@@ -211,7 +219,7 @@ const ExpenseRealizationContent = ({
|
||||
let expenseGrandTotal = 0;
|
||||
|
||||
kandangExpense.pengajuans?.forEach(
|
||||
(item) => (expenseGrandTotal += item.price)
|
||||
(item) => (expenseGrandTotal += item.qty * item.price)
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -273,7 +281,7 @@ const ExpenseRealizationContent = ({
|
||||
let expenseGrandTotal = 0;
|
||||
|
||||
kandangExpense.realisasi?.forEach(
|
||||
(item) => (expenseGrandTotal += item.price)
|
||||
(item) => (expenseGrandTotal += item.qty * item.price)
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
UploadRequestDocumentsFormSchema,
|
||||
UploadRequestDocumentsFormValues,
|
||||
} from '@/components/pages/expense/form/ExpenseRequestForm.schema';
|
||||
import { ACCEPTED_FILE_TYPE } from '@/config/constant';
|
||||
import { ACCEPTED_FILE_TYPE, S3_PUBLIC_BASE_URL } from '@/config/constant';
|
||||
import { ExpenseApi } from '@/services/api/expense';
|
||||
import { isResponseSuccess } from '@/lib/api-helper';
|
||||
import { EXPENSE_REQUEST_APPROVAL_LINE } from '@/config/approval-line';
|
||||
@@ -408,9 +408,13 @@ const ExpenseRequestContent = ({
|
||||
<th>Kandang</th>
|
||||
<th>:</th>
|
||||
<td>
|
||||
{initialValues?.kandangs
|
||||
.map((item) => item.name)
|
||||
.join(', ')}
|
||||
{initialValues?.kandangs &&
|
||||
initialValues?.kandangs.some((k) => k.name)
|
||||
? initialValues?.kandangs
|
||||
.filter((item) => item.name)
|
||||
.map((item) => item.name)
|
||||
.join(', ')
|
||||
: '-'}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -448,7 +452,14 @@ const ExpenseRequestContent = ({
|
||||
<tr>
|
||||
<th>Nominal Biaya</th>
|
||||
<th>:</th>
|
||||
<td>{formatCurrency(initialValues?.grand_total ?? 0)}</td>
|
||||
<td>
|
||||
{formatCurrency(
|
||||
initialValues?.latest_approval.step_number === 4 ||
|
||||
initialValues?.latest_approval.step_number === 5
|
||||
? (initialValues?.total_realisasi ?? 0)
|
||||
: (initialValues?.total_pengajuan ?? 0)
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Status Pencairan</th>
|
||||
@@ -482,24 +493,32 @@ const ExpenseRequestContent = ({
|
||||
initialValues?.documents.length > 0 && (
|
||||
<ul className='list-disc'>
|
||||
{initialValues?.documents.map(
|
||||
(requestDocument, requestDocumentIdx) => (
|
||||
<li key={requestDocumentIdx}>
|
||||
<Link
|
||||
href={requestDocument.path}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className='text-blue-500 underline'
|
||||
>
|
||||
{requestDocument.path}{' '}
|
||||
<Icon
|
||||
icon='cuida:open-in-new-tab-outline'
|
||||
width={12}
|
||||
height={12}
|
||||
className='inline'
|
||||
/>
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
(requestDocument, requestDocumentIdx) => {
|
||||
const path = requestDocument.path.startsWith(
|
||||
'/'
|
||||
)
|
||||
? requestDocument.path.slice(1)
|
||||
: requestDocument.path;
|
||||
const documentUrl = `${S3_PUBLIC_BASE_URL}/${path}`;
|
||||
return (
|
||||
<li key={requestDocumentIdx}>
|
||||
<Link
|
||||
href={documentUrl}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className='text-blue-500 underline'
|
||||
>
|
||||
{requestDocument.path}{' '}
|
||||
<Icon
|
||||
icon='cuida:open-in-new-tab-outline'
|
||||
width={12}
|
||||
height={12}
|
||||
className='inline'
|
||||
/>
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
@@ -558,7 +577,7 @@ const ExpenseRequestContent = ({
|
||||
let expenseGrandTotal = 0;
|
||||
|
||||
kandangExpense.pengajuans?.forEach(
|
||||
(item) => (expenseGrandTotal += item.price)
|
||||
(item) => (expenseGrandTotal += item.qty * item.price)
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -573,7 +592,9 @@ const ExpenseRequestContent = ({
|
||||
colSpan={5}
|
||||
className='font-bold text-center text-base-content text-lg'
|
||||
>
|
||||
Biaya {kandangExpense.name}
|
||||
{kandangExpense.kandang_id && kandangExpense.name
|
||||
? `Biaya ${kandangExpense.name}`
|
||||
: `Biaya ${initialValues?.location.name || 'Umum'}`}
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -20,10 +20,10 @@ interface ExpenseKandangsTableProps {
|
||||
locationId?: number;
|
||||
type: 'add' | 'edit' | 'detail';
|
||||
selectedKandangs: {
|
||||
id: number;
|
||||
name: string;
|
||||
id?: number;
|
||||
name?: string;
|
||||
}[];
|
||||
onChange: (kandangs: { id: number; name: string }[]) => void;
|
||||
onChange: (kandangs: { id?: number; name?: string }[]) => void;
|
||||
className?: {
|
||||
wrapper?: string;
|
||||
};
|
||||
@@ -67,7 +67,11 @@ const ExpenseKandangsTable = ({
|
||||
);
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>(
|
||||
convertRowSelectionArrToObj(selectedKandangs.map((item) => item.id))
|
||||
convertRowSelectionArrToObj(
|
||||
selectedKandangs
|
||||
.map((item) => item.id)
|
||||
.filter((id): id is number => id !== undefined)
|
||||
)
|
||||
);
|
||||
|
||||
const kandangsColumns: ColumnDef<Kandang>[] = [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as Yup from 'yup';
|
||||
import { Expense } from '@/types/api/expense';
|
||||
import { formatDate } from '@/lib/helper';
|
||||
import { S3_PUBLIC_BASE_URL } from '@/config/constant';
|
||||
|
||||
type ExpenseRealizationFormSchemaType = {
|
||||
category?: {
|
||||
@@ -12,7 +13,7 @@ type ExpenseRealizationFormSchemaType = {
|
||||
label: string;
|
||||
};
|
||||
realization_date?: string;
|
||||
kandangs?: { id: number; name: string }[];
|
||||
kandangs?: { id?: number; name?: string }[];
|
||||
supplier?: {
|
||||
value: number;
|
||||
label: string;
|
||||
@@ -20,7 +21,7 @@ type ExpenseRealizationFormSchemaType = {
|
||||
existing_documents?: { name: string; url: string }[];
|
||||
documents?: File[];
|
||||
realizations: {
|
||||
kandang_id: number;
|
||||
kandang_id?: number;
|
||||
cost_items: {
|
||||
nonstock?: {
|
||||
value: number;
|
||||
@@ -49,12 +50,11 @@ export const ExpenseRealizationFormSchema: Yup.ObjectSchema<ExpenseRealizationFo
|
||||
kandangs: Yup.array()
|
||||
.of(
|
||||
Yup.object({
|
||||
id: Yup.number().required('Kandang wajib dipilih!'),
|
||||
name: Yup.string().required('Kandang wajib dipilih!'),
|
||||
id: Yup.number().optional(),
|
||||
name: Yup.string().optional(),
|
||||
})
|
||||
)
|
||||
.min(1, 'Kandang wajib dipilih!')
|
||||
.required('Kandang wajib dipilih!'),
|
||||
.optional(),
|
||||
|
||||
supplier: Yup.object({
|
||||
value: Yup.number().min(1).required(),
|
||||
@@ -73,7 +73,7 @@ export const ExpenseRealizationFormSchema: Yup.ObjectSchema<ExpenseRealizationFo
|
||||
realizations: Yup.array()
|
||||
.of(
|
||||
Yup.object({
|
||||
kandang_id: Yup.number().min(1, 'Wajib memilih kandang!').required(),
|
||||
kandang_id: Yup.number().min(1, 'Wajib memilih kandang!').optional(),
|
||||
cost_items: Yup.array()
|
||||
.of(
|
||||
Yup.object({
|
||||
@@ -86,12 +86,12 @@ export const ExpenseRealizationFormSchema: Yup.ObjectSchema<ExpenseRealizationFo
|
||||
notes: Yup.string(),
|
||||
})
|
||||
)
|
||||
.min(1, 'Kandang harus memiliki setidaknya 1 biaya!')
|
||||
.required('Biaya kandang wajib diisi!'),
|
||||
.min(1, 'Harus memiliki setidaknya 1 biaya!')
|
||||
.required('Biaya wajib diisi!'),
|
||||
})
|
||||
)
|
||||
.min(1, 'Biaya kandang wajib diisi!')
|
||||
.required('Biaya kandang wajib diisi!'),
|
||||
.min(1, 'Biaya wajib diisi!')
|
||||
.required('Biaya wajib diisi!'),
|
||||
});
|
||||
|
||||
export const UpdateExpenseRealizationFormSchema = ExpenseRealizationFormSchema;
|
||||
@@ -139,10 +139,13 @@ export const getExpenseRealizationFormInitialValues = (
|
||||
label: initialValues.supplier.name,
|
||||
}
|
||||
: undefined,
|
||||
existing_documents: initialValues?.realization_docs?.map((doc) => ({
|
||||
name: doc.path,
|
||||
url: doc.path,
|
||||
})),
|
||||
existing_documents: initialValues?.realization_docs?.map((doc) => {
|
||||
const path = doc.path.startsWith('/') ? doc.path.slice(1) : doc.path;
|
||||
return {
|
||||
name: doc.path,
|
||||
url: `${S3_PUBLIC_BASE_URL}/${path}`,
|
||||
};
|
||||
}),
|
||||
documents: [],
|
||||
realizations: initialValues?.kandangs
|
||||
? initialValues.kandangs.map((kandangExpense) => {
|
||||
|
||||
@@ -150,25 +150,10 @@ const ExpenseRealizationForm = ({
|
||||
formik.setFieldValue('location', val);
|
||||
|
||||
formik.setFieldValue('kandangs', []);
|
||||
formik.setFieldValue('realizations', []);
|
||||
};
|
||||
|
||||
const kandangsChangeHandler = (kandangs: { id: number; name: string }[]) => {
|
||||
formik.setFieldTouched('kandangs', true);
|
||||
formik.setFieldValue('kandangs', kandangs);
|
||||
|
||||
const newRealizations = [...(formik.values.realizations ?? [])];
|
||||
|
||||
// add new realizations
|
||||
kandangs.forEach((kandangItem) => {
|
||||
const isKandangExistInRealization = newRealizations.find(
|
||||
(realizationItem) => realizationItem.kandang_id === kandangItem.id
|
||||
);
|
||||
|
||||
if (isKandangExistInRealization) return;
|
||||
|
||||
newRealizations.push({
|
||||
kandang_id: kandangItem.id,
|
||||
// Auto-create realization item for location (without kandang)
|
||||
formik.setFieldValue('realizations', [
|
||||
{
|
||||
cost_items: [
|
||||
{
|
||||
nonstock: undefined,
|
||||
@@ -177,25 +162,57 @@ const ExpenseRealizationForm = ({
|
||||
notes: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const kandangsChangeHandler = (
|
||||
kandangs: { id?: number; name?: string }[]
|
||||
) => {
|
||||
formik.setFieldTouched('kandangs', true);
|
||||
formik.setFieldValue('kandangs', kandangs);
|
||||
|
||||
// If no kandangs selected, create realization item for location
|
||||
if (kandangs.length === 0) {
|
||||
formik.setFieldValue('realizations', [
|
||||
{
|
||||
cost_items: [
|
||||
{
|
||||
nonstock: undefined,
|
||||
quantity: undefined,
|
||||
price: undefined,
|
||||
notes: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Start with empty array when kandangs are selected
|
||||
const newRealizations: typeof formik.values.realizations = [];
|
||||
|
||||
// add new realizations for each kandang
|
||||
kandangs.forEach((kandangItem) => {
|
||||
if (!kandangItem.id) return;
|
||||
|
||||
const existingRealization = formik.values.realizations?.find(
|
||||
(realizationItem) => realizationItem.kandang_id === kandangItem.id
|
||||
);
|
||||
|
||||
newRealizations.push({
|
||||
kandang_id: kandangItem.id,
|
||||
cost_items: existingRealization?.cost_items || [
|
||||
{
|
||||
nonstock: undefined,
|
||||
quantity: undefined,
|
||||
price: undefined,
|
||||
notes: '',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
// prune realizations
|
||||
const kandangIds = new Set(kandangs.map((kandang) => kandang.id));
|
||||
const deletedRealizationsIdx: number[] = [];
|
||||
|
||||
newRealizations.forEach((realization, idx) => {
|
||||
const isRealizationValid = kandangIds.has(realization.kandang_id);
|
||||
|
||||
if (!isRealizationValid) {
|
||||
deletedRealizationsIdx.push(idx);
|
||||
}
|
||||
});
|
||||
|
||||
deletedRealizationsIdx.forEach((deletedRealizationIdx) => {
|
||||
newRealizations.splice(deletedRealizationIdx, 1);
|
||||
});
|
||||
|
||||
formik.setFieldValue('realizations', newRealizations);
|
||||
};
|
||||
|
||||
@@ -338,7 +355,10 @@ const ExpenseRealizationForm = ({
|
||||
)}
|
||||
|
||||
<ExpenseRealizationKandangDetailExpense
|
||||
type={type}
|
||||
formik={formik}
|
||||
supplierId={formik.values.supplier?.value as number}
|
||||
location={formik.values.location}
|
||||
className={{
|
||||
wrapper: 'col-span-12',
|
||||
}}
|
||||
|
||||
@@ -18,6 +18,11 @@ import { Nonstock } from '@/types/api/master-data/nonstock';
|
||||
interface ExpenseRealizationKandangDetailExpenseProps {
|
||||
type?: 'add' | 'edit' | 'detail';
|
||||
formik: FormikContextType<ExpenseRealizationFormValues>;
|
||||
supplierId?: number;
|
||||
location?: {
|
||||
value: number;
|
||||
label: string;
|
||||
};
|
||||
className?: {
|
||||
wrapper?: string;
|
||||
};
|
||||
@@ -25,12 +30,18 @@ interface ExpenseRealizationKandangDetailExpenseProps {
|
||||
|
||||
const ExpenseRealizationKandangDetailExpense: React.FC<
|
||||
ExpenseRealizationKandangDetailExpenseProps
|
||||
> = ({ type, formik, className }) => {
|
||||
> = ({ type, formik, supplierId, location, className }) => {
|
||||
const {
|
||||
setInputValue: setNonstockInputValue,
|
||||
options: nonstockOptions,
|
||||
isLoadingOptions: isLoadingNonstockOptions,
|
||||
} = useSelect<Nonstock>(NonstockApi.basePath, 'id', 'name');
|
||||
} = useSelect<Nonstock>(
|
||||
NonstockApi.basePath,
|
||||
'id',
|
||||
'name',
|
||||
'search',
|
||||
supplierId ? { supplier_id: String(supplierId) } : undefined
|
||||
);
|
||||
|
||||
const nonstockChangeHandler = (
|
||||
kandangExpenseIdx: number,
|
||||
@@ -82,140 +93,159 @@ const ExpenseRealizationKandangDetailExpense: React.FC<
|
||||
</div>
|
||||
|
||||
<div className='w-full flex flex-col gap-6'>
|
||||
{formik.values.realizations.length === 0 && (
|
||||
{!formik.values.supplier?.value && (
|
||||
<div>
|
||||
<p className='text-sm text-gray-400 text-center'>
|
||||
Pilih kandang terlebih dahulu!
|
||||
Pilih supplier terlebih dahulu!
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{formik.values.realizations.map((kandangExpense, kandangExpenseIdx) => {
|
||||
const kandangName = formik.values.kandangs?.find(
|
||||
(kandang) => kandang.id === kandangExpense.kandang_id
|
||||
);
|
||||
{formik.values.realizations.length === 0 &&
|
||||
formik.values.supplier?.value && (
|
||||
<div>
|
||||
<p className='text-sm text-gray-400 text-center'>
|
||||
Belum ada item biaya. Silakan pilih lokasi terlebih dahulu.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
return (
|
||||
kandangName?.name && (
|
||||
<div
|
||||
key={`kandangExpense-${kandangExpenseIdx}`}
|
||||
className='w-full flex flex-col gap-4'
|
||||
>
|
||||
<div>
|
||||
<h5 className='mb-2 text-lg font-bold text-center'>
|
||||
Biaya {kandangName?.name}
|
||||
</h5>
|
||||
{formik.values.realizations.length > 0 &&
|
||||
formik.values.supplier?.value &&
|
||||
formik.values.realizations.map(
|
||||
(kandangExpense, kandangExpenseIdx) => {
|
||||
const kandangName = kandangExpense.kandang_id
|
||||
? formik.values.kandangs?.find(
|
||||
(kandang) => kandang.id === kandangExpense.kandang_id
|
||||
)
|
||||
: null;
|
||||
|
||||
<div className='overflow-x-auto'>
|
||||
<table className='table'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nonstock</th>
|
||||
<th>Total Kuantitas</th>
|
||||
<th>Harga Satuan</th>
|
||||
<th>Catatan</th>
|
||||
</tr>
|
||||
</thead>
|
||||
return (
|
||||
(kandangName?.name || !kandangExpense.kandang_id) && (
|
||||
<div
|
||||
key={`kandangExpense-${kandangExpenseIdx}`}
|
||||
className='w-full flex flex-col gap-4'
|
||||
>
|
||||
<div>
|
||||
<h5 className='mb-2 text-lg font-bold text-center'>
|
||||
{kandangName?.name
|
||||
? `Biaya ${kandangName.name}`
|
||||
: location?.label
|
||||
? `Biaya ${location.label}`
|
||||
: 'Biaya Umum'}
|
||||
</h5>
|
||||
|
||||
<tbody>
|
||||
{kandangExpense.cost_items.map(
|
||||
(expenseItem, expenseIdx) => (
|
||||
<tr key={`expense-${expenseIdx}`}>
|
||||
<td className='p-2'>
|
||||
<SelectInput
|
||||
placeholder='Pilih Nonstock'
|
||||
value={expenseItem.nonstock}
|
||||
onChange={(val) => {
|
||||
nonstockChangeHandler(
|
||||
kandangExpenseIdx,
|
||||
expenseIdx,
|
||||
val
|
||||
);
|
||||
}}
|
||||
options={nonstockOptions}
|
||||
isLoading={isLoadingNonstockOptions}
|
||||
onInputChange={setNonstockInputValue}
|
||||
className={{ wrapper: 'min-w-48' }}
|
||||
isDisabled
|
||||
/>
|
||||
</td>
|
||||
|
||||
<td className='p-2'>
|
||||
<NumberInput
|
||||
required
|
||||
name={`realizations[${kandangExpenseIdx}].cost_items[${expenseIdx}].quantity`}
|
||||
placeholder='Masukkan Total Kuantitas'
|
||||
value={
|
||||
formik.values.realizations[
|
||||
kandangExpenseIdx
|
||||
].cost_items[expenseIdx].quantity ?? ''
|
||||
}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
isError={isExpenseRepeaterInputError(
|
||||
'quantity',
|
||||
kandangExpenseIdx,
|
||||
expenseIdx
|
||||
)}
|
||||
className={{ wrapper: 'min-w-24' }}
|
||||
/>
|
||||
</td>
|
||||
|
||||
<td className='p-2'>
|
||||
<NumberInput
|
||||
name={`realizations[${kandangExpenseIdx}].cost_items[${expenseIdx}].price`}
|
||||
placeholder='Masukkan Harga Satuan'
|
||||
value={
|
||||
formik.values.realizations[
|
||||
kandangExpenseIdx
|
||||
].cost_items[expenseIdx].price ?? ''
|
||||
}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
isError={isExpenseRepeaterInputError(
|
||||
'price',
|
||||
kandangExpenseIdx,
|
||||
expenseIdx
|
||||
)}
|
||||
inputPrefix={
|
||||
<span className='text-gray-600 font-medium'>
|
||||
Rp
|
||||
</span>
|
||||
}
|
||||
className={{ wrapper: 'min-w-24' }}
|
||||
/>
|
||||
</td>
|
||||
|
||||
<td className='p-2'>
|
||||
<TextInput
|
||||
name={`realizations[${kandangExpenseIdx}].cost_items[${expenseIdx}].notes`}
|
||||
placeholder='Tuliskan catatan'
|
||||
value={
|
||||
formik.values.realizations[
|
||||
kandangExpenseIdx
|
||||
].cost_items[expenseIdx].notes ?? ''
|
||||
}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
isError={isExpenseRepeaterInputError(
|
||||
'notes',
|
||||
kandangExpenseIdx,
|
||||
expenseIdx
|
||||
)}
|
||||
className={{ wrapper: 'min-w-24' }}
|
||||
/>
|
||||
</td>
|
||||
<div className='overflow-x-auto'>
|
||||
<table className='table'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nonstock</th>
|
||||
<th>Total Kuantitas</th>
|
||||
<th>Harga Satuan</th>
|
||||
<th>Catatan</th>
|
||||
</tr>
|
||||
)
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{kandangExpense.cost_items.map(
|
||||
(expenseItem, expenseIdx) => (
|
||||
<tr key={`expense-${expenseIdx}`}>
|
||||
<td className='p-2'>
|
||||
<SelectInput
|
||||
placeholder='Pilih Nonstock'
|
||||
value={expenseItem.nonstock}
|
||||
onChange={(val) => {
|
||||
nonstockChangeHandler(
|
||||
kandangExpenseIdx,
|
||||
expenseIdx,
|
||||
val
|
||||
);
|
||||
}}
|
||||
options={nonstockOptions}
|
||||
isLoading={isLoadingNonstockOptions}
|
||||
onInputChange={setNonstockInputValue}
|
||||
className={{ wrapper: 'min-w-48' }}
|
||||
isDisabled
|
||||
/>
|
||||
</td>
|
||||
|
||||
<td className='p-2'>
|
||||
<NumberInput
|
||||
required
|
||||
name={`realizations[${kandangExpenseIdx}].cost_items[${expenseIdx}].quantity`}
|
||||
placeholder='Masukkan Total Kuantitas'
|
||||
value={
|
||||
formik.values.realizations[
|
||||
kandangExpenseIdx
|
||||
].cost_items[expenseIdx].quantity ?? ''
|
||||
}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
isError={isExpenseRepeaterInputError(
|
||||
'quantity',
|
||||
kandangExpenseIdx,
|
||||
expenseIdx
|
||||
)}
|
||||
className={{ wrapper: 'min-w-24' }}
|
||||
/>
|
||||
</td>
|
||||
|
||||
<td className='p-2'>
|
||||
<NumberInput
|
||||
name={`realizations[${kandangExpenseIdx}].cost_items[${expenseIdx}].price`}
|
||||
placeholder='Masukkan Harga Satuan'
|
||||
value={
|
||||
formik.values.realizations[
|
||||
kandangExpenseIdx
|
||||
].cost_items[expenseIdx].price ?? ''
|
||||
}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
isError={isExpenseRepeaterInputError(
|
||||
'price',
|
||||
kandangExpenseIdx,
|
||||
expenseIdx
|
||||
)}
|
||||
inputPrefix={
|
||||
<span className='text-gray-600 font-medium'>
|
||||
Rp
|
||||
</span>
|
||||
}
|
||||
className={{ wrapper: 'min-w-24' }}
|
||||
/>
|
||||
</td>
|
||||
|
||||
<td className='p-2'>
|
||||
<TextInput
|
||||
name={`realizations[${kandangExpenseIdx}].cost_items[${expenseIdx}].notes`}
|
||||
placeholder='Tuliskan catatan'
|
||||
value={
|
||||
formik.values.realizations[
|
||||
kandangExpenseIdx
|
||||
].cost_items[expenseIdx].notes ?? ''
|
||||
}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
isError={isExpenseRepeaterInputError(
|
||||
'notes',
|
||||
kandangExpenseIdx,
|
||||
expenseIdx
|
||||
)}
|
||||
className={{ wrapper: 'min-w-24' }}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
})}
|
||||
)
|
||||
);
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as Yup from 'yup';
|
||||
import { Expense } from '@/types/api/expense';
|
||||
import { formatDate } from '@/lib/helper';
|
||||
import { S3_PUBLIC_BASE_URL } from '@/config/constant';
|
||||
|
||||
type ExpenseFormSchemaType = {
|
||||
category?: {
|
||||
@@ -11,8 +12,9 @@ type ExpenseFormSchemaType = {
|
||||
value: number;
|
||||
label: string;
|
||||
};
|
||||
location_id: number;
|
||||
transaction_date?: string;
|
||||
kandangs?: { id: number; name: string }[];
|
||||
kandangs?: { id?: number; name?: string }[];
|
||||
supplier?: {
|
||||
value: number;
|
||||
label: string;
|
||||
@@ -21,7 +23,7 @@ type ExpenseFormSchemaType = {
|
||||
deleted_documents?: number[];
|
||||
documents?: File[];
|
||||
expense_nonstocks: {
|
||||
kandang_id: number;
|
||||
kandang_id?: number;
|
||||
cost_items: {
|
||||
nonstock?: {
|
||||
value: number;
|
||||
@@ -46,16 +48,17 @@ export const ExpenseRequestFormSchema: Yup.ObjectSchema<ExpenseFormSchemaType> =
|
||||
label: Yup.string().required(),
|
||||
}).required('Lokasi wajib diisi!'),
|
||||
|
||||
location_id: Yup.number().min(1).required('Lokasi wajib diisi!'),
|
||||
|
||||
transaction_date: Yup.string().required('Tanggal transaksi wajib diisi!'),
|
||||
kandangs: Yup.array()
|
||||
.of(
|
||||
Yup.object({
|
||||
id: Yup.number().required('Kandang wajib dipilih!'),
|
||||
name: Yup.string().required('Kandang wajib dipilih!'),
|
||||
id: Yup.number().optional(),
|
||||
name: Yup.string().optional(),
|
||||
})
|
||||
)
|
||||
.min(1, 'Kandang wajib dipilih!')
|
||||
.required('Kandang wajib dipilih!'),
|
||||
.optional(),
|
||||
|
||||
supplier: Yup.object({
|
||||
value: Yup.number().min(1).required(),
|
||||
@@ -77,7 +80,7 @@ export const ExpenseRequestFormSchema: Yup.ObjectSchema<ExpenseFormSchemaType> =
|
||||
expense_nonstocks: Yup.array()
|
||||
.of(
|
||||
Yup.object({
|
||||
kandang_id: Yup.number().min(1, 'Wajib memilih kandang!').required(),
|
||||
kandang_id: Yup.number().min(1, 'Wajib memilih kandang!').optional(),
|
||||
cost_items: Yup.array()
|
||||
.of(
|
||||
Yup.object({
|
||||
@@ -128,6 +131,7 @@ export const getExpenseFormInitialValues = (
|
||||
label: initialValues.location.name,
|
||||
}
|
||||
: undefined,
|
||||
location_id: Number(initialValues?.location.id || 0),
|
||||
transaction_date: initialValues?.transaction_date
|
||||
? formatDate(initialValues.transaction_date, 'YYYY-MM-DD')
|
||||
: undefined,
|
||||
@@ -141,11 +145,14 @@ export const getExpenseFormInitialValues = (
|
||||
label: initialValues.supplier.name,
|
||||
}
|
||||
: undefined,
|
||||
existing_documents: initialValues?.documents?.map((doc) => ({
|
||||
id: doc.id,
|
||||
name: doc.path,
|
||||
url: doc.path,
|
||||
})),
|
||||
existing_documents: initialValues?.documents?.map((doc) => {
|
||||
const path = doc.path.startsWith('/') ? doc.path.slice(1) : doc.path;
|
||||
return {
|
||||
id: doc.id,
|
||||
name: doc.path,
|
||||
url: `${S3_PUBLIC_BASE_URL}/${path}`,
|
||||
};
|
||||
}),
|
||||
deleted_documents: [],
|
||||
documents: [],
|
||||
expense_nonstocks: initialValues?.kandangs
|
||||
|
||||
@@ -108,18 +108,24 @@ const ExpenseRequestForm = ({
|
||||
|
||||
const expensePayload: CreateExpensePayload = {
|
||||
category: formik.values.category?.value as 'BOP' | 'NON-BOP',
|
||||
location_id: values.location_id as number,
|
||||
transaction_date: values?.transaction_date as string,
|
||||
supplier_id: values.supplier?.value as number,
|
||||
documents: values.documents as File[],
|
||||
expense_nonstocks: values.expense_nonstocks.map((expenseNonstock) => ({
|
||||
kandang_id: expenseNonstock.kandang_id,
|
||||
cost_items: expenseNonstock.cost_items.map((costItem) => ({
|
||||
nonstock_id: costItem.nonstock?.value as number,
|
||||
quantity: parseFloat(String(costItem.quantity)) as number,
|
||||
price: parseFloat(String(costItem.price)) as number,
|
||||
notes: costItem.notes ?? '',
|
||||
})),
|
||||
})),
|
||||
expense_nonstocks: values.expense_nonstocks.map((expenseNonstock) => {
|
||||
const basePayload = {
|
||||
cost_items: expenseNonstock.cost_items.map((costItem) => ({
|
||||
nonstock_id: costItem.nonstock?.value as number,
|
||||
quantity: parseFloat(String(costItem.quantity)) as number,
|
||||
price: parseFloat(String(costItem.price)) as number,
|
||||
notes: costItem.notes ?? '',
|
||||
})),
|
||||
};
|
||||
|
||||
return expenseNonstock.kandang_id
|
||||
? { ...basePayload, kandang_id: expenseNonstock.kandang_id }
|
||||
: basePayload;
|
||||
}),
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
@@ -130,19 +136,25 @@ const ExpenseRequestForm = ({
|
||||
case 'edit':
|
||||
const expenseUpdatePayload: UpdateExpensePayload = {
|
||||
category: formik.values.category?.value as 'BOP' | 'NON-BOP',
|
||||
location_id: values.location_id as number,
|
||||
transaction_date: values?.transaction_date as string,
|
||||
supplier_id: values.supplier?.value as number,
|
||||
documents: values.documents as File[],
|
||||
expense_nonstocks: values.expense_nonstocks.map(
|
||||
(expenseNonstock) => ({
|
||||
kandang_id: expenseNonstock.kandang_id,
|
||||
cost_items: expenseNonstock.cost_items.map((costItem) => ({
|
||||
nonstock_id: costItem.nonstock?.value as number,
|
||||
quantity: parseFloat(String(costItem.quantity)) as number,
|
||||
price: parseFloat(String(costItem.price)) as number,
|
||||
notes: costItem.notes ?? '',
|
||||
})),
|
||||
})
|
||||
(expenseNonstock) => {
|
||||
const basePayload = {
|
||||
cost_items: expenseNonstock.cost_items.map((costItem) => ({
|
||||
nonstock_id: costItem.nonstock?.value as number,
|
||||
quantity: parseFloat(String(costItem.quantity)) as number,
|
||||
price: parseFloat(String(costItem.price)) as number,
|
||||
notes: costItem.notes ?? '',
|
||||
})),
|
||||
};
|
||||
|
||||
return expenseNonstock.kandang_id
|
||||
? { ...basePayload, kandang_id: expenseNonstock.kandang_id }
|
||||
: basePayload;
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
@@ -179,27 +191,14 @@ const ExpenseRequestForm = ({
|
||||
formik.setFieldTouched('location', true);
|
||||
formik.setFieldValue('location', val);
|
||||
|
||||
const locationId = Array.isArray(val) ? val[0]?.value : val?.value;
|
||||
formik.setFieldValue('location_id', locationId);
|
||||
|
||||
formik.setFieldValue('kandangs', []);
|
||||
formik.setFieldValue('expense_nonstocks', []);
|
||||
};
|
||||
|
||||
const kandangsChangeHandler = (kandangs: { id: number; name: string }[]) => {
|
||||
formik.setFieldTouched('kandangs', true);
|
||||
formik.setFieldValue('kandangs', kandangs);
|
||||
|
||||
const newExpenseNonstocks = [...(formik.values.expense_nonstocks ?? [])];
|
||||
|
||||
// add new expense_nonstocks
|
||||
kandangs.forEach((kandangItem) => {
|
||||
const isKandangExistInExpenseNonstocks = newExpenseNonstocks.find(
|
||||
(expenseNonstockItem) =>
|
||||
expenseNonstockItem.kandang_id === kandangItem.id
|
||||
);
|
||||
|
||||
if (isKandangExistInExpenseNonstocks) return;
|
||||
|
||||
newExpenseNonstocks.push({
|
||||
kandang_id: kandangItem.id,
|
||||
// Auto-create expense item for location (without kandang)
|
||||
formik.setFieldValue('expense_nonstocks', [
|
||||
{
|
||||
cost_items: [
|
||||
{
|
||||
nonstock: undefined,
|
||||
@@ -208,25 +207,56 @@ const ExpenseRequestForm = ({
|
||||
notes: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const kandangsChangeHandler = (
|
||||
kandangs: { id?: number; name?: string }[]
|
||||
) => {
|
||||
formik.setFieldTouched('kandangs', true);
|
||||
formik.setFieldValue('kandangs', kandangs);
|
||||
|
||||
// If no kandangs selected, create expense item for location
|
||||
if (kandangs.length === 0) {
|
||||
formik.setFieldValue('expense_nonstocks', [
|
||||
{
|
||||
cost_items: [
|
||||
{
|
||||
nonstock: undefined,
|
||||
quantity: undefined,
|
||||
price: undefined,
|
||||
notes: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
const newExpenseNonstocks: typeof formik.values.expense_nonstocks = [];
|
||||
|
||||
kandangs.forEach((kandangItem) => {
|
||||
if (!kandangItem.id) return;
|
||||
|
||||
const existingExpenseNonstock = formik.values.expense_nonstocks?.find(
|
||||
(expenseNonstockItem) =>
|
||||
expenseNonstockItem.kandang_id === kandangItem.id
|
||||
);
|
||||
|
||||
newExpenseNonstocks.push({
|
||||
kandang_id: kandangItem.id,
|
||||
cost_items: existingExpenseNonstock?.cost_items || [
|
||||
{
|
||||
nonstock: undefined,
|
||||
quantity: undefined,
|
||||
price: undefined,
|
||||
notes: '',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
// prune expense_nonstocks
|
||||
const kandangIds = new Set(kandangs.map((kandang) => kandang.id));
|
||||
const deletedExpenseNonstocksIdx: number[] = [];
|
||||
|
||||
newExpenseNonstocks.forEach((expenseNonstock, idx) => {
|
||||
const isExpenseNonstockValid = kandangIds.has(expenseNonstock.kandang_id);
|
||||
|
||||
if (!isExpenseNonstockValid) {
|
||||
deletedExpenseNonstocksIdx.push(idx);
|
||||
}
|
||||
});
|
||||
|
||||
deletedExpenseNonstocksIdx.forEach((deletedExpenseNonstockIdx) => {
|
||||
newExpenseNonstocks.splice(deletedExpenseNonstockIdx, 1);
|
||||
});
|
||||
|
||||
formik.setFieldValue('expense_nonstocks', newExpenseNonstocks);
|
||||
};
|
||||
|
||||
@@ -454,7 +484,10 @@ const ExpenseRequestForm = ({
|
||||
)}
|
||||
|
||||
<ExpenseRequestKandangDetailExpense
|
||||
type={type}
|
||||
formik={formik}
|
||||
supplierId={formik.values.supplier?.value as number}
|
||||
location={formik.values.location}
|
||||
className={{
|
||||
wrapper: 'col-span-12',
|
||||
}}
|
||||
|
||||
@@ -21,6 +21,11 @@ import { removeArrayItemAndSync } from '@/lib/utils/formik';
|
||||
interface ExpenseRequestKandangDetailExpenseProps {
|
||||
type?: 'add' | 'edit' | 'detail';
|
||||
formik: FormikContextType<ExpenseRequestFormValues>;
|
||||
supplierId?: number;
|
||||
location?: {
|
||||
value: number;
|
||||
label: string;
|
||||
};
|
||||
className?: {
|
||||
wrapper?: string;
|
||||
};
|
||||
@@ -28,12 +33,18 @@ interface ExpenseRequestKandangDetailExpenseProps {
|
||||
|
||||
const ExpenseRequestKandangDetailExpense: React.FC<
|
||||
ExpenseRequestKandangDetailExpenseProps
|
||||
> = ({ type, formik, className }) => {
|
||||
> = ({ type, formik, supplierId, location, className }) => {
|
||||
const {
|
||||
setInputValue: setNonstockInputValue,
|
||||
options: nonstockOptions,
|
||||
isLoadingOptions: isLoadingNonstockOptions,
|
||||
} = useSelect<Nonstock>(NonstockApi.basePath, 'id', 'name');
|
||||
} = useSelect<Nonstock>(
|
||||
NonstockApi.basePath,
|
||||
'id',
|
||||
'name',
|
||||
'search',
|
||||
supplierId ? { supplier_id: String(supplierId) } : undefined
|
||||
);
|
||||
|
||||
const nonstockChangeHandler = (
|
||||
kandangExpenseIdx: number,
|
||||
@@ -113,41 +124,57 @@ const ExpenseRequestKandangDetailExpense: React.FC<
|
||||
</div>
|
||||
|
||||
<div className='w-full flex flex-col gap-6'>
|
||||
{(formik.values.expense_nonstocks.length === 0 ||
|
||||
!formik.values.supplier?.value) && (
|
||||
{!formik.values.supplier?.value && (
|
||||
<div>
|
||||
<p className='text-sm text-gray-400 text-center'>
|
||||
Pilih kandang terlebih dahulu!
|
||||
Pilih supplier terlebih dahulu!
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{formik.values.expense_nonstocks.length === 0 &&
|
||||
formik.values.supplier?.value && (
|
||||
<div>
|
||||
<p className='text-sm text-gray-400 text-center'>
|
||||
Belum ada item biaya. Silakan pilih lokasi terlebih dahulu.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{formik.values.expense_nonstocks.length > 0 &&
|
||||
formik.values.supplier?.value &&
|
||||
formik.values.expense_nonstocks.map(
|
||||
(kandangExpense, kandangExpenseIdx) => {
|
||||
const kandangName = formik.values.kandangs?.find(
|
||||
(kandang) => kandang.id === kandangExpense.kandang_id
|
||||
);
|
||||
const kandangName = kandangExpense.kandang_id
|
||||
? formik.values.kandangs?.find(
|
||||
(kandang) => kandang.id === kandangExpense.kandang_id
|
||||
)
|
||||
: null;
|
||||
|
||||
return (
|
||||
kandangName?.name && (
|
||||
(kandangName?.name || !kandangExpense.kandang_id) && (
|
||||
<div
|
||||
key={`kandangExpense-${kandangExpenseIdx}`}
|
||||
className='w-full flex flex-col gap-4'
|
||||
>
|
||||
<div>
|
||||
<h5 className='mb-2 text-lg font-bold text-center'>
|
||||
Biaya {kandangName?.name}
|
||||
Biaya {kandangName?.name || location?.label || 'Umum'}
|
||||
</h5>
|
||||
|
||||
<div className='overflow-x-auto'>
|
||||
<table className='table'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nonstock</th>
|
||||
<th>Total Kuantitas</th>
|
||||
<th>Harga Satuan</th>
|
||||
<th className='after:content-["*"] after:text-red-500 after:ml-0.5'>
|
||||
Nonstock
|
||||
</th>
|
||||
<th className='after:content-["*"] after:text-red-500 after:ml-0.5'>
|
||||
Total Kuantitas
|
||||
</th>
|
||||
<th className='after:content-["*"] after:text-red-500 after:ml-0.5'>
|
||||
Harga Satuan
|
||||
</th>
|
||||
<th>Catatan</th>
|
||||
{type !== 'detail' && <th>Aksi</th>}
|
||||
</tr>
|
||||
|
||||
@@ -219,7 +219,13 @@ const ExpensePDF = ({ expense }: ExpensePDFProps) => {
|
||||
{ label: 'Lokasi', value: expense?.location.name },
|
||||
{
|
||||
label: 'Kandang',
|
||||
value: expense?.kandangs.map((item) => item.name).join(', '),
|
||||
value:
|
||||
expense?.kandangs && expense?.kandangs.some((k) => k.name)
|
||||
? expense?.kandangs
|
||||
.filter((item) => item.name)
|
||||
.map((item) => item.name)
|
||||
.join(', ')
|
||||
: '-',
|
||||
},
|
||||
{ label: 'Vendor', value: expense?.supplier.name },
|
||||
{
|
||||
@@ -235,7 +241,12 @@ const ExpensePDF = ({ expense }: ExpensePDFProps) => {
|
||||
{ label: 'Nama Pengaju', value: expense?.created_user.name },
|
||||
{
|
||||
label: 'Nominal Biaya',
|
||||
value: formatCurrency(expense?.grand_total ?? 0),
|
||||
value: formatCurrency(
|
||||
expense?.latest_approval.step_number === 4 ||
|
||||
expense?.latest_approval.step_number === 5
|
||||
? (expense?.total_realisasi ?? 0)
|
||||
: (expense?.total_pengajuan ?? 0)
|
||||
),
|
||||
},
|
||||
{
|
||||
label: 'Nominal Pengajuan',
|
||||
@@ -326,7 +337,7 @@ const ExpensePDF = ({ expense }: ExpensePDFProps) => {
|
||||
let expenseRequestTotal = 0;
|
||||
|
||||
kandangExpense.pengajuans?.forEach(
|
||||
(item) => (expenseRequestTotal += item.price)
|
||||
(item) => (expenseRequestTotal += item.qty * item.price)
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -335,7 +346,9 @@ const ExpensePDF = ({ expense }: ExpensePDFProps) => {
|
||||
style={ExpensePDFStyle.kandangExpenseContainer}
|
||||
>
|
||||
<Text style={ExpensePDFStyle.kandangExpenseTitle}>
|
||||
{kandangExpense.name}
|
||||
{kandangExpense.kandang_id && kandangExpense.name
|
||||
? `Biaya ${kandangExpense.name}`
|
||||
: `Biaya ${expense?.location.name || 'Umum'}`}
|
||||
</Text>
|
||||
|
||||
<View style={ExpensePDFStyle.kandangExpenseTable}>
|
||||
@@ -484,7 +497,7 @@ const ExpensePDF = ({ expense }: ExpensePDFProps) => {
|
||||
let expenseRealizationTotal = 0;
|
||||
|
||||
kandangExpense.realisasi?.forEach(
|
||||
(item) => (expenseRealizationTotal += item.price)
|
||||
(item) => (expenseRealizationTotal += item.qty * item.price)
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -493,7 +506,9 @@ const ExpensePDF = ({ expense }: ExpensePDFProps) => {
|
||||
style={ExpensePDFStyle.kandangExpenseContainer}
|
||||
>
|
||||
<Text style={ExpensePDFStyle.kandangExpenseTitle}>
|
||||
{kandangExpense.name}
|
||||
{kandangExpense.kandang_id && kandangExpense.name
|
||||
? `Biaya ${kandangExpense.name}`
|
||||
: `Biaya ${expense?.location.name || 'Umum'}`}
|
||||
</Text>
|
||||
|
||||
<View style={ExpensePDFStyle.kandangExpenseTable}>
|
||||
|
||||
@@ -352,6 +352,9 @@ export const ACCEPTED_FILE_TYPE = {
|
||||
},
|
||||
};
|
||||
|
||||
export const S3_PUBLIC_BASE_URL = process.env
|
||||
.NEXT_PUBLIC_S3_PUBLIC_BASE_URL as string;
|
||||
|
||||
export const FILTER_TYPE_OPTIONS = [
|
||||
{
|
||||
label: 'Tanggal Realisasi',
|
||||
|
||||
@@ -483,6 +483,7 @@ export class ExpenseApiService extends BaseApiService<
|
||||
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));
|
||||
|
||||
@@ -505,6 +506,7 @@ export class ExpenseApiService extends BaseApiService<
|
||||
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));
|
||||
|
||||
|
||||
Vendored
+4
-2
@@ -57,11 +57,12 @@ export type Expense = BaseMetadata & BaseExpense;
|
||||
|
||||
export type CreateExpensePayload = {
|
||||
category: 'BOP' | 'NON-BOP';
|
||||
location_id: number;
|
||||
transaction_date: string;
|
||||
supplier_id: number;
|
||||
documents: File[];
|
||||
expense_nonstocks: {
|
||||
kandang_id: number;
|
||||
kandang_id?: number;
|
||||
cost_items: {
|
||||
nonstock_id: number;
|
||||
quantity: number;
|
||||
@@ -72,12 +73,13 @@ export type CreateExpensePayload = {
|
||||
};
|
||||
|
||||
export type UpdateExpensePayload = {
|
||||
location_id: number;
|
||||
category: 'BOP' | 'NON-BOP';
|
||||
transaction_date: string;
|
||||
supplier_id: number;
|
||||
documents: File[];
|
||||
expense_nonstocks: {
|
||||
kandang_id: number;
|
||||
kandang_id?: number;
|
||||
cost_items: {
|
||||
nonstock_id: number;
|
||||
quantity: number;
|
||||
|
||||
Reference in New Issue
Block a user