diff --git a/src/app/production/project-flock/closing/page.tsx b/src/app/production/project-flock/closing/page.tsx index d734f669..d10bdfa2 100644 --- a/src/app/production/project-flock/closing/page.tsx +++ b/src/app/production/project-flock/closing/page.tsx @@ -14,13 +14,13 @@ const ProjectFlockClosingPage = () => { const projectFlockKandangId = searchParams.get('projectFlockKandangId'); const { data: projectFlockKandang, isLoading: isLoadingProjectFlockKandang } = - useSWR(projectFlockKandangId, (id: number) => - ProjectFlockKandangApi.getSingle(id) + useSWR(`get-flock-kandang-id/${projectFlockKandangId}`, () => + ProjectFlockKandangApi.getSingle(parseInt(projectFlockKandangId ?? '')) ); const { data: projectFlock, isLoading: isLoadingProjectFlock } = useSWR( - projectFlockId, - (id: number) => ProjectFlockApi.getSingle(id) + `get-flock-id/${projectFlockId}`, + () => ProjectFlockApi.getSingle(parseInt(projectFlockId ?? '')) ); if (!projectFlockId || !projectFlockKandangId) { diff --git a/src/app/production/project-flock/layout.tsx b/src/app/production/project-flock/layout.tsx index b74ef612..3e9a65b7 100644 --- a/src/app/production/project-flock/layout.tsx +++ b/src/app/production/project-flock/layout.tsx @@ -15,7 +15,7 @@ export default function ProjectFlockLayout({ const router = useRouter(); const toggleValidate = useUiStore((s) => s.toggleValidate); - const isAdd = pathname.endsWith('/add'); + const isAdd = pathname.includes('/add'); const isEdit = pathname.includes('/detail/edit'); const isDetail = pathname.includes('/detail'); const isChickin = pathname.includes('/chickin/add/kandang'); diff --git a/src/app/production/recording/detail/edit/page.tsx b/src/app/production/recording/detail/edit/page.tsx index de53a354..ad6c6a9a 100644 --- a/src/app/production/recording/detail/edit/page.tsx +++ b/src/app/production/recording/detail/edit/page.tsx @@ -14,7 +14,7 @@ const RecordingEdit = () => { const { data: recording, isLoading: isLoadingRecording } = useSWR( recordingId, - (id: number) => RecordingApi.getSingle(id) // Gunakan RecordingApi + (id: string) => RecordingApi.getSingle(parseInt(id)) ); if (!recordingId) { diff --git a/src/app/production/recording/detail/page.tsx b/src/app/production/recording/detail/page.tsx index 77b82a68..194365a3 100644 --- a/src/app/production/recording/detail/page.tsx +++ b/src/app/production/recording/detail/page.tsx @@ -14,7 +14,7 @@ const RecordingDetail = () => { const { data: recording, isLoading: isLoadingRecording } = useSWR( recordingId, - (id: number) => RecordingApi.getSingle(id) + (id: string) => RecordingApi.getSingle(parseInt(id)) ); if (!recordingId) { diff --git a/src/components/helper/RequireAuth.tsx b/src/components/helper/RequireAuth.tsx index 65adf48c..dbd4b6bc 100644 --- a/src/components/helper/RequireAuth.tsx +++ b/src/components/helper/RequireAuth.tsx @@ -1,87 +1,197 @@ 'use client'; import { ReactNode, useEffect } from 'react'; -import useSWR from 'swr'; +import { useRouter } from 'next/navigation'; +import useSWRImmutable from 'swr/immutable'; import { useAuth } from '@/services/hooks/useAuth'; import { httpClientFetcher, SWRHttpKey } from '@/services/http/client'; -import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; -import { BaseApiResponse, GetMeResponse } from '@/types/api/api-general'; -import { AxiosError } from 'axios'; -import { redirectToSSO } from '@/lib/auth-helper'; +import { isResponseSuccess } from '@/lib/api-helper'; +import { GetMeResponse } from '@/types/api/api-general'; + +// TODO: delete this later, DONT HARDCODE USER DATA +const DUMMY_USER = { + id: 1, + email: 'admin@mbugroup.id', + npk: '0001', + name: 'Super Admin', + image: null, + created_at: '2025-09-30T03:24:20.899229Z', + updated_at: '2025-09-30T03:24:20.899229Z', + roles: [ + { + id: 1, + key: 'mbu.super_admin', + name: 'MBU Administrator', + client: { + id: 1, + name: 'PT Mitra Berlian Unggas', + alias: 'MBU', + }, + permissions: [ + { + id: 1, + name: 'mbu:purchase:read', + action: 'read', + client: { + id: 1, + name: 'PT Mitra Berlian Unggas', + alias: 'MBU', + }, + }, + { + id: 2, + name: 'mbu:purchase:create', + action: 'create', + client: { + id: 1, + name: 'PT Mitra Berlian Unggas', + alias: 'MBU', + }, + }, + { + id: 3, + name: 'mbu:purchase:approve', + action: 'approve', + client: { + id: 1, + name: 'PT Mitra Berlian Unggas', + alias: 'MBU', + }, + }, + ], + }, + { + id: 2, + key: 'lti.super_admin', + name: 'LTI Administrator', + client: { + id: 2, + name: 'PT Lumbung Telur Indonesia', + alias: 'LTI', + }, + permissions: [ + { + id: 4, + name: 'lti:purchase:read', + action: 'read', + client: { + id: 2, + name: 'PT Lumbung Telur Indonesia', + alias: 'LTI', + }, + }, + { + id: 5, + name: 'lti:purchase:create', + action: 'create', + client: { + id: 2, + name: 'PT Lumbung Telur Indonesia', + alias: 'LTI', + }, + }, + { + id: 6, + name: 'lti:purchase:approve', + action: 'approve', + client: { + id: 2, + name: 'PT Lumbung Telur Indonesia', + alias: 'LTI', + }, + }, + ], + }, + { + id: 3, + key: 'manbu.super_admin', + name: 'MANBU Administrator', + client: { + id: 3, + name: 'PT Mandiri Berlian Unggas', + alias: 'MANBU', + }, + permissions: [ + { + id: 7, + name: 'manbu:purchase:read', + action: 'read', + client: { + id: 3, + name: 'PT Mandiri Berlian Unggas', + alias: 'MANBU', + }, + }, + { + id: 8, + name: 'manbu:purchase:create', + action: 'create', + client: { + id: 3, + name: 'PT Mandiri Berlian Unggas', + alias: 'MANBU', + }, + }, + { + id: 9, + name: 'manbu:purchase:approve', + action: 'approve', + client: { + id: 3, + name: 'PT Mandiri Berlian Unggas', + alias: 'MANBU', + }, + }, + ], + }, + ], +}; interface RequireAuthProps { children?: ReactNode; } const RequireAuth = ({ children }: RequireAuthProps) => { - const { user, setUser, setIsLoadingUser } = useAuth(); + const router = useRouter(); + const { setUser, setIsLoadingUser } = useAuth(); - const { - data: userResponse, - isLoading: isLoadingUserResponse, - error: userErrorResponse, - } = useSWR< - GetMeResponse & { ok?: boolean }, - AxiosError, - SWRHttpKey - >('/sso/userinfo', httpClientFetcher, { - shouldRetryOnError: false, - }); + const { data: userResponse, isLoading: isLoadingUserResponse } = + useSWRImmutable( + '/auth/sso/userinfo', + httpClientFetcher, + { + shouldRetryOnError: false, + revalidateOnFocus: false, + revalidateOnReconnect: false, + refreshInterval: 0, + } + ); + + useEffect(() => { + setIsLoadingUser(isLoadingUserResponse); + }, [isLoadingUserResponse, setIsLoadingUser]); useEffect(() => { if (isResponseSuccess(userResponse)) { setUser(userResponse.data); + } else { + // router.replace(process.env.NEXT_PUBLIC_SSO_LOGIN_URL as string); + // TODO: remove this later, DONT HARDCODE USER DATA + setUser(DUMMY_USER); } - }, [userResponse, setUser]); + }, [userResponse, setIsLoadingUser, setUser]); - // Explicitly handle 401 redirect from the component level - useEffect(() => { - if ( - isResponseError(userResponse) && - userErrorResponse?.response?.status === 401 - ) { - // Clear cache to prevent stale data from rendering children - // mutate('/sso/userinfo', undefined, { revalidate: false }); // Optional: if using global mutate - setUser(undefined); - redirectToSSO(); - } - }, [userErrorResponse, setUser, userResponse]); + // TODO: uncomment this later + // if (isLoadingUserResponse && !userResponse) { + // return ( + //
+ // + //
+ // ); + // } - useEffect(() => { - setIsLoadingUser(isLoadingUserResponse); - }, [isLoadingUserResponse]); - - if ( - (isLoadingUserResponse && !userResponse && !userErrorResponse) || - (!userResponse && !userErrorResponse) - ) { - return ( -
- -
- ); - } - - if (userErrorResponse) { - return ( -
-

Authentication Failed

-

- Please try refreshing the page or contact support if the problem - persists. -

- -
- ); - } - - return <>{isResponseSuccess(userResponse) && user && children}; + return <>{children}; }; export default RequireAuth; diff --git a/src/components/pages/ApprovalSteps.tsx b/src/components/pages/ApprovalSteps.tsx index d5dcabc0..6ae7c13a 100644 --- a/src/components/pages/ApprovalSteps.tsx +++ b/src/components/pages/ApprovalSteps.tsx @@ -144,33 +144,45 @@ const ApprovalSteps = ({ approvals }: ApprovalStepsProps) => { export const formatGroupedApprovalsToApprovalSteps = ( approvalLine: ApprovalLine, - groupedApprovals: BaseGroupedApproval[], - latestApproval: BaseApproval + groupedApprovals: BaseGroupedApproval[] | undefined, + latestApproval: BaseApproval | undefined ): ApprovalStepsProps['approvals'] => { const formattedApprovalSteps: ApprovalStepsProps['approvals'] = approvalLine.map((approvalLineItem) => { - const approvalGroup = groupedApprovals.find( + const approvalGroup = groupedApprovals?.find( (approvalGroupItem) => approvalGroupItem.step_number === approvalLineItem.step_number ); const currentStepNumber = approvalLineItem.step_number; const lastStepNumber = - groupedApprovals[groupedApprovals.length - 1]?.step_number; + groupedApprovals?.[groupedApprovals.length - 1]?.step_number; - const isLatestApprovalRejected = latestApproval.action === 'REJECTED'; + const isLatestApprovalRejected = latestApproval?.action === 'REJECTED'; - if (!approvalGroup && currentStepNumber <= lastStepNumber) { - throw new Error( - `Approval dengan ${approvalLineItem.step_name} tidak ditemukan!` - ); + // Only throw error if we have a valid lastStepNumber to compare against + if ( + !approvalGroup && + lastStepNumber !== undefined && + currentStepNumber <= lastStepNumber + ) { + // throw new Error( + // `Approval dengan ${approvalLineItem.step_name} tidak ditemukan!` + // ); } if (!approvalGroup) { - const isWaiting = currentStepNumber === latestApproval.step_number + 1; + // Check if this step is waiting (only if we have latestApproval) + const isWaiting = + latestApproval?.step_number !== undefined && + currentStepNumber === latestApproval.step_number + 1; + + // Check if previous approval was rejected const isPreviousApprovalRejected = - groupedApprovals[groupedApprovals.length - 1].approvals[0].action === - 'REJECTED'; + groupedApprovals && + groupedApprovals.length > 0 && + groupedApprovals[groupedApprovals.length - 1]?.approvals?.[0] + ?.action === 'REJECTED'; return { name: approvalLineItem.step_name, @@ -184,7 +196,11 @@ export const formatGroupedApprovalsToApprovalSteps = ( let approvalStatus: ApprovalStepStatus = 'IDLE'; - if (approvalGroup.step_number <= latestApproval.step_number) { + // Only compare if latestApproval and its step_number exist + if ( + latestApproval?.step_number !== undefined && + approvalGroup.step_number <= latestApproval.step_number + ) { if (approvalGroup.approvals) { switch (approvalGroup?.approvals[0]?.action) { case 'CREATED': @@ -203,6 +219,7 @@ export const formatGroupedApprovalsToApprovalSteps = ( } } } else if ( + latestApproval?.step_number !== undefined && approvalGroup.step_number === latestApproval.step_number + 1 && !isLatestApprovalRejected ) { @@ -353,14 +370,33 @@ const useApprovalSteps = ({ // Formatting Akhir const approvals = useMemo(() => { - if (isLoading || !approvalLines.length || !latestApproval) { + if (isLoading || !approvalLines.length) { return []; } + + // Try to derive latestApproval from groupedApprovals if not provided + let effectiveLatestApproval = latestApproval; + + if (!effectiveLatestApproval && groupedApprovals.length > 0) { + // Get all approvals from grouped data + const allApprovals = groupedApprovals.flatMap((group) => group.approvals); + + if (allApprovals.length > 0) { + // Use the most recent approval (last in array) + effectiveLatestApproval = allApprovals[allApprovals.length - 1]; + } + } + + // If still no latestApproval, return empty + if (!effectiveLatestApproval) { + return []; + } + try { return formatGroupedApprovalsToApprovalSteps( approvalLines, groupedApprovals, - latestApproval + effectiveLatestApproval ); } catch (error) { console.warn('Gagal memformat approval steps:', error); diff --git a/src/components/pages/closing/ClosingDetail.tsx b/src/components/pages/closing/ClosingDetail.tsx index fd4c9182..fd88fa49 100644 --- a/src/components/pages/closing/ClosingDetail.tsx +++ b/src/components/pages/closing/ClosingDetail.tsx @@ -13,6 +13,7 @@ import { } from '@/types/api/closing'; import ClosingSapronakTabContent from './ClosingSapronakTabContent'; import ClosingSapronakCalculationTabContent from '@/components/pages/closing/ClosingSapronakCalculationTabContent'; +import ClosingOverheadTabContent from '@/components/pages/closing/ClosingOverheadTabContent'; import SalesReportTable from './sale/SalesReportTable'; interface ClosingDetailProps { @@ -48,7 +49,7 @@ const ClosingDetail: React.FC = ({ { id: 'overhead', label: 'Overhead', - content: 'Overhead', + content: , }, { id: 'hppEkspedisi', diff --git a/src/components/pages/closing/ClosingOverheadTabContent.tsx b/src/components/pages/closing/ClosingOverheadTabContent.tsx new file mode 100644 index 00000000..458cff0f --- /dev/null +++ b/src/components/pages/closing/ClosingOverheadTabContent.tsx @@ -0,0 +1,19 @@ +import ClosingOverheadTable from '@/components/pages/closing/ClosingOverheadTable'; + +interface ClosingOverheadTabContentProps { + projectFlockId: number; +} + +const ClosingOverheadTabContent = ({ + projectFlockId, +}: ClosingOverheadTabContentProps) => { + return ( +
+ {projectFlockId && ( + + )} +
+ ); +}; + +export default ClosingOverheadTabContent; diff --git a/src/components/pages/closing/ClosingOverheadTable.tsx b/src/components/pages/closing/ClosingOverheadTable.tsx new file mode 100644 index 00000000..3df0844d --- /dev/null +++ b/src/components/pages/closing/ClosingOverheadTable.tsx @@ -0,0 +1,162 @@ +import Card from '@/components/Card'; +import Table, { TABLE_DEFAULT_STYLING } from '@/components/Table'; +import { isResponseSuccess } from '@/lib/api-helper'; +import { cn, formatCurrency, formatDate, formatNumber } from '@/lib/helper'; +import { ClosingApi } from '@/services/api/closing'; +import { Overhead, OverheadTotal } from '@/types/api/closing'; +import { ColumnDef } from '@tanstack/react-table'; +import { useMemo } from 'react'; +import useSWR from 'swr'; + +interface ClosingOverheadTableProps { + type?: 'detail'; + projectFlockId: number; +} + +const ClosingOverheadTable = ({ + type, + projectFlockId, +}: ClosingOverheadTableProps) => { + const { data: overhead, isLoading: isLoadingOverhead } = useSWR( + `${ClosingApi.basePath}/${projectFlockId}/overhead`, + () => ClosingApi.getOverhead(projectFlockId), + { + keepPreviousData: true, + } + ); + + // Helper function to create columns with footer support + const createColumns = (total?: OverheadTotal): ColumnDef[] => [ + // Group untuk kolom tanpa footer + { + header: 'Nama Item', + accessorFn: (props) => props.item_name, + footer: 'Total Pengeluaran Overhead', + }, + { + header: 'Satuan', + accessorFn: (props) => props.uom_name, + }, + { + header: 'Budget Pengajuan', + footer: '', + columns: [ + { + id: 'budget_quantity', + header: 'Jumlah', + accessorFn: (props) => + props.budget_quantity ? formatNumber(props.budget_quantity) : '-', + footer: total ? () => formatNumber(total.budget_quantity) : '', + }, + { + id: 'budget_unit_price', + header: 'Harga Satuan', + accessorFn: (props) => + props.budget_unit_price + ? formatCurrency(props.budget_unit_price) + : '-', + footer: '', + }, + { + id: 'budget_total_amount', + header: 'Total', + accessorFn: (props) => + props.budget_total_amount + ? formatCurrency(props.budget_total_amount) + : '-', + footer: total ? () => formatCurrency(total.budget_total_amount) : '', + }, + ], + }, + { + header: 'Realisasi', + footer: '', + columns: [ + { + id: 'actual_date', + header: 'Tanggal', + accessorFn: (props) => + props.actual_date + ? formatDate(props.actual_date, 'DD MMM, YYYY') + : '-', + footer: '', + }, + { + id: 'actual_quantity', + header: 'Jumlah', + accessorFn: (props) => + props.actual_quantity ? formatNumber(props.actual_quantity) : '-', + footer: total ? () => formatNumber(total.actual_quantity) : '', + }, + { + id: 'actual_unit_price', + header: 'Harga Satuan', + accessorFn: (props) => + props.actual_unit_price + ? formatCurrency(props.actual_unit_price) + : '-', + footer: '', + }, + { + id: 'actual_total_amount', + header: 'Total', + accessorFn: (props) => + props.actual_total_amount + ? formatCurrency(props.actual_total_amount) + : '-', + footer: total ? () => formatCurrency(total.actual_total_amount) : '', + }, + ], + }, + { + id: 'cost_per_bird', + header: 'Rp/Ekor', + accessorFn: (props) => + props.cost_per_bird ? formatCurrency(props.cost_per_bird) : '-', + footer: total ? () => formatCurrency(total.cost_per_bird) : '', + }, + ]; + + const columns = useMemo( + () => + isResponseSuccess(overhead) + ? createColumns(overhead.data?.total) + : createColumns(), + [overhead] + ); + + return ( + <> + + + data={ + isResponseSuccess(overhead) ? (overhead.data?.overheads ?? []) : [] + } + columns={columns} + className={{ + containerClassName: 'my-4', + headerColumnClassName: cn( + TABLE_DEFAULT_STYLING.headerColumnClassName, + 'whitespace-nowrap' + ), + }} + renderFooter={ + isResponseSuccess(overhead) + ? overhead.data?.overheads.length > 0 + : false + } + /> + + + ); +}; + +export default ClosingOverheadTable; diff --git a/src/components/pages/closing/ClosingSapronakCalculationTable.tsx b/src/components/pages/closing/ClosingSapronakCalculationTable.tsx index 73c10331..445b7d8c 100644 --- a/src/components/pages/closing/ClosingSapronakCalculationTable.tsx +++ b/src/components/pages/closing/ClosingSapronakCalculationTable.tsx @@ -25,7 +25,10 @@ const ClosingSapronakCalculationTable = ({ }: ClosingSapronakCalculationTableProps) => { const { data: sapronakCalculation, isLoading } = useSWR( `/closing/sapronak-calculation/${projectFlockId}`, - () => ClosingApi.getPerhitunganSapronak(projectFlockId) + () => ClosingApi.getPerhitunganSapronak(projectFlockId), + { + keepPreviousData: true, + } ); // Helper function to create columns with footer support diff --git a/src/components/pages/production/chickin/form/ChickinForm.tsx b/src/components/pages/production/chickin/form/ChickinForm.tsx index 84c5b5a5..b6c5a2c0 100644 --- a/src/components/pages/production/chickin/form/ChickinForm.tsx +++ b/src/components/pages/production/chickin/form/ChickinForm.tsx @@ -11,12 +11,12 @@ import { useState } from 'react'; import ApprovalSteps, { useApprovalSteps, } from '@/components/pages/ApprovalSteps'; -import { PROJECT_FLOCK_KANDANG_APPROVAL_LINE } from '@/config/approval-line'; import ChickinFormView from '@/components/pages/production/chickin/form/tabs/ChickinFormView'; import ChickinLogsView from '@/components/pages/production/chickin/form/tabs/ChickLogsView'; import DrawerHeader from '@/components/helper/drawer/DrawerHeader'; import { Icon } from '@iconify/react'; import Badge from '@/components/Badge'; +import { CHICKINS_APPROVAL_LINE } from '@/config/approval-line'; const ChickinFormKandang = ({ formType = 'add', initialValues, @@ -34,8 +34,8 @@ const ChickinFormKandang = ({ refresh: refreshApprovals, } = useApprovalSteps({ latestApproval: initialValues?.approval, - approvalLines: PROJECT_FLOCK_KANDANG_APPROVAL_LINE, - moduleName: 'PROJECT_FLOCK_KANDANGS', + approvalLines: CHICKINS_APPROVAL_LINE, + moduleName: 'CHICKINS', moduleId: initialValues?.id.toString() ?? '', }); diff --git a/src/components/pages/production/chickin/form/tabs/ChickLogsView.tsx b/src/components/pages/production/chickin/form/tabs/ChickLogsView.tsx index 865091d7..99eb1cb3 100644 --- a/src/components/pages/production/chickin/form/tabs/ChickLogsView.tsx +++ b/src/components/pages/production/chickin/form/tabs/ChickLogsView.tsx @@ -145,7 +145,7 @@ const ChickinLogsView = ({ }) )} - {initialValues?.approval?.step_number == 1 && ( + {initialValues?.approval?.step_number <= 2 && ( - {!isApproved && ( + {!isApproved && !isRejected && ( - {type === 'detail' && !isRecordingApproved(initialValues) && ( -
- + {type === 'detail' && + initialValues?.approval && + !isRecordingApproved(initialValues) && + !isRecordingRejected(initialValues) && ( +
+ - -
- )} + +
+ )}

@@ -2803,7 +2818,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { {/* Approve Confirmation Modal */} {(type as 'add' | 'edit' | 'detail') === 'detail' && - !isRecordingApproved(initialValues) && ( + !isRecordingApproved(initialValues) && + !isRecordingRejected(initialValues) && ( | undefined> => { + await new Promise((resolve) => setTimeout(resolve, 400)); + return { + code: 200, + status: 'success', + message: 'Data overhead berhasil diambil', + data: dummyOverhead, + }; +}; diff --git a/src/services/api/closing.ts b/src/services/api/closing.ts index 168579da..0a6ef40c 100644 --- a/src/services/api/closing.ts +++ b/src/services/api/closing.ts @@ -6,6 +6,7 @@ import { ClosingGeneralInformation, ClosingIncomingSapronak, ClosingOutgoingSapronak, + ClosingOverhead, ClosingSapronakCalculation, } from '@/types/api/closing'; import { BaseApiResponse } from '@/types/api/api-general'; @@ -16,6 +17,7 @@ import { dummyGetAllOutgoingSapronakFetcher, dummyGetGeneralInfo, dummyGetPerhitunganSapronak, + dummyGetOverhead, } from '@/dummy/closing.dummy'; import { httpClient, httpClientFetcher } from '@/services/http/client'; import { ClosingSales } from '@/types/api/closing'; @@ -91,12 +93,12 @@ export class ClosingApiService extends BaseApiService { endpoint: string ): Promise> { // TODO: Remove this block when backend is ready - // return await dummyGetAllOutgoingSapronakFetcher(); + return await dummyGetAllOutgoingSapronakFetcher(); // Uncomment this when backend is ready - return await httpClientFetcher>( - endpoint - ); + // return await httpClientFetcher>( + // endpoint + // ); } async getGeneralInfo( @@ -165,6 +167,33 @@ export class ClosingApiService extends BaseApiService { return undefined; } } + + async getOverhead( + id: number + ): Promise | undefined> { + // TODO: Remove this block when backend is ready + // try { + // return await dummyGetOverhead(id); + // } catch (error) { + // if (axios.isAxiosError>(error)) { + // return error.response?.data; + // } + // return undefined; + // } + + // Uncomment this when backend is ready + try { + const path = `${this.basePath}/${id}/overhead`; + return await httpClient>(path, { + method: 'GET', + }); + } catch (error) { + if (axios.isAxiosError>(error)) { + return error.response?.data; + } + return undefined; + } + } } export const ClosingApi = new ClosingApiService('/closings'); diff --git a/src/services/api/production/project-flock-kandang.ts b/src/services/api/production/project-flock-kandang.ts index 47c9568e..f4887e68 100644 --- a/src/services/api/production/project-flock-kandang.ts +++ b/src/services/api/production/project-flock-kandang.ts @@ -54,120 +54,119 @@ export class ProjectFlockKandangService extends BaseApiService< id: number ): Promise | undefined> { // Dummy data - replace with actual API call when backend is ready - return new Promise((resolve) => { - setTimeout(() => { - resolve({ - code: 200, - status: 'success', - message: 'Cek persyaratan closing kandang', - data: { - unfinished_expenses: 2, - stock_remaining: [ - { - id: 1, - product_id: 1, - warehouse_id: 1, - quantity: 0, - product: { - id: 1, - name: 'Pakan Starter', - brand: 'Brand A', - sku: 'PKN-STR-001', - product_price: 15000, - selling_price: 17000, - tax: 0, - expiry_period: 365, - flags: ['active'], - uom: { - id: 1, - name: 'Kg', - created_user: { - id: 1, - id_user: 1, - email: 'admin@example.com', - name: 'Admin User', - }, - created_at: '2024-01-01', - updated_at: '2024-01-01', - }, - product_category: { - id: 1, - name: 'Pakan', - code: 'PKN', - created_user: { - id: 1, - id_user: 1, - email: 'admin@example.com', - name: 'Admin User', - }, - created_at: '2024-01-01', - updated_at: '2024-01-01', - }, - suppliers: [], - created_user: { - id: 1, - id_user: 1, - email: 'admin@example.com', - name: 'Admin User', - }, - created_at: '2024-01-01', - updated_at: '2024-01-01', - }, - warehouse: { - id: 1, - name: 'Gudang Utama', - type: 'AREA', - area: { - id: 1, - name: 'Area 1', - }, - created_user: { - id: 1, - id_user: 1, - email: 'admin@example.com', - name: 'Admin User', - }, - created_at: '2024-01-01', - updated_at: '2024-01-01', - }, - created_user: { - id: 1, - id_user: 1, - email: 'admin@example.com', - name: 'Admin User', - }, - created_at: '2025-01-01', - updated_at: '2025-01-01', - }, - ], - expenses: [ - { - id: 1, - po_number: 'PO-BOP-LTI-00001', - category: 'NON-BOP', - total: 110000, - status: 'SELESAI', - step_name: 'Approval Finance', - step: 5, - reference_number: 'BOP-LTI-00001', - }, - { - id: 3, - po_number: 'PO-BOP-LTI-00003', - category: 'BOP', - total: 110000, - status: 'SELESAI', - step_name: 'Approval Finance', - step: 5, - reference_number: 'BOP-LTI-00003', - }, - ], - }, - }); - }, 500); // Simulate network delay - }); + // return new Promise((resolve) => { + // setTimeout(() => { + // resolve({ + // code: 200, + // status: 'success', + // message: 'Cek persyaratan closing kandang', + // data: { + // unfinished_expenses: id % 2 === 1 ? 2 : 0, + // stock_remaining: [ + // { + // id: 1, + // product_id: 1, + // warehouse_id: 1, + // quantity: id % 2 === 1 ? 100 : 0, + // product: { + // id: 1, + // name: 'Pakan Starter', + // brand: 'Brand A', + // sku: 'PKN-STR-001', + // product_price: 15000, + // selling_price: 17000, + // tax: 0, + // expiry_period: 365, + // flags: ['active'], + // uom: { + // id: 1, + // name: 'Kg', + // created_user: { + // id: 1, + // id_user: 1, + // email: 'admin@example.com', + // name: 'Admin User', + // }, + // created_at: '2024-01-01', + // updated_at: '2024-01-01', + // }, + // product_category: { + // id: 1, + // name: 'Pakan', + // code: 'PKN', + // created_user: { + // id: 1, + // id_user: 1, + // email: 'admin@example.com', + // name: 'Admin User', + // }, + // created_at: '2024-01-01', + // updated_at: '2024-01-01', + // }, + // suppliers: [], + // created_user: { + // id: 1, + // id_user: 1, + // email: 'admin@example.com', + // name: 'Admin User', + // }, + // created_at: '2024-01-01', + // updated_at: '2024-01-01', + // }, + // warehouse: { + // id: 1, + // name: 'Gudang Utama', + // type: 'AREA', + // area: { + // id: 1, + // name: 'Area 1', + // }, + // created_user: { + // id: 1, + // id_user: 1, + // email: 'admin@example.com', + // name: 'Admin User', + // }, + // created_at: '2024-01-01', + // updated_at: '2024-01-01', + // }, + // created_user: { + // id: 1, + // id_user: 1, + // email: 'admin@example.com', + // name: 'Admin User', + // }, + // created_at: '2025-01-01', + // updated_at: '2025-01-01', + // }, + // ], + // expenses: [ + // { + // id: 1, + // po_number: 'PO-BOP-LTI-00001', + // category: 'NON-BOP', + // total: 110000, + // status: id % 2 === 1 ? 'PENGAJUAN' : 'SELESAI', + // step_name: id % 2 === 1 ? 'Approval Finance' : 'Selesai', + // step: id % 2 === 1 ? 1 : 5, + // reference_number: 'BOP-LTI-00001', + // }, + // { + // id: 3, + // po_number: 'PO-BOP-LTI-00003', + // category: 'BOP', + // total: 110000, + // status: id % 2 === 1 ? 'PENGAJUAN' : 'SELESAI', + // step_name: id % 2 === 1 ? 'Approval Finance' : 'Selesai', + // step: id % 2 === 1 ? 1 : 5, + // reference_number: 'BOP-LTI-00003', + // }, + // ], + // }, + // }); + // }, 500); // Simulate network delay + // }); - /* // Original API call - uncomment when backend is ready try { const path = `${this.basePath}/${id}/closing/check`; @@ -181,7 +180,6 @@ export class ProjectFlockKandangService extends BaseApiService< } return undefined; } - */ } } diff --git a/src/types/api/closing.d.ts b/src/types/api/closing.d.ts index ca39dbc9..baf4c7aa 100644 --- a/src/types/api/closing.d.ts +++ b/src/types/api/closing.d.ts @@ -114,4 +114,31 @@ export type ClosingSapronakCalculation = { ovk: ClosingSapronakCalculationItem; pakan: ClosingSapronakCalculationItem; }; + +// ====== OVERHEAD ====== +export type ClosingOverhead = { + overheads: Overhead[]; + total: OverheadTotal; +}; + +export type Overhead = { + item_name: string; + uom_name: string; + budget_quantity: number; + budget_unit_price: number; + budget_total_amount: number; + actual_date: string; + actual_quantity: number; + actual_unit_price: number; + actual_total_amount: number; + cost_per_bird: number; +}; + +export type OverheadTotal = { + budget_quantity: number; + budget_total_amount: number; + actual_quantity: number; + actual_total_amount: number; + cost_per_bird: number; +}; export type ClosingSales = BaseMetadata & BaseClosingSales; diff --git a/src/types/api/production/project-flock-kandang.d.ts b/src/types/api/production/project-flock-kandang.d.ts index 388eed32..3a98a6e8 100644 --- a/src/types/api/production/project-flock-kandang.d.ts +++ b/src/types/api/production/project-flock-kandang.d.ts @@ -56,8 +56,26 @@ export type ClosingExpense = { reference_number: string; }; +// "flag_name": "PAKAN", +// "product_warehouse_id": 14, +// "product_id": 8, +// "product_name": "281 SPECIAL STARTER", +// "product_category": "Bahan Baku", +// "uom": "Kilogram", +// "quantity": 1100 + +export type StockItem = { + flag_name: string; + product_warehouse_id: number; + product_id: number; + product_name: string; + product_category: string; + uom: string; + quantity: number; +}; + export type CheckClosingResponse = { unfinished_expenses: number; - stock_remaining: ProductWarehouse[]; + stock_remaining: StockItem[]; expenses: ClosingExpense[]; };