mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
feat(FE-284): Slicing and API Integration Perhitungan Sapronak
This commit is contained in:
@@ -4,13 +4,17 @@ import { useRouter, useSearchParams } from 'next/navigation';
|
|||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import SalesReportTable from '@/components/pages/closing/sale/SalesReportTable';
|
import SalesReportTable from '@/components/pages/closing/sale/SalesReportTable';
|
||||||
import { ClosingApi } from '@/services/api/closing';
|
import { ClosingApi } from '@/services/api/closing';
|
||||||
import { isResponseSuccess, isResponseError } from '@/lib/api-helper';
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
import SapronakCalculationTable from '@/components/pages/closing/sapronak/SapronakCalculationTable';
|
import SapronakCalculationTable from '@/components/pages/closing/sapronak/SapronakCalculationTable';
|
||||||
|
import Tabs from '@/components/Tabs';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
const ClosingDetailPage = () => {
|
const ClosingDetailPage = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
|
const [activeTab, setActiveTab] = useState<string>('perhitungan_sapronak');
|
||||||
|
|
||||||
const closingId = searchParams.get('closingId');
|
const closingId = searchParams.get('closingId');
|
||||||
|
|
||||||
const { data: closing, isLoading: isLoadingClosing } = useSWR(
|
const { data: closing, isLoading: isLoadingClosing } = useSWR(
|
||||||
@@ -24,6 +28,17 @@ const ClosingDetailPage = () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { data: sapronakCalculation, isLoading: isLoadingSapronakCalculation } =
|
||||||
|
useSWR(`/closing/${closingId}/perhitungan_sapronak`, () => {
|
||||||
|
const numericId = parseInt(closingId ?? '', 10);
|
||||||
|
if (isNaN(numericId) || numericId <= 0) {
|
||||||
|
throw new Error('Invalid closing ID');
|
||||||
|
}
|
||||||
|
const res = ClosingApi.getPerhitunganSapronak(numericId);
|
||||||
|
console.log(res);
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
|
||||||
if (!closingId) {
|
if (!closingId) {
|
||||||
router.back();
|
router.back();
|
||||||
|
|
||||||
@@ -34,24 +49,34 @@ const ClosingDetailPage = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isLoadingClosing && (!closing || isResponseError(closing))) {
|
|
||||||
router.replace('/404');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full p-4'>
|
<div className='w-full p-4'>
|
||||||
{isLoadingClosing && (
|
<Tabs
|
||||||
<div className='w-full flex flex-row justify-center items-center'>
|
activeTabId={activeTab}
|
||||||
<span className='loading loading-spinner loading-xl' />
|
onTabChange={setActiveTab}
|
||||||
</div>
|
variant='lifted'
|
||||||
)}
|
className={{
|
||||||
{!isLoadingClosing && isResponseSuccess(closing) && (
|
wrapper: 'mx-auto mt-4',
|
||||||
<>
|
}}
|
||||||
<SalesReportTable type='detail' initialValues={closing.data} />
|
tabs={[
|
||||||
<SapronakCalculationTable />
|
{
|
||||||
</>
|
id: 'perhitungan_sapronak',
|
||||||
)}
|
label: 'Perhitungan Sapronak',
|
||||||
|
content: isResponseSuccess(sapronakCalculation) && (
|
||||||
|
<SapronakCalculationTable
|
||||||
|
initialValues={sapronakCalculation.data}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'penjualan',
|
||||||
|
label: 'Penjualan',
|
||||||
|
content: isResponseSuccess(closing) && (
|
||||||
|
<SalesReportTable type='detail' initialValues={closing.data} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,310 @@
|
|||||||
const SapronakCalculationTable = () => {
|
'use client';
|
||||||
return <div>SapronakCalculationTable</div>;
|
|
||||||
|
import Card from '@/components/Card';
|
||||||
|
|
||||||
|
import Table from '@/components/Table';
|
||||||
|
import { cn, formatCurrency, formatNumber } from '@/lib/helper';
|
||||||
|
import {
|
||||||
|
SapronakCalculation,
|
||||||
|
RowSapronakCalculation,
|
||||||
|
TotalSapronakCalculation,
|
||||||
|
} from '@/types/api/closing/closing';
|
||||||
|
import { ColumnDef } from '@tanstack/react-table';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
interface SapronakCalculationTableProps {
|
||||||
|
type?: 'detail';
|
||||||
|
initialValues?: SapronakCalculation;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FooterSapronakCalculationRow extends RowSapronakCalculation {
|
||||||
|
_isFooter: true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SapronakCalculationTable = ({
|
||||||
|
type,
|
||||||
|
initialValues,
|
||||||
|
}: SapronakCalculationTableProps) => {
|
||||||
|
const columns: ColumnDef<RowSapronakCalculation>[] = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
header: 'Tanggal',
|
||||||
|
accessorKey: 'tanggal',
|
||||||
|
cell: (props) => {
|
||||||
|
const isFooter = '_isFooter' in props.row.original;
|
||||||
|
if (isFooter) return null;
|
||||||
|
const value = props.getValue() as string;
|
||||||
|
return value || '-';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'No. Referensi',
|
||||||
|
accessorKey: 'no_referensi',
|
||||||
|
cell: (props) => {
|
||||||
|
const isFooter = '_isFooter' in props.row.original;
|
||||||
|
const value = props.getValue() as string;
|
||||||
|
if (isFooter) {
|
||||||
|
return (
|
||||||
|
<div className='font-semibold text-gray-900 col-span-2'>
|
||||||
|
{value}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return value || '-';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'QTY Masuk',
|
||||||
|
accessorKey: 'qty_masuk',
|
||||||
|
cell: (props) => {
|
||||||
|
const value = props.getValue() as number;
|
||||||
|
const isFooter = '_isFooter' in props.row.original;
|
||||||
|
return (
|
||||||
|
<div className={isFooter ? 'font-semibold text-gray-900' : ''}>
|
||||||
|
{formatNumber(value)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'QTY Keluar',
|
||||||
|
accessorKey: 'qty_keluar',
|
||||||
|
cell: (props) => {
|
||||||
|
const value = props.getValue() as number;
|
||||||
|
const isFooter = '_isFooter' in props.row.original;
|
||||||
|
return (
|
||||||
|
<div className={isFooter ? 'font-semibold text-gray-900' : ''}>
|
||||||
|
{formatNumber(value)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'QTY Pakai',
|
||||||
|
accessorKey: 'qty_pakai',
|
||||||
|
cell: (props) => {
|
||||||
|
const value = props.getValue() as number;
|
||||||
|
const isFooter = '_isFooter' in props.row.original;
|
||||||
|
return (
|
||||||
|
<div className={isFooter ? 'font-semibold text-gray-900' : ''}>
|
||||||
|
{formatNumber(value)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Uraian',
|
||||||
|
accessorKey: 'uraian',
|
||||||
|
cell: (props) => {
|
||||||
|
const isFooter = '_isFooter' in props.row.original;
|
||||||
|
if (isFooter) return null;
|
||||||
|
const value = props.getValue() as string;
|
||||||
|
return value || '-';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Kategori Produk',
|
||||||
|
accessorKey: 'kategori_produk',
|
||||||
|
cell: (props) => {
|
||||||
|
const isFooter = '_isFooter' in props.row.original;
|
||||||
|
if (isFooter) return null;
|
||||||
|
const value = props.getValue() as string;
|
||||||
|
return value || '-';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Harga Beli/Qty (Rp)',
|
||||||
|
accessorKey: 'harga_beli_per_qty',
|
||||||
|
cell: (props) => {
|
||||||
|
const value = props.getValue() as number;
|
||||||
|
const isFooter = '_isFooter' in props.row.original;
|
||||||
|
return (
|
||||||
|
<div className={isFooter ? 'font-semibold text-gray-900' : ''}>
|
||||||
|
{formatCurrency(value)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Total Harga (Rp)',
|
||||||
|
accessorKey: 'total_harga',
|
||||||
|
cell: (props) => {
|
||||||
|
const value = props.getValue() as number;
|
||||||
|
const isFooter = '_isFooter' in props.row.original;
|
||||||
|
return (
|
||||||
|
<div className={isFooter ? 'font-semibold text-gray-900' : ''}>
|
||||||
|
{formatCurrency(value)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Keterangan',
|
||||||
|
accessorKey: 'keterangan',
|
||||||
|
cell: (props) => {
|
||||||
|
const isFooter = '_isFooter' in props.row.original;
|
||||||
|
if (isFooter) return null;
|
||||||
|
const value = props.getValue() as string;
|
||||||
|
return value || '-';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const createFooterRow = (
|
||||||
|
total?: TotalSapronakCalculation
|
||||||
|
): FooterSapronakCalculationRow[] => {
|
||||||
|
if (!total) return [];
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: -999,
|
||||||
|
tanggal: '',
|
||||||
|
no_referensi: total.label,
|
||||||
|
qty_masuk: total.qty_masuk,
|
||||||
|
qty_keluar: total.qty_keluar,
|
||||||
|
qty_pakai: total.qty_pakai,
|
||||||
|
uraian: '',
|
||||||
|
kategori_produk: '',
|
||||||
|
harga_beli_per_qty: total.harga_beli_per_qty,
|
||||||
|
total_harga: total.total_harga,
|
||||||
|
keterangan: '',
|
||||||
|
_isFooter: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const docBroilerFooter = useMemo(
|
||||||
|
() => createFooterRow(initialValues?.doc_broiler.total),
|
||||||
|
[initialValues?.doc_broiler.total]
|
||||||
|
);
|
||||||
|
|
||||||
|
const ovkFooter = useMemo(
|
||||||
|
() => createFooterRow(initialValues?.ovk.total),
|
||||||
|
[initialValues?.ovk.total]
|
||||||
|
);
|
||||||
|
|
||||||
|
const pakanFooter = useMemo(
|
||||||
|
() => createFooterRow(initialValues?.pakan.total),
|
||||||
|
[initialValues?.pakan.total]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col gap-4'>
|
||||||
|
<>
|
||||||
|
<Card
|
||||||
|
title='DOC Broiler'
|
||||||
|
variant='bordered'
|
||||||
|
collapsible
|
||||||
|
defaultCollapsed={false}
|
||||||
|
className={{
|
||||||
|
wrapper: 'w-full',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Table<RowSapronakCalculation>
|
||||||
|
data={initialValues?.doc_broiler.rows ?? []}
|
||||||
|
columns={columns}
|
||||||
|
footerData={docBroilerFooter}
|
||||||
|
renderFooter={
|
||||||
|
(initialValues?.doc_broiler.rows.length ?? 0) > 0 &&
|
||||||
|
!!initialValues?.doc_broiler.total
|
||||||
|
}
|
||||||
|
className={{
|
||||||
|
containerClassName: cn({
|
||||||
|
'mb-20': initialValues?.doc_broiler.rows.length === 0,
|
||||||
|
}),
|
||||||
|
tableWrapperClassName: 'overflow-x-auto min-h-full!',
|
||||||
|
tableClassName: 'font-inter w-full table-auto min-h-full!',
|
||||||
|
headerRowClassName: 'border-b border-b-gray-200',
|
||||||
|
headerColumnClassName:
|
||||||
|
'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end',
|
||||||
|
bodyRowClassName: 'border-b border-b-gray-200',
|
||||||
|
bodyColumnClassName:
|
||||||
|
'px-6 py-3 last:flex last:flex-row last:justify-end',
|
||||||
|
tableFooterClassName:
|
||||||
|
'bg-gray-100 font-semibold border border-gray-200',
|
||||||
|
footerRowClassName: 'border-t-2 border-gray-300',
|
||||||
|
footerColumnClassName: 'px-6 py-3 text-xs text-gray-900',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card
|
||||||
|
title='OVK'
|
||||||
|
variant='bordered'
|
||||||
|
collapsible
|
||||||
|
defaultCollapsed={true}
|
||||||
|
className={{
|
||||||
|
wrapper: 'w-full',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Table<RowSapronakCalculation>
|
||||||
|
data={initialValues?.ovk.rows ?? []}
|
||||||
|
columns={columns}
|
||||||
|
footerData={ovkFooter}
|
||||||
|
renderFooter={
|
||||||
|
(initialValues?.ovk.rows.length ?? 0) > 0 &&
|
||||||
|
!!initialValues?.ovk.total
|
||||||
|
}
|
||||||
|
className={{
|
||||||
|
containerClassName: cn({
|
||||||
|
'mb-20': initialValues?.ovk.rows.length === 0,
|
||||||
|
}),
|
||||||
|
tableWrapperClassName: 'overflow-x-auto min-h-full!',
|
||||||
|
tableClassName: 'font-inter w-full table-auto min-h-full!',
|
||||||
|
headerRowClassName: 'border-b border-b-gray-200',
|
||||||
|
headerColumnClassName:
|
||||||
|
'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end',
|
||||||
|
bodyRowClassName: 'border-b border-b-gray-200',
|
||||||
|
bodyColumnClassName:
|
||||||
|
'px-6 py-3 last:flex last:flex-row last:justify-end',
|
||||||
|
tableFooterClassName:
|
||||||
|
'bg-gray-100 font-semibold border border-gray-200',
|
||||||
|
footerRowClassName: 'border-t-2 border-gray-300',
|
||||||
|
footerColumnClassName: 'px-6 py-3 text-xs text-gray-900',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card
|
||||||
|
title='Pakan'
|
||||||
|
variant='bordered'
|
||||||
|
collapsible
|
||||||
|
defaultCollapsed={true}
|
||||||
|
className={{
|
||||||
|
wrapper: 'w-full',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Table<RowSapronakCalculation>
|
||||||
|
data={initialValues?.pakan.rows ?? []}
|
||||||
|
columns={columns}
|
||||||
|
footerData={pakanFooter}
|
||||||
|
renderFooter={
|
||||||
|
(initialValues?.pakan.rows.length ?? 0) > 0 &&
|
||||||
|
!!initialValues?.pakan.total
|
||||||
|
}
|
||||||
|
className={{
|
||||||
|
containerClassName: cn({
|
||||||
|
'mb-20': initialValues?.pakan.rows.length === 0,
|
||||||
|
}),
|
||||||
|
tableWrapperClassName: 'overflow-x-auto min-h-full!',
|
||||||
|
tableClassName: 'font-inter w-full table-auto min-h-full!',
|
||||||
|
headerRowClassName: 'border-b border-b-gray-200',
|
||||||
|
headerColumnClassName:
|
||||||
|
'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end',
|
||||||
|
bodyRowClassName: 'border-b border-b-gray-200',
|
||||||
|
bodyColumnClassName:
|
||||||
|
'px-6 py-3 last:flex last:flex-row last:justify-end',
|
||||||
|
tableFooterClassName:
|
||||||
|
'bg-gray-100 font-semibold border border-gray-200',
|
||||||
|
footerRowClassName: 'border-t-2 border-gray-300',
|
||||||
|
footerColumnClassName: 'px-6 py-3 text-xs text-gray-900',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SapronakCalculationTable;
|
export default SapronakCalculationTable;
|
||||||
|
|||||||
@@ -0,0 +1,225 @@
|
|||||||
|
import { SapronakCalculation } from '@/types/api/closing/closing';
|
||||||
|
|
||||||
|
// Dummy data
|
||||||
|
const DUMMY_SAPRONAK_CALCULATION: SapronakCalculation = {
|
||||||
|
doc_broiler: {
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
tanggal: '11-Sep-2025',
|
||||||
|
no_referensi: 'PO-PULLET-388',
|
||||||
|
qty_masuk: 32800,
|
||||||
|
qty_keluar: 0,
|
||||||
|
qty_pakai: 32800,
|
||||||
|
uraian: 'PULLET LOHMANN (16 MINGGU)',
|
||||||
|
kategori_produk: 'PULLET LAYER',
|
||||||
|
harga_beli_per_qty: 60136,
|
||||||
|
total_harga: 1972556800,
|
||||||
|
keterangan: '-',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
tanggal: '24-Sep-2025',
|
||||||
|
no_referensi: 'PO-PULLET-410',
|
||||||
|
qty_masuk: 14758,
|
||||||
|
qty_keluar: 0,
|
||||||
|
qty_pakai: 14758,
|
||||||
|
uraian: 'PULLET HY-LINE (17 MINGGU)',
|
||||||
|
kategori_produk: 'PULLET LAYER',
|
||||||
|
harga_beli_per_qty: 65421,
|
||||||
|
total_harga: 965908998,
|
||||||
|
keterangan: '-',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
tanggal: '29-Sep-2025',
|
||||||
|
no_referensi: 'PO-PULLET-196',
|
||||||
|
qty_masuk: 35439,
|
||||||
|
qty_keluar: 0,
|
||||||
|
qty_pakai: 35439,
|
||||||
|
uraian: 'PULLET ISA BROWN (15 MINGGU)',
|
||||||
|
kategori_produk: 'PULLET LAYER',
|
||||||
|
harga_beli_per_qty: 55909,
|
||||||
|
total_harga: 1981297351,
|
||||||
|
keterangan: '-',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
total: {
|
||||||
|
label: 'TOTAL PULLET',
|
||||||
|
qty_masuk: 82997,
|
||||||
|
qty_keluar: 0,
|
||||||
|
qty_pakai: 82997,
|
||||||
|
harga_beli_per_qty: 59271.85,
|
||||||
|
total_harga: 4919763149,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ovk: {
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
tanggal: '28-Sep-2025',
|
||||||
|
no_referensi: 'PO-OVK-276',
|
||||||
|
qty_masuk: 52,
|
||||||
|
qty_keluar: 0,
|
||||||
|
qty_pakai: 52,
|
||||||
|
uraian: 'ND-IB VACCINE',
|
||||||
|
kategori_produk: 'OVK VAKSIN',
|
||||||
|
harga_beli_per_qty: 204652,
|
||||||
|
total_harga: 10641904,
|
||||||
|
keterangan: 'Program kesehatan & biosecurity',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
tanggal: '26-Sep-2025',
|
||||||
|
no_referensi: 'PO-OVK-811',
|
||||||
|
qty_masuk: 43,
|
||||||
|
qty_keluar: 0,
|
||||||
|
qty_pakai: 43,
|
||||||
|
uraian: 'GUMBORO VACCINE',
|
||||||
|
kategori_produk: 'OVK VAKSIN',
|
||||||
|
harga_beli_per_qty: 298379,
|
||||||
|
total_harga: 12830297,
|
||||||
|
keterangan: 'Program kesehatan & biosecurity',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
tanggal: '28-Sep-2025',
|
||||||
|
no_referensi: 'PO-OVK-879',
|
||||||
|
qty_masuk: 21,
|
||||||
|
qty_keluar: 0,
|
||||||
|
qty_pakai: 21,
|
||||||
|
uraian: 'AMOXITIN SOLUBLE',
|
||||||
|
kategori_produk: 'OVK OBAT',
|
||||||
|
harga_beli_per_qty: 145952,
|
||||||
|
total_harga: 3064992,
|
||||||
|
keterangan: 'Program kesehatan & biosecurity',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
tanggal: '11-Okt-2025',
|
||||||
|
no_referensi: 'PO-OVK-340',
|
||||||
|
qty_masuk: 38,
|
||||||
|
qty_keluar: 0,
|
||||||
|
qty_pakai: 38,
|
||||||
|
uraian: 'TILOXIN SOLUBLE',
|
||||||
|
kategori_produk: 'OVK OBAT',
|
||||||
|
harga_beli_per_qty: 200424,
|
||||||
|
total_harga: 7616112,
|
||||||
|
keterangan: 'Program kesehatan & biosecurity',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
tanggal: '27-Sep-2025',
|
||||||
|
no_referensi: 'PO-OVK-364',
|
||||||
|
qty_masuk: 7,
|
||||||
|
qty_keluar: 0,
|
||||||
|
qty_pakai: 7,
|
||||||
|
uraian: 'EGG STIMULANT',
|
||||||
|
kategori_produk: 'OVK VITAMIN',
|
||||||
|
harga_beli_per_qty: 115024,
|
||||||
|
total_harga: 805168,
|
||||||
|
keterangan: 'Program kesehatan & biosecurity',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
tanggal: '16-Sep-2025',
|
||||||
|
no_referensi: 'PO-OVK-982',
|
||||||
|
qty_masuk: 57,
|
||||||
|
qty_keluar: 0,
|
||||||
|
qty_pakai: 57,
|
||||||
|
uraian: 'MULTIVIT-AMINO',
|
||||||
|
kategori_produk: 'OVK VITAMIN',
|
||||||
|
harga_beli_per_qty: 65123,
|
||||||
|
total_harga: 3712011,
|
||||||
|
keterangan: 'Program kesehatan & biosecurity',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
tanggal: '04-Okt-2025',
|
||||||
|
no_referensi: 'PO-OVK-876',
|
||||||
|
qty_masuk: 4,
|
||||||
|
qty_keluar: 0,
|
||||||
|
qty_pakai: 4,
|
||||||
|
uraian: 'BKC DESINFEKTAN',
|
||||||
|
kategori_produk: 'OVK KIMIA',
|
||||||
|
harga_beli_per_qty: 105677,
|
||||||
|
total_harga: 422708,
|
||||||
|
keterangan: 'Program kesehatan & biosecurity',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
total: {
|
||||||
|
label: 'TOTAL OVK',
|
||||||
|
qty_masuk: 222,
|
||||||
|
qty_keluar: 0,
|
||||||
|
qty_pakai: 222,
|
||||||
|
harga_beli_per_qty: 176096.36,
|
||||||
|
total_harga: 39093192,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pakan: {
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
tanggal: '13-Ags-2025',
|
||||||
|
no_referensi: 'PO-FEED-730',
|
||||||
|
qty_masuk: 4833,
|
||||||
|
qty_keluar: 0,
|
||||||
|
qty_pakai: 4833,
|
||||||
|
uraian: 'FEED PRE-LAY',
|
||||||
|
kategori_produk: 'PAKAN PRE-LAY',
|
||||||
|
harga_beli_per_qty: 7578,
|
||||||
|
total_harga: 36625874,
|
||||||
|
keterangan: 'Konsumsi pakan kandang layer',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
tanggal: '28-Jul-2025',
|
||||||
|
no_referensi: 'PO-FEED-555',
|
||||||
|
qty_masuk: 6500,
|
||||||
|
qty_keluar: 0,
|
||||||
|
qty_pakai: 6500,
|
||||||
|
uraian: 'FEED LAYER PHASE 1',
|
||||||
|
kategori_produk: 'PAKAN LAYER',
|
||||||
|
harga_beli_per_qty: 8116,
|
||||||
|
total_harga: 52754000,
|
||||||
|
keterangan: 'Konsumsi pakan kandang layer',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
tanggal: '24-Agu-2025',
|
||||||
|
no_referensi: 'PO-FEED-683',
|
||||||
|
qty_masuk: 8802,
|
||||||
|
qty_keluar: 0,
|
||||||
|
qty_pakai: 8802,
|
||||||
|
uraian: 'FEED LAYER PHASE 2',
|
||||||
|
kategori_produk: 'PAKAN LAYER',
|
||||||
|
harga_beli_per_qty: 8801,
|
||||||
|
total_harga: 77465402,
|
||||||
|
keterangan: 'Konsumsi pakan kandang layer',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
tanggal: '02-Sep-2025',
|
||||||
|
no_referensi: 'PO-FEED-448',
|
||||||
|
qty_masuk: 2185,
|
||||||
|
qty_keluar: 0,
|
||||||
|
qty_pakai: 2185,
|
||||||
|
uraian: 'JAGUNG GILING',
|
||||||
|
kategori_produk: 'PAKAN MIX',
|
||||||
|
harga_beli_per_qty: 5573,
|
||||||
|
total_harga: 12187705,
|
||||||
|
keterangan: 'Konsumsi pakan kandang layer',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
total: {
|
||||||
|
label: 'TOTAL PAKAN',
|
||||||
|
qty_masuk: 22320,
|
||||||
|
qty_keluar: 0,
|
||||||
|
qty_pakai: 22320,
|
||||||
|
harga_beli_per_qty: 8020.93,
|
||||||
|
total_harga: 179032981,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DUMMY_SAPRONAK_CALCULATION;
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
|
import DUMMY_SAPRONAK_CALCULATION from '@/dummy/closing.dummy';
|
||||||
import { BaseApiService } from './base';
|
import { BaseApiService } from './base';
|
||||||
import { BaseApiResponse } from '@/types/api/api-general';
|
import { BaseApiResponse } from '@/types/api/api-general';
|
||||||
import { ClosingSales } from '@/types/api/closing/closing';
|
import { ClosingSales, SapronakCalculation } from '@/types/api/closing/closing';
|
||||||
|
|
||||||
export class ClosingApiService extends BaseApiService<
|
export class ClosingApiService extends BaseApiService<
|
||||||
ClosingSales,
|
ClosingSales,
|
||||||
@@ -23,6 +24,38 @@ export class ClosingApiService extends BaseApiService<
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getPerhitunganSapronak(
|
||||||
|
projectFlockId: number
|
||||||
|
): Promise<BaseApiResponse<SapronakCalculation> | undefined> {
|
||||||
|
// Dummy implementation - simulate API call with delay
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve({
|
||||||
|
code: 200,
|
||||||
|
status: 'success',
|
||||||
|
message: 'Retrieved sapronak calculation successfully',
|
||||||
|
data: DUMMY_SAPRONAK_CALCULATION,
|
||||||
|
});
|
||||||
|
}, 500); // Simulate 500ms network delay
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Real API implementation - uncomment when backend is ready
|
||||||
|
try {
|
||||||
|
const path = `${this.basePath}/${projectFlockId}/perhitungan_sapronak`;
|
||||||
|
|
||||||
|
return await httpClient<BaseApiResponse<SapronakCalculation>>(path, {
|
||||||
|
method: 'GET',
|
||||||
|
});
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (axios.isAxiosError<BaseApiResponse<SapronakCalculation>>(error)) {
|
||||||
|
return error.response?.data;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ClosingApi = new ClosingApiService('/closings');
|
export const ClosingApi = new ClosingApiService('/closings');
|
||||||
|
|||||||
Vendored
+36
@@ -27,3 +27,39 @@ export type BaseClosingSales = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type ClosingSales = BaseMetadata & BaseClosingSales;
|
export type ClosingSales = BaseMetadata & BaseClosingSales;
|
||||||
|
|
||||||
|
// ====== PERHITUNGAN SAPRONAK ======
|
||||||
|
|
||||||
|
export type RowSapronakCalculation = {
|
||||||
|
id: number;
|
||||||
|
tanggal: string;
|
||||||
|
no_referensi: string;
|
||||||
|
qty_masuk: number;
|
||||||
|
qty_keluar: number;
|
||||||
|
qty_pakai: number;
|
||||||
|
uraian: string;
|
||||||
|
kategori_produk: string;
|
||||||
|
harga_beli_per_qty: number;
|
||||||
|
total_harga: number;
|
||||||
|
keterangan: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TotalSapronakCalculation = {
|
||||||
|
label: string;
|
||||||
|
qty_masuk: number;
|
||||||
|
qty_keluar: number;
|
||||||
|
qty_pakai: number;
|
||||||
|
harga_beli_per_qty: number;
|
||||||
|
total_harga: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SapronakCalculationItem = {
|
||||||
|
rows: RowSapronakCalculation[];
|
||||||
|
total: TotalSapronakCalculation;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SapronakCalculation = {
|
||||||
|
doc_broiler: SapronakCalculationItem;
|
||||||
|
ovk: SapronakCalculationItem;
|
||||||
|
pakan: SapronakCalculationItem;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user