mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
fix(FE): closing project flock & merge development
This commit is contained in:
@@ -3,6 +3,7 @@ import type { NextConfig } from 'next';
|
|||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
output: 'export',
|
output: 'export',
|
||||||
images: { unoptimized: true },
|
images: { unoptimized: true },
|
||||||
|
trailingSlash: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|||||||
@@ -7,4 +7,5 @@ const Marketing = () => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Marketing;
|
export default Marketing;
|
||||||
|
|||||||
+19
-8
@@ -1,18 +1,29 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
|
import { useAuth } from '@/services/hooks/useAuth';
|
||||||
|
import { redirectToSSO } from '@/lib/auth-helper';
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
|
const { user, isLoadingUser } = useAuth();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
router.replace('/dashboard');
|
if (pathname === '/') {
|
||||||
}, [router]);
|
router.replace('/dashboard');
|
||||||
|
}
|
||||||
|
}, [pathname]);
|
||||||
|
|
||||||
return (
|
if (isLoadingUser) {
|
||||||
<main className='w-full h-full min-h-screen flex flex-row justify-center items-center'>
|
return (
|
||||||
<span className='loading loading-spinner loading-lg'></span>
|
<main className='w-full h-full min-h-screen flex flex-row justify-center items-center'>
|
||||||
</main>
|
<span className='loading loading-spinner loading-lg'></span>
|
||||||
);
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>Loading...</>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export default function ProjectFlockLayout({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const toggleValidate = useUiStore((s) => s.toggleValidate);
|
const toggleValidate = useUiStore((s) => s.toggleValidate);
|
||||||
|
|
||||||
const isAdd = pathname.endsWith('/add');
|
const isAdd = pathname.includes('/add');
|
||||||
const isEdit = pathname.includes('/detail/edit');
|
const isEdit = pathname.includes('/detail/edit');
|
||||||
const isDetail = pathname.includes('/detail');
|
const isDetail = pathname.includes('/detail');
|
||||||
const isChickin = pathname.includes('/chickin/add/kandang');
|
const isChickin = pathname.includes('/chickin/add/kandang');
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { useRouter, useSearchParams } from 'next/navigation';
|
|
||||||
import useSWR from 'swr';
|
|
||||||
import GradingForm from '@/components/pages/production/recording/grading/form/GradingForm';
|
|
||||||
import { RecordingApi } from '@/services/api/production';
|
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
|
||||||
|
|
||||||
const AddGrading = () => {
|
|
||||||
const router = useRouter();
|
|
||||||
const searchParams = useSearchParams();
|
|
||||||
|
|
||||||
const recordingId = searchParams.get('recording_id');
|
|
||||||
|
|
||||||
const { data: recording, isLoading: isLoadingRecording } = useSWR(
|
|
||||||
recordingId && recordingId !== 'new' ? [recordingId] : null,
|
|
||||||
([id]) => RecordingApi.getSingle(parseInt(id))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
|
||||||
recordingId &&
|
|
||||||
recordingId !== 'new' &&
|
|
||||||
!isLoadingRecording &&
|
|
||||||
(!recording || !isResponseSuccess(recording))
|
|
||||||
) {
|
|
||||||
router.replace('/404');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='w-full p-4 flex flex-row justify-center'>
|
|
||||||
{recordingId && recordingId !== 'new' && isLoadingRecording && (
|
|
||||||
<span className='loading loading-spinner loading-xl' />
|
|
||||||
)}
|
|
||||||
{(!recordingId ||
|
|
||||||
recordingId === 'new' ||
|
|
||||||
(!isLoadingRecording && recording && isResponseSuccess(recording))) && (
|
|
||||||
<GradingForm
|
|
||||||
type='add'
|
|
||||||
initialValues={
|
|
||||||
isResponseSuccess(recording) ? recording.data?.eggs?.[0] : undefined
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AddGrading;
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { useRouter, useSearchParams } from 'next/navigation';
|
|
||||||
import useSWR from 'swr';
|
|
||||||
import GradingForm from '@/components/pages/production/recording/grading/form/GradingForm';
|
|
||||||
import { RecordingApi } from '@/services/api/production';
|
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
|
||||||
|
|
||||||
const EditGrading = () => {
|
|
||||||
const router = useRouter();
|
|
||||||
const searchParams = useSearchParams();
|
|
||||||
|
|
||||||
const recordingId = searchParams.get('recordingId');
|
|
||||||
const gradingId = searchParams.get('gradingId');
|
|
||||||
|
|
||||||
const { data: recording, isLoading: isLoadingRecording } = useSWR(
|
|
||||||
recordingId ? [recordingId] : null,
|
|
||||||
([id]) => RecordingApi.getSingle(parseInt(id))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!recordingId) {
|
|
||||||
router.back();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='w-full flex flex-row justify-center items-center p-4'>
|
|
||||||
<span className='loading loading-spinner loading-xl' />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isLoadingRecording && (!recording || !isResponseSuccess(recording))) {
|
|
||||||
router.replace('/404');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='w-full p-4 flex flex-row justify-center'>
|
|
||||||
{isLoadingRecording && (
|
|
||||||
<span className='loading loading-spinner loading-xl' />
|
|
||||||
)}
|
|
||||||
{!isLoadingRecording && recording && isResponseSuccess(recording) && (
|
|
||||||
<GradingForm
|
|
||||||
type='edit'
|
|
||||||
initialValues={recording.data.eggs?.find(
|
|
||||||
(egg) => egg.id === parseInt(gradingId || '0')
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EditGrading;
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { useRouter, useSearchParams } from 'next/navigation';
|
|
||||||
import useSWR from 'swr';
|
|
||||||
import GradingForm from '@/components/pages/production/recording/grading/form/GradingForm';
|
|
||||||
import { RecordingApi } from '@/services/api/production';
|
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
|
||||||
|
|
||||||
const DetailGrading = () => {
|
|
||||||
const router = useRouter();
|
|
||||||
const searchParams = useSearchParams();
|
|
||||||
|
|
||||||
const gradingId = searchParams.get('gradingId');
|
|
||||||
|
|
||||||
const { data: grading, isLoading: isLoadingGrading } = useSWR(
|
|
||||||
gradingId ? [gradingId] : null,
|
|
||||||
([id]) => RecordingApi.getSingle(parseInt(id))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!gradingId) {
|
|
||||||
router.back();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='w-full flex flex-row justify-center items-center p-4'>
|
|
||||||
<span className='loading loading-spinner loading-xl' />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isLoadingGrading && (!grading || !isResponseSuccess(grading))) {
|
|
||||||
router.replace('/404');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='w-full p-4 flex flex-row justify-center'>
|
|
||||||
{isLoadingGrading && (
|
|
||||||
<span className='loading loading-spinner loading-xl' />
|
|
||||||
)}
|
|
||||||
{!isLoadingGrading && grading && isResponseSuccess(grading) && (
|
|
||||||
<GradingForm
|
|
||||||
type='detail'
|
|
||||||
initialValues={grading.data.eggs?.find(
|
|
||||||
(egg) => egg.id === parseInt(gradingId)
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DetailGrading;
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import SuspenseHelper from '@/components/helper/SuspenseHelper';
|
|
||||||
|
|
||||||
const Layout = ({
|
|
||||||
children,
|
|
||||||
}: Readonly<{
|
|
||||||
children: React.ReactNode;
|
|
||||||
}>) => {
|
|
||||||
return <SuspenseHelper>{children}</SuspenseHelper>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Layout;
|
|
||||||
@@ -1,197 +1,87 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { ReactNode, useEffect } from 'react';
|
import { ReactNode, useEffect } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import useSWR from 'swr';
|
||||||
import useSWRImmutable from 'swr/immutable';
|
|
||||||
|
|
||||||
import { useAuth } from '@/services/hooks/useAuth';
|
import { useAuth } from '@/services/hooks/useAuth';
|
||||||
import { httpClientFetcher, SWRHttpKey } from '@/services/http/client';
|
import { httpClientFetcher, SWRHttpKey } from '@/services/http/client';
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||||
import { GetMeResponse } from '@/types/api/api-general';
|
import { BaseApiResponse, GetMeResponse } from '@/types/api/api-general';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
// TODO: delete this later, DONT HARDCODE USER DATA
|
import { redirectToSSO } from '@/lib/auth-helper';
|
||||||
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 {
|
interface RequireAuthProps {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RequireAuth = ({ children }: RequireAuthProps) => {
|
const RequireAuth = ({ children }: RequireAuthProps) => {
|
||||||
const router = useRouter();
|
const { user, setUser, setIsLoadingUser } = useAuth();
|
||||||
const { setUser, setIsLoadingUser } = useAuth();
|
|
||||||
|
|
||||||
const { data: userResponse, isLoading: isLoadingUserResponse } =
|
const {
|
||||||
useSWRImmutable<GetMeResponse & { ok?: boolean }, unknown, SWRHttpKey>(
|
data: userResponse,
|
||||||
'/auth/sso/userinfo',
|
isLoading: isLoadingUserResponse,
|
||||||
httpClientFetcher,
|
error: userErrorResponse,
|
||||||
{
|
} = useSWR<
|
||||||
shouldRetryOnError: false,
|
GetMeResponse & { ok?: boolean },
|
||||||
revalidateOnFocus: false,
|
AxiosError<BaseApiResponse>,
|
||||||
revalidateOnReconnect: false,
|
SWRHttpKey
|
||||||
refreshInterval: 0,
|
>('/sso/userinfo', httpClientFetcher, {
|
||||||
}
|
shouldRetryOnError: false,
|
||||||
);
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setIsLoadingUser(isLoadingUserResponse);
|
|
||||||
}, [isLoadingUserResponse, setIsLoadingUser]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isResponseSuccess(userResponse)) {
|
if (isResponseSuccess(userResponse)) {
|
||||||
setUser(userResponse.data);
|
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, setIsLoadingUser, setUser]);
|
}, [userResponse, setUser]);
|
||||||
|
|
||||||
// TODO: uncomment this later
|
// Explicitly handle 401 redirect from the component level
|
||||||
// if (isLoadingUserResponse && !userResponse) {
|
useEffect(() => {
|
||||||
// return (
|
if (
|
||||||
// <div className='w-full flex flex-row justify-center items-center p-4'>
|
isResponseError(userResponse) &&
|
||||||
// <span className='loading loading-spinner loading-xl' />
|
userErrorResponse?.response?.status === 401
|
||||||
// </div>
|
) {
|
||||||
// );
|
// 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]);
|
||||||
|
|
||||||
return <>{children}</>;
|
useEffect(() => {
|
||||||
|
setIsLoadingUser(isLoadingUserResponse);
|
||||||
|
}, [isLoadingUserResponse]);
|
||||||
|
|
||||||
|
if (
|
||||||
|
(isLoadingUserResponse && !userResponse && !userErrorResponse) ||
|
||||||
|
(!userResponse && !userErrorResponse)
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<div className='w-full flex flex-row justify-center items-center p-4'>
|
||||||
|
<span className='loading loading-spinner loading-xl' />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userErrorResponse) {
|
||||||
|
return (
|
||||||
|
<div className='w-full h-screen flex flex-col justify-center items-center gap-4'>
|
||||||
|
<h2 className='text-2xl font-bold text-error'>Authentication Failed</h2>
|
||||||
|
<p className='text-gray-600'>
|
||||||
|
Please try refreshing the page or contact support if the problem
|
||||||
|
persists.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
className='btn btn-primary'
|
||||||
|
onClick={() => window.location.reload()}
|
||||||
|
>
|
||||||
|
Retry
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>{isResponseSuccess(userResponse) && user && children}</>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default RequireAuth;
|
export default RequireAuth;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { ProjectFlock } from '@/types/api/production/project-flock';
|
|||||||
import {
|
import {
|
||||||
ClosingExpense,
|
ClosingExpense,
|
||||||
ProjectFlockKandang,
|
ProjectFlockKandang,
|
||||||
|
StockItem,
|
||||||
} from '@/types/api/production/project-flock-kandang';
|
} from '@/types/api/production/project-flock-kandang';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
@@ -41,7 +42,7 @@ const ProjectFlockClosingForm = ({
|
|||||||
const confirmationModalCloseClickHandler = async () => {
|
const confirmationModalCloseClickHandler = async () => {
|
||||||
setIsClosingLoading(true);
|
setIsClosingLoading(true);
|
||||||
const deleteProjectFlockRes = await ProjectFlockKandangApi.closing(
|
const deleteProjectFlockRes = await ProjectFlockKandangApi.closing(
|
||||||
projectFlock?.id as number,
|
projectFlockKandang?.id as number,
|
||||||
{
|
{
|
||||||
closed_date: formatDate(new Date(), 'YYYY-MM-DD'),
|
closed_date: formatDate(new Date(), 'YYYY-MM-DD'),
|
||||||
action: isCanClose ? 'close' : 'unclose',
|
action: isCanClose ? 'close' : 'unclose',
|
||||||
@@ -222,7 +223,7 @@ const ProjectFlockClosingForm = ({
|
|||||||
<div className='divider'></div>
|
<div className='divider'></div>
|
||||||
<div className='px-4 pb-4'>
|
<div className='px-4 pb-4'>
|
||||||
<h2 className='text-2xl font-semibold'>Persediaan Gudang</h2>
|
<h2 className='text-2xl font-semibold'>Persediaan Gudang</h2>
|
||||||
<Table<ProductWarehouse>
|
<Table<StockItem>
|
||||||
data={
|
data={
|
||||||
isResponseSuccess(closingData)
|
isResponseSuccess(closingData)
|
||||||
? closingData.data?.stock_remaining
|
? closingData.data?.stock_remaining
|
||||||
@@ -231,11 +232,11 @@ const ProjectFlockClosingForm = ({
|
|||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
header: 'Product',
|
header: 'Product',
|
||||||
accessorKey: 'product.name',
|
accessorKey: 'product_name',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Kategori',
|
header: 'Kategori',
|
||||||
accessorKey: 'product.product_category.name',
|
accessorKey: 'product_category',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Quantity',
|
header: 'Quantity',
|
||||||
@@ -243,7 +244,7 @@ const ProjectFlockClosingForm = ({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'UOM',
|
header: 'UOM',
|
||||||
accessorKey: 'product.uom.name',
|
accessorKey: 'uom',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
className={{
|
className={{
|
||||||
|
|||||||
@@ -35,28 +35,22 @@ const RowOptionsMenu = ({
|
|||||||
deleteClickHandler,
|
deleteClickHandler,
|
||||||
approveClickHandler,
|
approveClickHandler,
|
||||||
rejectClickHandler,
|
rejectClickHandler,
|
||||||
isGradingCompleted,
|
|
||||||
}: {
|
}: {
|
||||||
type: 'dropdown' | 'collapse';
|
type: 'dropdown' | 'collapse';
|
||||||
props: CellContext<Recording, unknown>;
|
props: CellContext<Recording, unknown>;
|
||||||
deleteClickHandler: () => void;
|
deleteClickHandler: () => void;
|
||||||
approveClickHandler: () => void;
|
approveClickHandler: () => void;
|
||||||
rejectClickHandler: () => void;
|
rejectClickHandler: () => void;
|
||||||
isGradingCompleted: (recording: Recording) => boolean;
|
|
||||||
}) => {
|
}) => {
|
||||||
const isLayingCategory =
|
|
||||||
props.row.original.project_flock_category === 'LAYING';
|
|
||||||
|
|
||||||
const isRecordingApproved = (recording: Recording) => {
|
const isRecordingApproved = (recording: Recording) => {
|
||||||
return (
|
return (
|
||||||
recording.approval?.action === 'APPROVED' &&
|
recording.approval?.action === 'APPROVED' &&
|
||||||
recording.approval?.step_name === 'Disetujui' &&
|
recording.approval?.step_number === 2 &&
|
||||||
recording.approval?.step_number === 3
|
recording.approval?.step_name === 'Disetujui'
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const isApproved = isRecordingApproved(props.row.original);
|
const isApproved = isRecordingApproved(props.row.original);
|
||||||
const isGradingDone = isGradingCompleted(props.row.original);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RowOptionsMenuWrapper type={type}>
|
<RowOptionsMenuWrapper type={type}>
|
||||||
@@ -78,7 +72,7 @@ const RowOptionsMenu = ({
|
|||||||
<Icon icon='mdi:pencil-outline' width={16} height={16} />
|
<Icon icon='mdi:pencil-outline' width={16} height={16} />
|
||||||
Edit
|
Edit
|
||||||
</Button>
|
</Button>
|
||||||
{!isApproved && !(isLayingCategory && !isGradingDone) && (
|
{!isApproved && (
|
||||||
<Button
|
<Button
|
||||||
onClick={approveClickHandler}
|
onClick={approveClickHandler}
|
||||||
variant='ghost'
|
variant='ghost'
|
||||||
@@ -89,7 +83,7 @@ const RowOptionsMenu = ({
|
|||||||
Approve
|
Approve
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{!isApproved && !(isLayingCategory && !isGradingDone) && (
|
{!isApproved && (
|
||||||
<Button
|
<Button
|
||||||
onClick={rejectClickHandler}
|
onClick={rejectClickHandler}
|
||||||
variant='ghost'
|
variant='ghost'
|
||||||
@@ -386,33 +380,10 @@ const RecordingTable = () => {
|
|||||||
RecordingApi.getAllFetcher
|
RecordingApi.getAllFetcher
|
||||||
);
|
);
|
||||||
|
|
||||||
const isRecordingFullyApproved = useCallback(
|
const isRecordingApproved = useCallback((recording: Recording): boolean => {
|
||||||
(recording: Recording): boolean => {
|
|
||||||
return (
|
|
||||||
recording.approval?.action === 'APPROVED' &&
|
|
||||||
recording.approval?.step_name === 'Disetujui' &&
|
|
||||||
Number(recording.approval?.step_number) === 3
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const isRecordingApproved = useCallback(
|
|
||||||
(recording: Recording) => {
|
|
||||||
return isRecordingFullyApproved(recording);
|
|
||||||
},
|
|
||||||
[isRecordingFullyApproved]
|
|
||||||
);
|
|
||||||
|
|
||||||
const isGradingCompleted = useCallback((recording: Recording): boolean => {
|
|
||||||
if (recording.project_flock_category !== 'LAYING') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
recording.egg_grading_status === 'COMPLETED' ||
|
recording.approval?.action === 'APPROVED' &&
|
||||||
(recording.approval?.action === 'UPDATED' &&
|
recording.approval?.step_name === 'Disetujui'
|
||||||
recording.approval?.step_number === 2)
|
|
||||||
);
|
);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -506,19 +477,9 @@ const RecordingTable = () => {
|
|||||||
if (!isResponseSuccess(recordings) || !recordings.data) return [];
|
if (!isResponseSuccess(recordings) || !recordings.data) return [];
|
||||||
return selectedRowIds.filter((id) => {
|
return selectedRowIds.filter((id) => {
|
||||||
const recording = recordings.data.find((r) => r.id === id);
|
const recording = recordings.data.find((r) => r.id === id);
|
||||||
if (!recording || isRecordingApproved(recording)) return false;
|
return recording && !isRecordingApproved(recording);
|
||||||
|
|
||||||
if (recording.project_flock_category === 'GROWING') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recording.project_flock_category === 'LAYING') {
|
|
||||||
return isGradingCompleted(recording);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
});
|
||||||
}, [selectedRowIds, recordings, isRecordingApproved, isGradingCompleted]);
|
}, [selectedRowIds, recordings, isRecordingApproved]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isResponseSuccess(recordings) && recordings.data) {
|
if (isResponseSuccess(recordings) && recordings.data) {
|
||||||
@@ -530,14 +491,7 @@ const RecordingTable = () => {
|
|||||||
(r) => r.id === parseInt(rowId)
|
(r) => r.id === parseInt(rowId)
|
||||||
);
|
);
|
||||||
if (recording && !isRecordingApproved(recording)) {
|
if (recording && !isRecordingApproved(recording)) {
|
||||||
if (recording.project_flock_category === 'GROWING') {
|
newSelection[rowId] = true;
|
||||||
newSelection[rowId] = true;
|
|
||||||
} else if (
|
|
||||||
recording.project_flock_category === 'LAYING' &&
|
|
||||||
isGradingCompleted(recording)
|
|
||||||
) {
|
|
||||||
newSelection[rowId] = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -548,13 +502,7 @@ const RecordingTable = () => {
|
|||||||
setRowSelection(newSelection);
|
setRowSelection(newSelection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [recordings, rowSelection, isRecordingApproved, setRowSelection]);
|
||||||
recordings,
|
|
||||||
rowSelection,
|
|
||||||
isRecordingApproved,
|
|
||||||
isGradingCompleted,
|
|
||||||
setRowSelection,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full p-0 sm:p-4'>
|
<div className='w-full p-0 sm:p-4'>
|
||||||
@@ -562,7 +510,7 @@ const RecordingTable = () => {
|
|||||||
<div className='w-full flex flex-col xl:flex-row justify-between items-end xl:items-center gap-2'>
|
<div className='w-full flex flex-col xl:flex-row justify-between items-end xl:items-center gap-2'>
|
||||||
<div className='w-full sm:w-fit flex flex-col sm:flex-row self-start gap-2'>
|
<div className='w-full sm:w-fit flex flex-col sm:flex-row self-start gap-2'>
|
||||||
<Button
|
<Button
|
||||||
href='recording/add'
|
href='/production/recording/add'
|
||||||
variant='outline'
|
variant='outline'
|
||||||
color='primary'
|
color='primary'
|
||||||
className='w-full sm:w-fit'
|
className='w-full sm:w-fit'
|
||||||
@@ -640,40 +588,28 @@ const RecordingTable = () => {
|
|||||||
id: 'select',
|
id: 'select',
|
||||||
header: ({ table }) => {
|
header: ({ table }) => {
|
||||||
const allRows = table.getRowModel().rows;
|
const allRows = table.getRowModel().rows;
|
||||||
|
const selectableRows = allRows.filter((row) => {
|
||||||
const selectableGrowingRows = allRows.filter((row) => {
|
|
||||||
const recording = row.original;
|
const recording = row.original;
|
||||||
return (
|
return !isRecordingApproved(recording);
|
||||||
recording.project_flock_category === 'GROWING' &&
|
|
||||||
!isRecordingApproved(recording)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const hasNoSelectableGrowing = selectableGrowingRows.length === 0;
|
const hasNoSelectableRows = selectableRows.length === 0;
|
||||||
|
|
||||||
const handleSelectAllGrowing = () => {
|
const handleSelectAll = () => {
|
||||||
const isAllSelected = selectableGrowingRows.every((row) =>
|
const isAllSelected = selectableRows.every((row) =>
|
||||||
row.getIsSelected()
|
row.getIsSelected()
|
||||||
);
|
);
|
||||||
|
|
||||||
allRows.forEach((row) => {
|
selectableRows.forEach((row) => {
|
||||||
const recording = row.original;
|
row.toggleSelected(!isAllSelected);
|
||||||
if (
|
|
||||||
recording.project_flock_category === 'GROWING' &&
|
|
||||||
!isRecordingApproved(recording)
|
|
||||||
) {
|
|
||||||
row.toggleSelected(!isAllSelected);
|
|
||||||
} else if (recording.project_flock_category === 'LAYING') {
|
|
||||||
row.toggleSelected(false);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const isAllGrowingSelected =
|
const isAllSelected =
|
||||||
selectableGrowingRows.length > 0 &&
|
selectableRows.length > 0 &&
|
||||||
selectableGrowingRows.every((row) => row.getIsSelected());
|
selectableRows.every((row) => row.getIsSelected());
|
||||||
|
|
||||||
const isSomeGrowingSelected = selectableGrowingRows.some((row) =>
|
const isSomeSelected = selectableRows.some((row) =>
|
||||||
row.getIsSelected()
|
row.getIsSelected()
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -681,33 +617,20 @@ const RecordingTable = () => {
|
|||||||
<div className='w-full flex flex-row justify-center'>
|
<div className='w-full flex flex-row justify-center'>
|
||||||
<CheckboxInput
|
<CheckboxInput
|
||||||
name='allRow'
|
name='allRow'
|
||||||
checked={isAllGrowingSelected}
|
checked={isAllSelected}
|
||||||
indeterminate={
|
indeterminate={isSomeSelected && !isAllSelected}
|
||||||
isSomeGrowingSelected && !isAllGrowingSelected
|
onChange={handleSelectAll}
|
||||||
}
|
disabled={hasNoSelectableRows}
|
||||||
onChange={handleSelectAllGrowing}
|
|
||||||
disabled={hasNoSelectableGrowing}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const isApproved = isRecordingApproved(row.original);
|
|
||||||
const isLayingCategory =
|
|
||||||
row.original.project_flock_category === 'LAYING';
|
|
||||||
|
|
||||||
if (isLayingCategory) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isDisabled = !row.getCanSelect() || isApproved;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<CheckboxInput
|
<CheckboxInput
|
||||||
name='row'
|
name='row'
|
||||||
checked={row.getIsSelected()}
|
checked={row.getIsSelected()}
|
||||||
disabled={isDisabled}
|
|
||||||
indeterminate={row.getIsSomeSelected()}
|
indeterminate={row.getIsSomeSelected()}
|
||||||
onChange={row.getToggleSelectedHandler()}
|
onChange={row.getToggleSelectedHandler()}
|
||||||
/>
|
/>
|
||||||
@@ -883,7 +806,6 @@ const RecordingTable = () => {
|
|||||||
deleteClickHandler={deleteClickHandler}
|
deleteClickHandler={deleteClickHandler}
|
||||||
approveClickHandler={approveClickHandler}
|
approveClickHandler={approveClickHandler}
|
||||||
rejectClickHandler={rejectClickHandler}
|
rejectClickHandler={rejectClickHandler}
|
||||||
isGradingCompleted={isGradingCompleted}
|
|
||||||
/>
|
/>
|
||||||
</RowDropdownOptions>
|
</RowDropdownOptions>
|
||||||
)}
|
)}
|
||||||
@@ -896,7 +818,6 @@ const RecordingTable = () => {
|
|||||||
deleteClickHandler={deleteClickHandler}
|
deleteClickHandler={deleteClickHandler}
|
||||||
approveClickHandler={approveClickHandler}
|
approveClickHandler={approveClickHandler}
|
||||||
rejectClickHandler={rejectClickHandler}
|
rejectClickHandler={rejectClickHandler}
|
||||||
isGradingCompleted={isGradingCompleted}
|
|
||||||
/>
|
/>
|
||||||
</RowCollapseOptions>
|
</RowCollapseOptions>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
CreateGrowingRecordingPayload,
|
CreateGrowingRecordingPayload,
|
||||||
CreateLayingRecordingPayload,
|
CreateLayingRecordingPayload,
|
||||||
CreateEggPayload,
|
CreateEggPayload,
|
||||||
CreateGradingPayload,
|
|
||||||
} from '@/types/api/production/recording';
|
} from '@/types/api/production/recording';
|
||||||
|
|
||||||
type RecordingGrowingFormSchemaType = {
|
type RecordingGrowingFormSchemaType = {
|
||||||
@@ -32,14 +31,7 @@ type RecordingLayingFormSchemaType = RecordingGrowingFormSchemaType & {
|
|||||||
eggs: {
|
eggs: {
|
||||||
product_warehouse_id: number;
|
product_warehouse_id: number;
|
||||||
qty: number | string;
|
qty: number | string;
|
||||||
}[];
|
weight: number | string;
|
||||||
};
|
|
||||||
|
|
||||||
type RecordingGradingFormSchemaType = {
|
|
||||||
eggs_grading: {
|
|
||||||
recording_egg_id: number;
|
|
||||||
grade: string;
|
|
||||||
qty: number | string;
|
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -62,6 +54,7 @@ export type DepletionSchema = {
|
|||||||
export type EggSchema = {
|
export type EggSchema = {
|
||||||
product_warehouse_id: number;
|
product_warehouse_id: number;
|
||||||
qty: number | string;
|
qty: number | string;
|
||||||
|
weight: number | string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const BodyWeightObjectSchema: Yup.ObjectSchema<BodyWeightSchema> = Yup.object({
|
const BodyWeightObjectSchema: Yup.ObjectSchema<BodyWeightSchema> = Yup.object({
|
||||||
@@ -109,6 +102,10 @@ const EggObjectSchema: Yup.ObjectSchema<EggSchema> = Yup.object({
|
|||||||
.required('Jumlah telur wajib diisi!')
|
.required('Jumlah telur wajib diisi!')
|
||||||
.min(1, 'Jumlah telur tidak boleh 0!')
|
.min(1, 'Jumlah telur tidak boleh 0!')
|
||||||
.typeError('Jumlah telur harus berupa angka!'),
|
.typeError('Jumlah telur harus berupa angka!'),
|
||||||
|
weight: Yup.number()
|
||||||
|
.required('Berat telur wajib diisi!')
|
||||||
|
.min(1, 'Berat telur minimal 1 gram!')
|
||||||
|
.typeError('Berat telur harus berupa angka!'),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const RecordingGrowingFormSchema: Yup.ObjectSchema<RecordingGrowingFormSchemaType> =
|
export const RecordingGrowingFormSchema: Yup.ObjectSchema<RecordingGrowingFormSchemaType> =
|
||||||
@@ -190,30 +187,6 @@ export const UpdateRecordingLayingFormSchema = RecordingLayingFormSchema.shape({
|
|||||||
.required('Project Flock Kandang wajib diisi!'),
|
.required('Project Flock Kandang wajib diisi!'),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const RecordingGradingFormSchema: Yup.ObjectSchema<RecordingGradingFormSchemaType> =
|
|
||||||
Yup.object({
|
|
||||||
eggs_grading: Yup.array()
|
|
||||||
.of(
|
|
||||||
Yup.object({
|
|
||||||
recording_egg_id: Yup.number()
|
|
||||||
.required('Recording Egg ID wajib diisi!')
|
|
||||||
.min(1, 'Recording Egg ID minimal 1!')
|
|
||||||
.typeError('Recording Egg ID harus berupa angka!'),
|
|
||||||
grade: Yup.string()
|
|
||||||
.required('Grade telur wajib diisi!')
|
|
||||||
.typeError('Grade telur harus berupa string!'),
|
|
||||||
qty: Yup.number()
|
|
||||||
.required('Jumlah telur wajib diisi!')
|
|
||||||
.min(1, 'Jumlah telur minimal 1!')
|
|
||||||
.typeError('Jumlah telur harus berupa angka!'),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.min(1, 'Minimal harus ada 1 data grading telur!')
|
|
||||||
.required('Data grading telur wajib diisi!'),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const UpdateRecordingGradingFormSchema = RecordingGradingFormSchema;
|
|
||||||
|
|
||||||
export type RecordingGrowingFormValues = Yup.InferType<
|
export type RecordingGrowingFormValues = Yup.InferType<
|
||||||
typeof RecordingGrowingFormSchema
|
typeof RecordingGrowingFormSchema
|
||||||
>;
|
>;
|
||||||
@@ -222,10 +195,6 @@ export type RecordingLayingFormValues = Yup.InferType<
|
|||||||
typeof RecordingLayingFormSchema
|
typeof RecordingLayingFormSchema
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type RecordingGradingFormValues = Yup.InferType<
|
|
||||||
typeof RecordingGradingFormSchema
|
|
||||||
>;
|
|
||||||
|
|
||||||
type RecordingFormData = Partial<Recording> & {
|
type RecordingFormData = Partial<Recording> & {
|
||||||
body_weights?: CreateGrowingRecordingPayload['body_weights'];
|
body_weights?: CreateGrowingRecordingPayload['body_weights'];
|
||||||
stocks?: CreateGrowingRecordingPayload['stocks'] | Recording['stocks'];
|
stocks?: CreateGrowingRecordingPayload['stocks'] | Recording['stocks'];
|
||||||
@@ -295,26 +264,12 @@ export const getRecordingLayingFormInitialValues = (
|
|||||||
eggs: initialValues?.eggs?.map((egg: CreateEggPayload) => ({
|
eggs: initialValues?.eggs?.map((egg: CreateEggPayload) => ({
|
||||||
product_warehouse_id: egg.product_warehouse_id,
|
product_warehouse_id: egg.product_warehouse_id,
|
||||||
qty: egg.qty,
|
qty: egg.qty,
|
||||||
|
weight: egg.weight,
|
||||||
})) ?? [
|
})) ?? [
|
||||||
{
|
{
|
||||||
product_warehouse_id: 0,
|
product_warehouse_id: 0,
|
||||||
qty: '',
|
qty: '',
|
||||||
},
|
weight: '',
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
export const getRecordingGradingFormInitialValues = (
|
|
||||||
initialValues?: Partial<CreateGradingPayload> & { recording_egg_id?: number }
|
|
||||||
): RecordingGradingFormValues => ({
|
|
||||||
eggs_grading: initialValues?.eggs_grading?.map((grading) => ({
|
|
||||||
recording_egg_id: grading.recording_egg_id,
|
|
||||||
grade: grading.grade,
|
|
||||||
qty: grading.qty,
|
|
||||||
})) ?? [
|
|
||||||
{
|
|
||||||
recording_egg_id: initialValues?.recording_egg_id ?? 0,
|
|
||||||
grade: '',
|
|
||||||
qty: '',
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import CheckboxInput from '@/components/input/CheckboxInput';
|
|||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
|
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
|
||||||
import { useModal } from '@/components/Modal';
|
import { useModal } from '@/components/Modal';
|
||||||
import Tooltip from '@/components/Tooltip';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ProjectFlockKandangApi,
|
ProjectFlockKandangApi,
|
||||||
@@ -98,9 +97,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
const [recordingFormErrorMessage, setRecordingFormErrorMessage] =
|
const [recordingFormErrorMessage, setRecordingFormErrorMessage] =
|
||||||
useState('');
|
useState('');
|
||||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||||
const [newRecordingData, setNewRecordingData] = useState<Recording | null>(
|
const [, setNewRecordingData] = useState<Recording | null>(null);
|
||||||
null
|
|
||||||
);
|
|
||||||
const [nextDayRecording, setNextDayRecording] =
|
const [nextDayRecording, setNextDayRecording] =
|
||||||
useState<NextDayRecording | null>(null);
|
useState<NextDayRecording | null>(null);
|
||||||
|
|
||||||
@@ -111,18 +108,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
const isRecordingApproved = useCallback((recording?: Recording) => {
|
const isRecordingApproved = useCallback((recording?: Recording) => {
|
||||||
return (
|
return (
|
||||||
recording?.approval?.action === 'APPROVED' &&
|
recording?.approval?.action === 'APPROVED' &&
|
||||||
recording?.approval?.step_name === 'Disetujui' &&
|
recording?.approval?.step_name === 'Disetujui'
|
||||||
recording?.approval?.step_number === 3
|
|
||||||
);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const hasGradingData = useCallback((recording?: Recording) => {
|
|
||||||
if (!recording || !recording.eggs) return false;
|
|
||||||
return recording.eggs.some(
|
|
||||||
(egg) =>
|
|
||||||
egg.gradings &&
|
|
||||||
egg.gradings.length > 0 &&
|
|
||||||
egg.gradings.some((grading) => grading.qty > 0)
|
|
||||||
);
|
);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -181,6 +167,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
eggs: (values.eggs ?? []).map((egg) => ({
|
eggs: (values.eggs ?? []).map((egg) => ({
|
||||||
product_warehouse_id: egg.product_warehouse_id,
|
product_warehouse_id: egg.product_warehouse_id,
|
||||||
qty: Number(egg.qty) || 0,
|
qty: Number(egg.qty) || 0,
|
||||||
|
weight:
|
||||||
|
typeof egg.weight === 'number'
|
||||||
|
? egg.weight
|
||||||
|
: parseFloat(String(egg.weight)) || 0,
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -203,35 +193,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
[router]
|
[router]
|
||||||
);
|
);
|
||||||
|
|
||||||
const createRecordingHandlerWithRedirect = useCallback(
|
|
||||||
async (
|
|
||||||
payload: CreateGrowingRecordingPayload | CreateLayingRecordingPayload,
|
|
||||||
redirectToGrading: boolean = false
|
|
||||||
) => {
|
|
||||||
const res = await RecordingApi.create(payload);
|
|
||||||
if (isResponseError(res)) {
|
|
||||||
setRecordingFormErrorMessage(res.message);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
toast.success(res?.message as string);
|
|
||||||
|
|
||||||
if (res?.status === 'success' && res.data) {
|
|
||||||
setNewRecordingData(res.data);
|
|
||||||
return res.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (redirectToGrading) {
|
|
||||||
toast.error(
|
|
||||||
'Gagal mendapatkan ID recording. Silakan coba dari halaman list.'
|
|
||||||
);
|
|
||||||
router.push('/production/recording');
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
[router]
|
|
||||||
);
|
|
||||||
|
|
||||||
const updateRecordingHandler = useCallback(
|
const updateRecordingHandler = useCallback(
|
||||||
async (
|
async (
|
||||||
recordingId: number,
|
recordingId: number,
|
||||||
@@ -650,7 +611,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
const hasPakanFlag = product.product.flags?.includes('PAKAN');
|
const hasPakanFlag = product.product.flags?.includes('PAKAN');
|
||||||
const hasOvkFlag = product.product.flags?.includes('OVK');
|
const hasOvkFlag = product.product.flags?.includes('OVK');
|
||||||
|
|
||||||
// Only include products that are in the same location as the selected kandang
|
|
||||||
if (hasPakanFlag || hasOvkFlag) {
|
if (hasPakanFlag || hasOvkFlag) {
|
||||||
options.push({
|
options.push({
|
||||||
value: product.id,
|
value: product.id,
|
||||||
@@ -690,7 +650,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
depletionProductsData.data.forEach((product) => {
|
depletionProductsData.data.forEach((product) => {
|
||||||
const productName = product.product.name;
|
const productName = product.product.name;
|
||||||
|
|
||||||
// Filter for depletion-related products (culling, mati, afkir)
|
|
||||||
if (
|
if (
|
||||||
productName.toLowerCase().includes('culling') ||
|
productName.toLowerCase().includes('culling') ||
|
||||||
productName.toLowerCase().includes('mati') ||
|
productName.toLowerCase().includes('mati') ||
|
||||||
@@ -732,7 +691,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
eggProductsData.data.forEach((product) => {
|
eggProductsData.data.forEach((product) => {
|
||||||
const productName = product.product.name;
|
const productName = product.product.name;
|
||||||
|
|
||||||
// Filter for egg-related products
|
|
||||||
if (
|
if (
|
||||||
productName.toLowerCase().includes('telur') ||
|
productName.toLowerCase().includes('telur') ||
|
||||||
productName.toLowerCase().includes('egg') ||
|
productName.toLowerCase().includes('egg') ||
|
||||||
@@ -1019,54 +977,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
);
|
);
|
||||||
}, [formik.values.stocks, getStockUsageError, type]);
|
}, [formik.values.stocks, getStockUsageError, type]);
|
||||||
|
|
||||||
const hasConsumableEggs = useMemo(() => {
|
|
||||||
if (!isLayingCategory) return false;
|
|
||||||
const layingValues = formik.values as RecordingLayingFormValues;
|
|
||||||
if (!layingValues.eggs || layingValues.eggs.length === 0) return false;
|
|
||||||
|
|
||||||
return layingValues.eggs.some((egg) => {
|
|
||||||
if (!egg.product_warehouse_id || Number(egg.qty) <= 0) return false;
|
|
||||||
|
|
||||||
const product = eggProducts.find(
|
|
||||||
(opt) => opt.value === egg.product_warehouse_id
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!product) return false;
|
|
||||||
|
|
||||||
const productName = product.label.toLowerCase();
|
|
||||||
return (
|
|
||||||
productName.includes('konsumsi') &&
|
|
||||||
productName.includes('baik') &&
|
|
||||||
Number(egg.qty) > 0
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}, [isLayingCategory, formik.values, eggProducts]);
|
|
||||||
|
|
||||||
const hasConsumableEggsInRecording = useCallback((recording?: Recording) => {
|
|
||||||
if (!recording || !recording.eggs || recording.eggs.length === 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return recording.eggs.some((egg) => {
|
|
||||||
if (!egg.product_warehouse || !egg.product_warehouse.product)
|
|
||||||
return false;
|
|
||||||
if (Number(egg.qty) <= 0) return false;
|
|
||||||
|
|
||||||
const productName = egg.product_warehouse.product.name.toLowerCase();
|
|
||||||
return (
|
|
||||||
productName.includes('konsumsi') &&
|
|
||||||
productName.includes('baik') &&
|
|
||||||
Number(egg.qty) > 0
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const hasConsumableEggsInCurrentRecording = useMemo(() => {
|
|
||||||
return (
|
|
||||||
hasConsumableEggsInRecording(initialValues) ||
|
|
||||||
hasConsumableEggsInRecording(newRecordingData || undefined)
|
|
||||||
);
|
|
||||||
}, [initialValues, newRecordingData, hasConsumableEggsInRecording]);
|
|
||||||
|
|
||||||
const isRepeaterInputError = (
|
const isRepeaterInputError = (
|
||||||
arrayName: 'body_weights' | 'stocks' | 'depletions' | 'eggs',
|
arrayName: 'body_weights' | 'stocks' | 'depletions' | 'eggs',
|
||||||
column: string,
|
column: string,
|
||||||
@@ -1148,7 +1058,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
|
|
||||||
if (hasSameDayRecording) {
|
if (hasSameDayRecording) {
|
||||||
toast.error(
|
toast.error(
|
||||||
`Recording untuk hari ${nextDayRecording.next_day} sudah ada.
|
`Recording untuk hari ${nextDayRecording.next_day} sudah ada.
|
||||||
Tidak bisa membuat recording duplikat, mohon perbarui recording yang sudah ada terlebih dahulu.`
|
Tidak bisa membuat recording duplikat, mohon perbarui recording yang sudah ada terlebih dahulu.`
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@@ -1278,7 +1188,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
setIsRejectLoading(false);
|
setIsRejectLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Body Weights Handlers
|
|
||||||
const addBodyWeight = () => {
|
const addBodyWeight = () => {
|
||||||
const newBodyWeights = [
|
const newBodyWeights = [
|
||||||
...(formik.values.body_weights || []),
|
...(formik.values.body_weights || []),
|
||||||
@@ -1397,7 +1306,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
setSelectedBodyWeights([]);
|
setSelectedBodyWeights([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Stocks Handlers
|
|
||||||
const addStock = () => {
|
const addStock = () => {
|
||||||
const newStocks = [
|
const newStocks = [
|
||||||
...(formik.values.stocks || []),
|
...(formik.values.stocks || []),
|
||||||
@@ -1430,7 +1338,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
setSelectedStocks([]);
|
setSelectedStocks([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Depletions Handlers
|
|
||||||
const addDepletion = () => {
|
const addDepletion = () => {
|
||||||
const newDepletions = [
|
const newDepletions = [
|
||||||
...(formik.values.depletions || []),
|
...(formik.values.depletions || []),
|
||||||
@@ -1465,7 +1372,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
setSelectedDepletions([]);
|
setSelectedDepletions([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Eggs Handlers
|
|
||||||
const addEgg = () => {
|
const addEgg = () => {
|
||||||
const newEggs = [
|
const newEggs = [
|
||||||
...((formik.values as RecordingLayingFormValues).eggs || []),
|
...((formik.values as RecordingLayingFormValues).eggs || []),
|
||||||
@@ -1485,6 +1391,14 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
[formik]
|
[formik]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleEggWeightChangeWrapper = useCallback(
|
||||||
|
(idx: number) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = parseFloat(e.target.value) || 0;
|
||||||
|
formik.setFieldValue(`eggs.${idx}.weight`, value);
|
||||||
|
},
|
||||||
|
[formik]
|
||||||
|
);
|
||||||
|
|
||||||
const removeEgg = (idx: number) => {
|
const removeEgg = (idx: number) => {
|
||||||
const updatedEggs = (
|
const updatedEggs = (
|
||||||
formik.values as RecordingLayingFormValues
|
formik.values as RecordingLayingFormValues
|
||||||
@@ -1569,47 +1483,37 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
Kembali
|
Kembali
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{type === 'detail' &&
|
{type === 'detail' && !isRecordingApproved(initialValues) && (
|
||||||
!isRecordingApproved(initialValues) &&
|
<div className='flex flex-row gap-2'>
|
||||||
(!isLayingCategory || hasGradingData(initialValues)) && (
|
<Button
|
||||||
<div className='flex flex-row gap-2'>
|
variant='outline'
|
||||||
<Button
|
color='success'
|
||||||
variant='outline'
|
onClick={() => {
|
||||||
color='success'
|
setApprovalNotes('');
|
||||||
onClick={() => {
|
approveModal.openModal();
|
||||||
setApprovalNotes('');
|
}}
|
||||||
approveModal.openModal();
|
isLoading={isApproveLoading}
|
||||||
}}
|
className='w-full sm:w-fit'
|
||||||
isLoading={isApproveLoading}
|
>
|
||||||
className='w-full sm:w-fit'
|
<Icon icon='material-symbols:check' width={24} height={24} />
|
||||||
>
|
Approve
|
||||||
<Icon
|
</Button>
|
||||||
icon='material-symbols:check'
|
|
||||||
width={24}
|
|
||||||
height={24}
|
|
||||||
/>
|
|
||||||
Approve
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant='outline'
|
variant='outline'
|
||||||
color='error'
|
color='error'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setApprovalNotes('');
|
setApprovalNotes('');
|
||||||
rejectModal.openModal();
|
rejectModal.openModal();
|
||||||
}}
|
}}
|
||||||
isLoading={isRejectLoading}
|
isLoading={isRejectLoading}
|
||||||
className='w-full sm:w-fit'
|
className='w-full sm:w-fit'
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon icon='material-symbols:close' width={24} height={24} />
|
||||||
icon='material-symbols:close'
|
Reject
|
||||||
width={24}
|
</Button>
|
||||||
height={24}
|
</div>
|
||||||
/>
|
)}
|
||||||
Reject
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 className='text-2xl font-bold text-center'>
|
<h1 className='text-2xl font-bold text-center'>
|
||||||
@@ -1916,7 +1820,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
{formik.values.body_weights?.map((bw, idx) => (
|
{formik.values.body_weights?.map((bw, idx) => (
|
||||||
<tr key={`body-weight-${idx}`}>
|
<tr key={`body-weight-${idx}`}>
|
||||||
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
||||||
<td className='!align-middle'>
|
<td className='align-middle!'>
|
||||||
<CheckboxInput
|
<CheckboxInput
|
||||||
name={`body-weight-${idx}`}
|
name={`body-weight-${idx}`}
|
||||||
checked={selectedBodyWeights.includes(idx)}
|
checked={selectedBodyWeights.includes(idx)}
|
||||||
@@ -2166,7 +2070,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
{formik.values.stocks?.map((stock, idx) => (
|
{formik.values.stocks?.map((stock, idx) => (
|
||||||
<tr key={`stock-${idx}`}>
|
<tr key={`stock-${idx}`}>
|
||||||
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
||||||
<td className='!align-middle'>
|
<td className='align-middle!'>
|
||||||
<CheckboxInput
|
<CheckboxInput
|
||||||
name={`stock-${idx}`}
|
name={`stock-${idx}`}
|
||||||
checked={selectedStocks.includes(idx)}
|
checked={selectedStocks.includes(idx)}
|
||||||
@@ -2386,7 +2290,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
{formik.values.depletions?.map((depletion, idx) => (
|
{formik.values.depletions?.map((depletion, idx) => (
|
||||||
<tr key={`depletion-${idx}`}>
|
<tr key={`depletion-${idx}`}>
|
||||||
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
||||||
<td className='!align-middle'>
|
<td className='align-middle!'>
|
||||||
<CheckboxInput
|
<CheckboxInput
|
||||||
name={`depletion-${idx}`}
|
name={`depletion-${idx}`}
|
||||||
checked={selectedDepletions.includes(idx)}
|
checked={selectedDepletions.includes(idx)}
|
||||||
@@ -2587,6 +2491,15 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
<span className='text-error'>*</span>
|
<span className='text-error'>*</span>
|
||||||
</span>
|
</span>
|
||||||
</th>
|
</th>
|
||||||
|
<th>
|
||||||
|
Berat (gram)
|
||||||
|
<span
|
||||||
|
className='tooltip tooltip-error tooltip-bottom '
|
||||||
|
data-tip='required'
|
||||||
|
>
|
||||||
|
<span className='text-error'>*</span>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
||||||
<th>Action</th>
|
<th>Action</th>
|
||||||
)}
|
)}
|
||||||
@@ -2597,7 +2510,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
(egg, idx) => (
|
(egg, idx) => (
|
||||||
<tr key={`egg-${idx}`}>
|
<tr key={`egg-${idx}`}>
|
||||||
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
||||||
<td className='!align-middle'>
|
<td className='align-middle!'>
|
||||||
<CheckboxInput
|
<CheckboxInput
|
||||||
name={`egg-${idx}`}
|
name={`egg-${idx}`}
|
||||||
checked={selectedEggs.includes(idx)}
|
checked={selectedEggs.includes(idx)}
|
||||||
@@ -2662,32 +2575,55 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div className='flex flex-col gap-1'>
|
<NumberInput
|
||||||
<NumberInput
|
required
|
||||||
required
|
name={`eggs.${idx}.qty`}
|
||||||
name={`eggs.${idx}.qty`}
|
value={egg.qty ?? ''}
|
||||||
value={egg.qty ?? ''}
|
onChange={handleEggQtyChangeWrapper(idx)}
|
||||||
onChange={handleEggQtyChangeWrapper(idx)}
|
onBlur={formik.handleBlur}
|
||||||
onBlur={formik.handleBlur}
|
decimalScale={0}
|
||||||
decimalScale={0}
|
allowNegative={false}
|
||||||
allowNegative={false}
|
thousandSeparator=','
|
||||||
thousandSeparator=','
|
decimalSeparator='.'
|
||||||
decimalSeparator='.'
|
isError={
|
||||||
isError={
|
isRepeaterInputError('eggs', 'qty', idx).isError
|
||||||
isRepeaterInputError('eggs', 'qty', idx)
|
}
|
||||||
.isError
|
errorMessage={
|
||||||
}
|
isRepeaterInputError('eggs', 'qty', idx)
|
||||||
errorMessage={
|
.errorMessage
|
||||||
isRepeaterInputError('eggs', 'qty', idx)
|
}
|
||||||
.errorMessage
|
readOnly={type === 'detail'}
|
||||||
}
|
className={{
|
||||||
readOnly={type === 'detail'}
|
wrapper: 'w-full min-w-24',
|
||||||
className={{
|
}}
|
||||||
wrapper: 'w-full min-w-24',
|
placeholder='Masukkan jumlah telur'
|
||||||
}}
|
/>
|
||||||
placeholder='Masukkan jumlah telur'
|
</td>
|
||||||
/>
|
<td>
|
||||||
</div>
|
<NumberInput
|
||||||
|
required
|
||||||
|
name={`eggs.${idx}.weight`}
|
||||||
|
value={egg.weight ?? ''}
|
||||||
|
onChange={handleEggWeightChangeWrapper(idx)}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
|
decimalScale={0}
|
||||||
|
allowNegative={false}
|
||||||
|
thousandSeparator=','
|
||||||
|
decimalSeparator='.'
|
||||||
|
isError={
|
||||||
|
isRepeaterInputError('eggs', 'weight', idx)
|
||||||
|
.isError
|
||||||
|
}
|
||||||
|
errorMessage={
|
||||||
|
isRepeaterInputError('eggs', 'weight', idx)
|
||||||
|
.errorMessage
|
||||||
|
}
|
||||||
|
readOnly={type === 'detail'}
|
||||||
|
className={{
|
||||||
|
wrapper: 'w-full min-w-24',
|
||||||
|
}}
|
||||||
|
placeholder='Masukkan berat telur (gram)...'
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
||||||
<td>
|
<td>
|
||||||
@@ -2779,46 +2715,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
</div>
|
</div>
|
||||||
{/* Right side actions */}
|
{/* Right side actions */}
|
||||||
<div className='flex flex-col sm:flex-row sm:justify-end gap-2 w-full sm:w-auto'>
|
<div className='flex flex-col sm:flex-row sm:justify-end gap-2 w-full sm:w-auto'>
|
||||||
{type === 'detail' && isLayingCategory && (
|
|
||||||
<Tooltip
|
|
||||||
content={
|
|
||||||
hasConsumableEggsInCurrentRecording
|
|
||||||
? 'Lanjut ke proses grading untuk telur konsumsi baik'
|
|
||||||
: 'Hanya bisa melanjutkan ke grading jika ada Telur Konsumsi Baik'
|
|
||||||
}
|
|
||||||
position='left'
|
|
||||||
color={
|
|
||||||
hasConsumableEggsInCurrentRecording ? 'info' : 'warning'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
type='button'
|
|
||||||
color='primary'
|
|
||||||
disabled={!hasConsumableEggsInCurrentRecording}
|
|
||||||
className='w-full sm:w-auto'
|
|
||||||
onClick={() => {
|
|
||||||
const recordingId =
|
|
||||||
newRecordingData?.id || initialValues?.id;
|
|
||||||
if (recordingId) {
|
|
||||||
router.push(
|
|
||||||
`/production/recording/grading/add?recording_id=${recordingId}`
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
toast.error(
|
|
||||||
'Recording ID tidak ditemukan. Silakan refresh halaman.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon icon='material-symbols:egg' width={24} height={24} />
|
|
||||||
{hasGradingData(initialValues) ||
|
|
||||||
hasGradingData(newRecordingData || undefined)
|
|
||||||
? 'Edit Grading'
|
|
||||||
: 'Lanjut ke Grading'}
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{type === 'edit' && (
|
{type === 'edit' && (
|
||||||
<div className='flex flex-col sm:flex-row gap-2 w-full sm:w-auto'>
|
<div className='flex flex-col sm:flex-row gap-2 w-full sm:w-auto'>
|
||||||
<Button
|
<Button
|
||||||
@@ -2870,78 +2766,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
>
|
>
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
{isLayingCategory && (
|
|
||||||
<Tooltip
|
|
||||||
content={
|
|
||||||
hasConsumableEggs
|
|
||||||
? 'Lanjut ke proses grading untuk telur konsumsi baik'
|
|
||||||
: 'Hanya bisa melanjutkan ke grading jika ada Telur Konsumsi Baik'
|
|
||||||
}
|
|
||||||
position='left'
|
|
||||||
color={hasConsumableEggs ? 'info' : 'warning'}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
type='button'
|
|
||||||
color='info'
|
|
||||||
className='px-4'
|
|
||||||
isLoading={formik.isSubmitting}
|
|
||||||
disabled={
|
|
||||||
hasExceededStock ||
|
|
||||||
!formik.isValid ||
|
|
||||||
formik.isSubmitting ||
|
|
||||||
!hasConsumableEggs
|
|
||||||
}
|
|
||||||
onClick={async () => {
|
|
||||||
if (!formik.isValid) {
|
|
||||||
await formik.validateForm();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setRecordingFormErrorMessage('');
|
|
||||||
formik.setSubmitting(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (isLayingCategory) {
|
|
||||||
const layingValues =
|
|
||||||
formik.values as RecordingLayingFormValues;
|
|
||||||
const layingPayload =
|
|
||||||
createLayingPayload(layingValues);
|
|
||||||
|
|
||||||
const recordingData =
|
|
||||||
await createRecordingHandlerWithRedirect(
|
|
||||||
layingPayload as CreateLayingRecordingPayload,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
if (recordingData?.id) {
|
|
||||||
toast.success(
|
|
||||||
'Recording berhasil disimpan! Mengalihkan ke form Grading...'
|
|
||||||
);
|
|
||||||
setTimeout(() => {
|
|
||||||
router.push(
|
|
||||||
`/production/recording/grading/add?recording_id=${recordingData.id}`
|
|
||||||
);
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
toast.error(
|
|
||||||
'Gagal membuat recording. Silakan coba lagi.'
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
formik.setSubmitting(false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='material-symbols:egg'
|
|
||||||
width={24}
|
|
||||||
height={24}
|
|
||||||
/>
|
|
||||||
Next Step: Grading
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -2979,8 +2803,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
|
|
||||||
{/* Approve Confirmation Modal */}
|
{/* Approve Confirmation Modal */}
|
||||||
{(type as 'add' | 'edit' | 'detail') === 'detail' &&
|
{(type as 'add' | 'edit' | 'detail') === 'detail' &&
|
||||||
!isRecordingApproved(initialValues) &&
|
!isRecordingApproved(initialValues) && (
|
||||||
(!isLayingCategory || hasGradingData(initialValues)) && (
|
|
||||||
<ConfirmationModalWithNotes
|
<ConfirmationModalWithNotes
|
||||||
ref={approveModal.ref}
|
ref={approveModal.ref}
|
||||||
type='success'
|
type='success'
|
||||||
@@ -3002,8 +2825,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
|
|
||||||
{/* Reject Confirmation Modal */}
|
{/* Reject Confirmation Modal */}
|
||||||
{(type as 'add' | 'edit' | 'detail') === 'detail' &&
|
{(type as 'add' | 'edit' | 'detail') === 'detail' &&
|
||||||
!isRecordingApproved(initialValues) &&
|
!isRecordingApproved(initialValues) && (
|
||||||
(!isLayingCategory || hasGradingData(initialValues)) && (
|
|
||||||
<ConfirmationModalWithNotes
|
<ConfirmationModalWithNotes
|
||||||
ref={rejectModal.ref}
|
ref={rejectModal.ref}
|
||||||
type='error'
|
type='error'
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,7 @@ import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
|||||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||||
|
|
||||||
import { cn, formatDate, formatCurrency } from '@/lib/helper';
|
import { cn, formatDate } from '@/lib/helper';
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
|
|
||||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||||
@@ -136,14 +136,6 @@ const PurchaseTable = () => {
|
|||||||
? formatDate(props.row.original.po_date, 'DD MMM YYYY')
|
? formatDate(props.row.original.po_date, 'DD MMM YYYY')
|
||||||
: '-',
|
: '-',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
accessorKey: 'due_date',
|
|
||||||
header: 'Jatuh Tempo',
|
|
||||||
cell: (props) =>
|
|
||||||
props.row.original.due_date
|
|
||||||
? formatDate(props.row.original.due_date, 'DD MMM YYYY')
|
|
||||||
: '-',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
header: 'Aging',
|
header: 'Aging',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
@@ -156,11 +148,6 @@ const PurchaseTable = () => {
|
|||||||
return `${diffDays} hari`;
|
return `${diffDays} hari`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
accessorKey: 'grand_total',
|
|
||||||
header: 'Total (Rp.)',
|
|
||||||
cell: (props) => formatCurrency(props.row.original.grand_total),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
header: 'Aksi',
|
header: 'Aksi',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ const PurchaseOrderAcceptApprovalForm = ({
|
|||||||
const [purchaseOrderFormErrorMessage, setPurchaseOrderFormErrorMessage] =
|
const [purchaseOrderFormErrorMessage, setPurchaseOrderFormErrorMessage] =
|
||||||
useState('');
|
useState('');
|
||||||
|
|
||||||
|
const isRejected = initialValues?.latest_approval?.action === 'REJECTED';
|
||||||
|
|
||||||
// ===== UTILITY FUNCTIONS =====
|
// ===== UTILITY FUNCTIONS =====
|
||||||
const isRepeaterInputError = (
|
const isRepeaterInputError = (
|
||||||
idx: number,
|
idx: number,
|
||||||
@@ -64,7 +66,6 @@ const PurchaseOrderAcceptApprovalForm = ({
|
|||||||
| 'expedition_vendor_id'
|
| 'expedition_vendor_id'
|
||||||
| 'received_qty'
|
| 'received_qty'
|
||||||
| 'transport_per_item'
|
| 'transport_per_item'
|
||||||
| 'transport_total'
|
|
||||||
): { isError: boolean; errorMessage: string } => {
|
): { isError: boolean; errorMessage: string } => {
|
||||||
const touchedItem = formik.touched.items?.[idx];
|
const touchedItem = formik.touched.items?.[idx];
|
||||||
const errorItem = formik.errors.items?.[idx] as
|
const errorItem = formik.errors.items?.[idx] as
|
||||||
@@ -163,6 +164,7 @@ const PurchaseOrderAcceptApprovalForm = ({
|
|||||||
validateOnBlur: true,
|
validateOnBlur: true,
|
||||||
onSubmit: async (values) => {
|
onSubmit: async (values) => {
|
||||||
const payload: CreateAcceptApprovalRequestPayload = {
|
const payload: CreateAcceptApprovalRequestPayload = {
|
||||||
|
action: 'APPROVED',
|
||||||
notes: values.notes || '',
|
notes: values.notes || '',
|
||||||
items:
|
items:
|
||||||
values.items?.map((formItem) => {
|
values.items?.map((formItem) => {
|
||||||
@@ -181,10 +183,6 @@ const PurchaseOrderAcceptApprovalForm = ({
|
|||||||
typeof formItem.transport_per_item === 'string'
|
typeof formItem.transport_per_item === 'string'
|
||||||
? parseFloat(formItem.transport_per_item) || 0
|
? parseFloat(formItem.transport_per_item) || 0
|
||||||
: formItem.transport_per_item || 0,
|
: formItem.transport_per_item || 0,
|
||||||
transport_total:
|
|
||||||
typeof formItem.transport_total === 'string'
|
|
||||||
? parseFloat(formItem.transport_total) || 0
|
|
||||||
: formItem.transport_total || 0,
|
|
||||||
};
|
};
|
||||||
}) || [],
|
}) || [],
|
||||||
};
|
};
|
||||||
@@ -239,9 +237,8 @@ const PurchaseOrderAcceptApprovalForm = ({
|
|||||||
vehicle_number: item.vehicle_number || '',
|
vehicle_number: item.vehicle_number || '',
|
||||||
expedition_vendor: null,
|
expedition_vendor: null,
|
||||||
expedition_vendor_id: 0,
|
expedition_vendor_id: 0,
|
||||||
received_qty: '',
|
received_qty: item.total_qty || '',
|
||||||
transport_per_item: '',
|
transport_per_item: '',
|
||||||
transport_total: '',
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
formik.setFieldValue('items', updatedItems);
|
formik.setFieldValue('items', updatedItems);
|
||||||
@@ -301,7 +298,7 @@ const PurchaseOrderAcceptApprovalForm = ({
|
|||||||
// ===== PURCHASE ITEM OPERATIONS =====
|
// ===== PURCHASE ITEM OPERATIONS =====
|
||||||
const handlePurchaseItemChange = (
|
const handlePurchaseItemChange = (
|
||||||
idx: number,
|
idx: number,
|
||||||
field: 'received_qty' | 'transport_per_item' | 'transport_total',
|
field: 'received_qty' | 'transport_per_item',
|
||||||
value: string | number
|
value: string | number
|
||||||
) => {
|
) => {
|
||||||
const numValue = typeof value === 'string' ? parseFloat(value) || 0 : value;
|
const numValue = typeof value === 'string' ? parseFloat(value) || 0 : value;
|
||||||
@@ -318,26 +315,6 @@ const PurchaseOrderAcceptApprovalForm = ({
|
|||||||
: parseFloat(
|
: parseFloat(
|
||||||
formik.values.items?.[idx]?.transport_per_item as string
|
formik.values.items?.[idx]?.transport_per_item as string
|
||||||
) || 0;
|
) || 0;
|
||||||
|
|
||||||
if (receivedQty > 0 && transportPerItem >= 0) {
|
|
||||||
const calculatedTransportTotal = receivedQty * transportPerItem;
|
|
||||||
formik.setFieldValue(
|
|
||||||
`items.${idx}.transport_total`,
|
|
||||||
calculatedTransportTotal
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (field === 'transport_total') {
|
|
||||||
const receivedQty =
|
|
||||||
parseFloat(formik.values.items?.[idx]?.received_qty as string) || 0;
|
|
||||||
if (receivedQty > 0 && numValue >= 0) {
|
|
||||||
const calculatedTransportPerItem = numValue / receivedQty;
|
|
||||||
formik.setFieldValue(
|
|
||||||
`items.${idx}.transport_per_item`,
|
|
||||||
calculatedTransportPerItem
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -386,10 +363,6 @@ const PurchaseOrderAcceptApprovalForm = ({
|
|||||||
Transport/Item
|
Transport/Item
|
||||||
<span className='text-error'>*</span>
|
<span className='text-error'>*</span>
|
||||||
</th>
|
</th>
|
||||||
<th>
|
|
||||||
Total Transport
|
|
||||||
<span className='text-error'>*</span>
|
|
||||||
</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -657,37 +630,6 @@ const PurchaseOrderAcceptApprovalForm = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
|
||||||
<NumberInput
|
|
||||||
required
|
|
||||||
name={`items.${idx}.transport_total`}
|
|
||||||
value={formItem?.transport_total || ''}
|
|
||||||
onChange={(e) =>
|
|
||||||
handlePurchaseItemChange(
|
|
||||||
idx,
|
|
||||||
'transport_total',
|
|
||||||
e.target.value
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onBlur={formik.handleBlur}
|
|
||||||
placeholder='Masukkan total transport'
|
|
||||||
allowNegative={false}
|
|
||||||
decimalScale={2}
|
|
||||||
thousandSeparator=','
|
|
||||||
decimalSeparator='.'
|
|
||||||
inputPrefix={'Rp'}
|
|
||||||
isError={
|
|
||||||
isRepeaterInputError(idx, 'transport_total').isError
|
|
||||||
}
|
|
||||||
errorMessage={
|
|
||||||
isRepeaterInputError(idx, 'transport_total')
|
|
||||||
.errorMessage
|
|
||||||
}
|
|
||||||
className={{
|
|
||||||
wrapper: 'min-w-40 md:min-w-52 lg:min-w-64',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -732,7 +674,8 @@ const PurchaseOrderAcceptApprovalForm = ({
|
|||||||
disabled={
|
disabled={
|
||||||
!formik.isValid ||
|
!formik.isValid ||
|
||||||
formik.isSubmitting ||
|
formik.isSubmitting ||
|
||||||
hasQuantityExceededErrors
|
hasQuantityExceededErrors ||
|
||||||
|
isRejected
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Submit
|
Submit
|
||||||
|
|||||||
@@ -23,10 +23,12 @@ type PurchaseRequestStaffApprovalFormSchemaType = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type PurchaseRequestManagerApprovalFormSchemaType = {
|
type PurchaseRequestManagerApprovalFormSchemaType = {
|
||||||
|
action: 'APPROVED' | 'REJECTED';
|
||||||
notes: string | null;
|
notes: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
type PurchaseRequestAcceptApprovalFormSchemaType = {
|
type PurchaseRequestAcceptApprovalFormSchemaType = {
|
||||||
|
action: 'APPROVED' | 'REJECTED';
|
||||||
notes: string | null;
|
notes: string | null;
|
||||||
items: {
|
items: {
|
||||||
purchase_item?: {
|
purchase_item?: {
|
||||||
@@ -45,7 +47,6 @@ type PurchaseRequestAcceptApprovalFormSchemaType = {
|
|||||||
expedition_vendor_id: number;
|
expedition_vendor_id: number;
|
||||||
received_qty: number | string;
|
received_qty: number | string;
|
||||||
transport_per_item: number | string;
|
transport_per_item: number | string;
|
||||||
transport_total: number | string;
|
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -83,7 +84,6 @@ export type PurchaseAcceptApprovalItemSchema = {
|
|||||||
expedition_vendor_id: number;
|
expedition_vendor_id: number;
|
||||||
received_qty: number | string;
|
received_qty: number | string;
|
||||||
transport_per_item: number | string;
|
transport_per_item: number | string;
|
||||||
transport_total: number | string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PurchaseDeleteItemsSchema = {
|
export type PurchaseDeleteItemsSchema = {
|
||||||
@@ -152,6 +152,10 @@ const PurchaseStaffApprovalItemObjectSchema: Yup.ObjectSchema<PurchaseStaffAppro
|
|||||||
|
|
||||||
const PurchaseManagerApprovalObjectSchema: Yup.ObjectSchema<PurchaseRequestManagerApprovalFormSchemaType> =
|
const PurchaseManagerApprovalObjectSchema: Yup.ObjectSchema<PurchaseRequestManagerApprovalFormSchemaType> =
|
||||||
Yup.object({
|
Yup.object({
|
||||||
|
action: Yup.mixed<'APPROVED' | 'REJECTED'>()
|
||||||
|
.oneOf(['APPROVED', 'REJECTED'], 'Action harus APPROVED atau REJECTED')
|
||||||
|
.required('Action wajib diisi!')
|
||||||
|
.default('APPROVED'),
|
||||||
notes: Yup.string().nullable().default(null),
|
notes: Yup.string().nullable().default(null),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -230,20 +234,6 @@ const PurchaseAcceptApprovalItemObjectSchema: Yup.ObjectSchema<PurchaseAcceptApp
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.typeError('Biaya transport per item harus berupa angka!'),
|
.typeError('Biaya transport per item harus berupa angka!'),
|
||||||
transport_total: Yup.mixed<string | number>()
|
|
||||||
.required('Total biaya transport wajib diisi!')
|
|
||||||
.test(
|
|
||||||
'is-valid-transport-total',
|
|
||||||
'Total biaya transport harus berupa angka lebih dari atau sama dengan 0!',
|
|
||||||
function (value) {
|
|
||||||
if (value === '' || value === null || value === undefined)
|
|
||||||
return false;
|
|
||||||
const numValue =
|
|
||||||
typeof value === 'string' ? parseFloat(value) : value;
|
|
||||||
return !isNaN(numValue) && numValue >= 0;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.typeError('Total biaya transport harus berupa angka!'),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const PurchaseRequestStaffApprovalFormSchema: Yup.ObjectSchema<PurchaseRequestStaffApprovalFormSchemaType> =
|
export const PurchaseRequestStaffApprovalFormSchema: Yup.ObjectSchema<PurchaseRequestStaffApprovalFormSchemaType> =
|
||||||
@@ -368,6 +358,7 @@ export const PurchaseRequestManagerApprovalFormDefaultValues = (
|
|||||||
purchase?: Purchase
|
purchase?: Purchase
|
||||||
): PurchaseRequestManagerApprovalFormSchemaType => {
|
): PurchaseRequestManagerApprovalFormSchemaType => {
|
||||||
return {
|
return {
|
||||||
|
action: 'APPROVED',
|
||||||
notes: purchase?.notes ?? null,
|
notes: purchase?.notes ?? null,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -378,6 +369,10 @@ export type PurchaseRequestManagerApprovalFormValues = Yup.InferType<
|
|||||||
|
|
||||||
export const PurchaseRequestAcceptApprovalFormSchema: Yup.ObjectSchema<PurchaseRequestAcceptApprovalFormSchemaType> =
|
export const PurchaseRequestAcceptApprovalFormSchema: Yup.ObjectSchema<PurchaseRequestAcceptApprovalFormSchemaType> =
|
||||||
Yup.object({
|
Yup.object({
|
||||||
|
action: Yup.mixed<'APPROVED' | 'REJECTED'>()
|
||||||
|
.oneOf(['APPROVED', 'REJECTED'], 'Action harus APPROVED atau REJECTED')
|
||||||
|
.required('Action wajib diisi!')
|
||||||
|
.default('APPROVED'),
|
||||||
notes: Yup.string().nullable().default(null),
|
notes: Yup.string().nullable().default(null),
|
||||||
items: Yup.array()
|
items: Yup.array()
|
||||||
.of(PurchaseAcceptApprovalItemObjectSchema)
|
.of(PurchaseAcceptApprovalItemObjectSchema)
|
||||||
@@ -388,6 +383,7 @@ export const PurchaseRequestAcceptApprovalFormSchema: Yup.ObjectSchema<PurchaseR
|
|||||||
|
|
||||||
export const PurchaseRequestAcceptApprovalFormInitialValues: PurchaseRequestAcceptApprovalFormSchemaType =
|
export const PurchaseRequestAcceptApprovalFormInitialValues: PurchaseRequestAcceptApprovalFormSchemaType =
|
||||||
{
|
{
|
||||||
|
action: 'APPROVED',
|
||||||
notes: '',
|
notes: '',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
@@ -399,7 +395,6 @@ export const PurchaseRequestAcceptApprovalFormInitialValues: PurchaseRequestAcce
|
|||||||
expedition_vendor_id: 0,
|
expedition_vendor_id: 0,
|
||||||
received_qty: '',
|
received_qty: '',
|
||||||
transport_per_item: '',
|
transport_per_item: '',
|
||||||
transport_total: '',
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@@ -408,6 +403,7 @@ export const PurchaseRequestAcceptApprovalFormDefaultValues = (
|
|||||||
purchase?: Purchase
|
purchase?: Purchase
|
||||||
): PurchaseRequestAcceptApprovalFormSchemaType => {
|
): PurchaseRequestAcceptApprovalFormSchemaType => {
|
||||||
return {
|
return {
|
||||||
|
action: 'APPROVED',
|
||||||
notes: purchase?.notes ?? null,
|
notes: purchase?.notes ?? null,
|
||||||
items: purchase?.items
|
items: purchase?.items
|
||||||
? purchase.items.map((item) => ({
|
? purchase.items.map((item) => ({
|
||||||
@@ -419,7 +415,6 @@ export const PurchaseRequestAcceptApprovalFormDefaultValues = (
|
|||||||
expedition_vendor_id: 0,
|
expedition_vendor_id: 0,
|
||||||
received_qty: '',
|
received_qty: '',
|
||||||
transport_per_item: '',
|
transport_per_item: '',
|
||||||
transport_total: '',
|
|
||||||
}))
|
}))
|
||||||
: [
|
: [
|
||||||
{
|
{
|
||||||
@@ -431,7 +426,6 @@ export const PurchaseRequestAcceptApprovalFormDefaultValues = (
|
|||||||
expedition_vendor_id: 0,
|
expedition_vendor_id: 0,
|
||||||
received_qty: '',
|
received_qty: '',
|
||||||
transport_per_item: '',
|
transport_per_item: '',
|
||||||
transport_total: '',
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ const PurchaseOrderStaffApprovalForm = ({
|
|||||||
return 'add';
|
return 'add';
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentStep = initialValues?.approval?.step_number || 1;
|
const currentStep = initialValues?.latest_approval?.step_number || 1;
|
||||||
|
|
||||||
switch (currentStep) {
|
switch (currentStep) {
|
||||||
case 1:
|
case 1:
|
||||||
@@ -77,7 +77,9 @@ const PurchaseOrderStaffApprovalForm = ({
|
|||||||
// Step 4+ (Penerimaan Barang dan selesai), tidak boleh edit kalau sudah disetujui
|
// Step 4+ (Penerimaan Barang dan selesai), tidak boleh edit kalau sudah disetujui
|
||||||
return 'edit';
|
return 'edit';
|
||||||
}
|
}
|
||||||
}, [rawDataApprovals, propType, initialValues?.approval?.step_number]);
|
}, [rawDataApprovals, propType, initialValues?.latest_approval?.step_number]);
|
||||||
|
|
||||||
|
const isRejected = initialValues?.latest_approval?.action === 'REJECTED';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
@@ -93,16 +95,16 @@ const PurchaseOrderStaffApprovalForm = ({
|
|||||||
|
|
||||||
// ===== UTILITY FUNCTIONS =====
|
// ===== UTILITY FUNCTIONS =====
|
||||||
const canUpdatePurchaseItems = useMemo(() => {
|
const canUpdatePurchaseItems = useMemo(() => {
|
||||||
if (!initialValues?.approval) return false;
|
if (!initialValues?.latest_approval) return false;
|
||||||
|
|
||||||
const currentStep = initialValues.approval.step_number;
|
const currentStep = initialValues.latest_approval.step_number;
|
||||||
return currentStep >= 3;
|
return currentStep >= 3;
|
||||||
}, [initialValues?.approval]);
|
}, [initialValues?.latest_approval]);
|
||||||
|
|
||||||
const canShowDeleteAddButtons = useMemo(() => {
|
const canShowDeleteAddButtons = useMemo(() => {
|
||||||
if (!initialValues?.approval) return false;
|
if (!initialValues?.latest_approval) return false;
|
||||||
|
|
||||||
const currentStep = initialValues.approval.step_number;
|
const currentStep = initialValues.latest_approval.step_number;
|
||||||
|
|
||||||
// Step 2 (Staff Purchase) dengan mode 'add' tidak boleh add/delete items
|
// Step 2 (Staff Purchase) dengan mode 'add' tidak boleh add/delete items
|
||||||
// User hanya boleh input harga dan total harga untuk items yang sudah ada
|
// User hanya boleh input harga dan total harga untuk items yang sudah ada
|
||||||
@@ -112,7 +114,7 @@ const PurchaseOrderStaffApprovalForm = ({
|
|||||||
|
|
||||||
// Step 3 (Manager Purchase) boleh add/delete items
|
// Step 3 (Manager Purchase) boleh add/delete items
|
||||||
return currentStep === 3;
|
return currentStep === 3;
|
||||||
}, [initialValues?.approval, type]);
|
}, [initialValues?.latest_approval, type]);
|
||||||
|
|
||||||
const isRepeaterInputError = (
|
const isRepeaterInputError = (
|
||||||
idx: number,
|
idx: number,
|
||||||
@@ -719,7 +721,10 @@ const PurchaseOrderStaffApprovalForm = ({
|
|||||||
'min-w-52 md:min-w-72 lg:min-w-80',
|
'min-w-52 md:min-w-72 lg:min-w-80',
|
||||||
}}
|
}}
|
||||||
bottomLabel={
|
bottomLabel={
|
||||||
'Previous: ' + purchaseItem.product.name
|
type === 'edit'
|
||||||
|
? 'Previous: ' +
|
||||||
|
purchaseItem.product.name
|
||||||
|
: undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
@@ -819,7 +824,11 @@ const PurchaseOrderStaffApprovalForm = ({
|
|||||||
thousandSeparator=','
|
thousandSeparator=','
|
||||||
decimalSeparator='.'
|
decimalSeparator='.'
|
||||||
inputPrefix={'Rp'}
|
inputPrefix={'Rp'}
|
||||||
bottomLabel={`Previous: Rp${formatNumber(initialValues?.items?.find((item) => item.id === purchaseItem.id)?.price || 0, 'id-ID', 2, 2)}`}
|
bottomLabel={
|
||||||
|
type === 'edit'
|
||||||
|
? `Previous: Rp${formatNumber(initialValues?.items?.find((item) => item.id === purchaseItem.id)?.price || 0, 'id-ID', 2, 2)}`
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
isError={
|
isError={
|
||||||
isRepeaterInputError(
|
isRepeaterInputError(
|
||||||
formItemIndex,
|
formItemIndex,
|
||||||
@@ -857,7 +866,11 @@ const PurchaseOrderStaffApprovalForm = ({
|
|||||||
thousandSeparator=','
|
thousandSeparator=','
|
||||||
decimalSeparator='.'
|
decimalSeparator='.'
|
||||||
inputPrefix={'Rp'}
|
inputPrefix={'Rp'}
|
||||||
bottomLabel={`Previous: Rp${formatNumber(initialValues?.items?.find((item) => item.id === purchaseItem.id)?.total_price || 0, 'id-ID', 2, 2)}`}
|
bottomLabel={
|
||||||
|
type === 'edit'
|
||||||
|
? `Previous: Rp${formatNumber(initialValues?.items?.find((item) => item.id === purchaseItem.id)?.total_price || 0, 'id-ID', 2, 2)}`
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
isError={
|
isError={
|
||||||
isRepeaterInputError(
|
isRepeaterInputError(
|
||||||
formItemIndex,
|
formItemIndex,
|
||||||
@@ -1131,7 +1144,7 @@ const PurchaseOrderStaffApprovalForm = ({
|
|||||||
color='primary'
|
color='primary'
|
||||||
className='px-4'
|
className='px-4'
|
||||||
isLoading={formik.isSubmitting}
|
isLoading={formik.isSubmitting}
|
||||||
disabled={!formik.isValid || formik.isSubmitting}
|
disabled={!formik.isValid || formik.isSubmitting || isRejected}
|
||||||
>
|
>
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ type PurchaseRequestFormSchemaType = {
|
|||||||
label: string;
|
label: string;
|
||||||
} | null;
|
} | null;
|
||||||
supplier_id: number;
|
supplier_id: number;
|
||||||
credit_term: number;
|
|
||||||
area?: {
|
area?: {
|
||||||
value: number;
|
value: number;
|
||||||
label: string;
|
label: string;
|
||||||
@@ -78,10 +77,6 @@ export const PurchaseRequestFormSchema: Yup.ObjectSchema<PurchaseRequestFormSche
|
|||||||
value: Yup.number().min(1).required(),
|
value: Yup.number().min(1).required(),
|
||||||
label: Yup.string().required(),
|
label: Yup.string().required(),
|
||||||
}).nullable(),
|
}).nullable(),
|
||||||
credit_term: Yup.number()
|
|
||||||
.required('Jangka waktu kredit wajib diisi!')
|
|
||||||
.min(0, 'Jangka waktu kredit tidak boleh kurang dari 0!')
|
|
||||||
.typeError('Jangka waktu kredit wajib diisi!'),
|
|
||||||
supplier_id: Yup.number()
|
supplier_id: Yup.number()
|
||||||
.required('Supplier wajib dipilih!')
|
.required('Supplier wajib dipilih!')
|
||||||
.min(1, 'Supplier wajib dipilih!')
|
.min(1, 'Supplier wajib dipilih!')
|
||||||
@@ -124,7 +119,6 @@ export const getPurchaseRequestFormInitialValues = (
|
|||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
supplier_id: initialValues?.supplier?.id ?? 0,
|
supplier_id: initialValues?.supplier?.id ?? 0,
|
||||||
credit_term: initialValues?.credit_term ?? 0,
|
|
||||||
area: initialValues?.area
|
area: initialValues?.area
|
||||||
? {
|
? {
|
||||||
value: initialValues.area.id,
|
value: initialValues.area.id,
|
||||||
|
|||||||
@@ -185,10 +185,6 @@ const PurchaseRequestForm = ({
|
|||||||
typeof values.supplier_id === 'string'
|
typeof values.supplier_id === 'string'
|
||||||
? parseInt(values.supplier_id) || 0
|
? parseInt(values.supplier_id) || 0
|
||||||
: values.supplier_id || 0,
|
: values.supplier_id || 0,
|
||||||
credit_term:
|
|
||||||
typeof values.credit_term === 'string'
|
|
||||||
? parseInt(values.credit_term) || 0
|
|
||||||
: values.credit_term || 0,
|
|
||||||
notes: values.notes || '',
|
notes: values.notes || '',
|
||||||
items: (values.items || []).map((item) => ({
|
items: (values.items || []).map((item) => ({
|
||||||
warehouse_id: Number(item.warehouse_id) || 0,
|
warehouse_id: Number(item.warehouse_id) || 0,
|
||||||
@@ -342,27 +338,6 @@ const PurchaseRequestForm = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// ===== UTILITY FUNCTIONS =====
|
// ===== UTILITY FUNCTIONS =====
|
||||||
const updateCreditTermBasedOnSupplier = useCallback(
|
|
||||||
(supplierId: number) => {
|
|
||||||
if (supplierId > 0 && isResponseSuccess(supplierRawData)) {
|
|
||||||
const supplierData = supplierRawData.data.find(
|
|
||||||
(s: Supplier) => s.id === supplierId
|
|
||||||
);
|
|
||||||
if (supplierData?.due_date) {
|
|
||||||
formik.setFieldTouched('credit_term', false);
|
|
||||||
formik.setFieldValue('credit_term', supplierData.due_date.toString());
|
|
||||||
} else {
|
|
||||||
formik.setFieldTouched('credit_term', false);
|
|
||||||
formik.setFieldValue('credit_term', '');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
formik.setFieldTouched('credit_term', false);
|
|
||||||
formik.setFieldValue('credit_term', '');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[supplierRawData]
|
|
||||||
);
|
|
||||||
|
|
||||||
const resetPurchaseItems = useCallback(() => {
|
const resetPurchaseItems = useCallback(() => {
|
||||||
if (formik.values.items) {
|
if (formik.values.items) {
|
||||||
formik.values.items.forEach((_, idx) => {
|
formik.values.items.forEach((_, idx) => {
|
||||||
@@ -377,16 +352,6 @@ const PurchaseRequestForm = ({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// ===== SIDE EFFECTS =====
|
// ===== SIDE EFFECTS =====
|
||||||
useEffect(() => {
|
|
||||||
if (formik.values.supplier_id && Number(formik.values.supplier_id) > 0) {
|
|
||||||
updateCreditTermBasedOnSupplier(Number(formik.values.supplier_id));
|
|
||||||
resetPurchaseItems();
|
|
||||||
} else {
|
|
||||||
formik.setFieldTouched('credit_term', false);
|
|
||||||
formik.setFieldValue('credit_term', '');
|
|
||||||
resetPurchaseItems();
|
|
||||||
}
|
|
||||||
}, [formik.values.supplier_id]);
|
|
||||||
|
|
||||||
// ===== FORM HANDLERS =====
|
// ===== FORM HANDLERS =====
|
||||||
const handleSupplierChange = useCallback(
|
const handleSupplierChange = useCallback(
|
||||||
@@ -402,23 +367,6 @@ const PurchaseRequestForm = ({
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleCreditTermChange = useCallback(
|
|
||||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const value = e.target.value;
|
|
||||||
|
|
||||||
formik.setFieldTouched('credit_term', true);
|
|
||||||
formik.setFieldValue('credit_term', value);
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleCreditTermBlur = useCallback(
|
|
||||||
(e: React.FocusEvent<HTMLInputElement>) => {
|
|
||||||
formik.handleBlur(e);
|
|
||||||
},
|
|
||||||
[formik]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleAreaChange = useCallback(
|
const handleAreaChange = useCallback(
|
||||||
(val: OptionType | OptionType[] | null) => {
|
(val: OptionType | OptionType[] | null) => {
|
||||||
const area = val as OptionType | null;
|
const area = val as OptionType | null;
|
||||||
@@ -499,7 +447,7 @@ const PurchaseRequestForm = ({
|
|||||||
body: 'flex flex-col gap-6',
|
body: 'flex flex-col gap-6',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className={'grid grid-cols-1 md:grid-cols-2 gap-6'}>
|
<div className={'grid grid-cols-1 md:grid-cols-3 gap-6'}>
|
||||||
<SelectInput
|
<SelectInput
|
||||||
required
|
required
|
||||||
label='Vendor'
|
label='Vendor'
|
||||||
@@ -518,29 +466,6 @@ const PurchaseRequestForm = ({
|
|||||||
isClearable
|
isClearable
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<NumberInput
|
|
||||||
required={!!formik.values.supplier_id}
|
|
||||||
label='Jatuh tempo (hari)'
|
|
||||||
name='credit_term'
|
|
||||||
value={formik.values.credit_term || ''}
|
|
||||||
onChange={handleCreditTermChange}
|
|
||||||
onBlur={handleCreditTermBlur}
|
|
||||||
isError={
|
|
||||||
formik.touched.credit_term &&
|
|
||||||
Boolean(formik.errors.credit_term)
|
|
||||||
}
|
|
||||||
errorMessage={formik.errors.credit_term as string}
|
|
||||||
readOnly={type === 'detail' || !formik.values.supplier_id}
|
|
||||||
disabled={type === 'detail' || !formik.values.supplier_id}
|
|
||||||
allowNegative={false}
|
|
||||||
decimalScale={0}
|
|
||||||
placeholder={
|
|
||||||
!formik.values.supplier_id
|
|
||||||
? 'Pilih Vendor terlebih dahulu'
|
|
||||||
: 'Masukkan jumlah hari jatuh tempo'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<SelectInput
|
<SelectInput
|
||||||
label='Area'
|
label='Area'
|
||||||
placeholder='Pilih Area...'
|
placeholder='Pilih Area...'
|
||||||
@@ -565,7 +490,7 @@ const PurchaseRequestForm = ({
|
|||||||
isClearable={type !== 'detail'}
|
isClearable={type !== 'detail'}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className={'col-span-2'}>
|
<div className={'md:col-span-3'}>
|
||||||
<TextInput
|
<TextInput
|
||||||
label='Notes'
|
label='Notes'
|
||||||
name='notes'
|
name='notes'
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import PurchaseOrderInvoice from '@/components/pages/purchase/order/PurchaseOrde
|
|||||||
|
|
||||||
import Card from '@/components/Card';
|
import Card from '@/components/Card';
|
||||||
import {
|
import {
|
||||||
|
CreateAcceptApprovalRequestPayload,
|
||||||
CreateManagerApprovalRequestPayload,
|
CreateManagerApprovalRequestPayload,
|
||||||
CreateStaffApprovalRequestPayload,
|
CreateStaffApprovalRequestPayload,
|
||||||
Purchase,
|
Purchase,
|
||||||
@@ -88,6 +89,8 @@ const PurchaseOrderDetail = ({
|
|||||||
const staffApprovalModal = useModal();
|
const staffApprovalModal = useModal();
|
||||||
const staffRejectionModal = useModal();
|
const staffRejectionModal = useModal();
|
||||||
const acceptApprovalModal = useModal();
|
const acceptApprovalModal = useModal();
|
||||||
|
const acceptRejectionModal = useModal();
|
||||||
|
const managerRejectionModal = useModal();
|
||||||
const editModal = useModal();
|
const editModal = useModal();
|
||||||
const penerimaanBarangModal = useModal();
|
const penerimaanBarangModal = useModal();
|
||||||
const deleteModal = useModal();
|
const deleteModal = useModal();
|
||||||
@@ -156,9 +159,9 @@ const PurchaseOrderDetail = ({
|
|||||||
}, [goodsReceiptItems]);
|
}, [goodsReceiptItems]);
|
||||||
|
|
||||||
const approvalStep = useMemo(() => {
|
const approvalStep = useMemo(() => {
|
||||||
if (!initialValues?.approval) return null;
|
if (!initialValues?.latest_approval) return null;
|
||||||
return initialValues.approval.step_number;
|
return initialValues.latest_approval.step_number;
|
||||||
}, [initialValues?.approval]);
|
}, [initialValues?.latest_approval]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
approvals,
|
approvals,
|
||||||
@@ -166,7 +169,7 @@ const PurchaseOrderDetail = ({
|
|||||||
rawDataApprovals,
|
rawDataApprovals,
|
||||||
refresh: refreshApprovals,
|
refresh: refreshApprovals,
|
||||||
} = useApprovalSteps({
|
} = useApprovalSteps({
|
||||||
latestApproval: initialValues?.approval,
|
latestApproval: initialValues?.latest_approval,
|
||||||
approvalLines: PURCHASE_ORDER_APPROVAL_LINE,
|
approvalLines: PURCHASE_ORDER_APPROVAL_LINE,
|
||||||
moduleName: 'PURCHASES',
|
moduleName: 'PURCHASES',
|
||||||
moduleId: initialValues?.id?.toString() ?? '',
|
moduleId: initialValues?.id?.toString() ?? '',
|
||||||
@@ -177,19 +180,22 @@ const PurchaseOrderDetail = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const showApprovalButton =
|
const showApprovalButton =
|
||||||
approvalStep !== null && approvalStep >= 1 && approvalStep <= 3;
|
approvalStep !== null &&
|
||||||
|
approvalStep >= 1 &&
|
||||||
|
approvalStep <= 3 &&
|
||||||
|
initialValues?.latest_approval?.action !== 'REJECTED';
|
||||||
|
|
||||||
const canDeleteItems = useMemo(() => {
|
const canDeleteItems = useMemo(() => {
|
||||||
if (!initialValues?.approval) return false;
|
if (!initialValues?.latest_approval) return false;
|
||||||
|
|
||||||
const currentStep = initialValues.approval.step_number;
|
const currentStep = initialValues.latest_approval.step_number;
|
||||||
|
|
||||||
const hasReachedStep5 = rawDataApprovals?.some(
|
const hasReachedStep5 = rawDataApprovals?.some(
|
||||||
(approval) => approval.step_number === 5
|
(approval) => approval.step_number === 5
|
||||||
);
|
);
|
||||||
|
|
||||||
return currentStep === 3 && !hasReachedStep5;
|
return currentStep === 3 && !hasReachedStep5;
|
||||||
}, [initialValues?.approval, rawDataApprovals]);
|
}, [initialValues?.latest_approval, rawDataApprovals]);
|
||||||
|
|
||||||
const handleApprovalClick = () => {
|
const handleApprovalClick = () => {
|
||||||
if (!approvalStep) return;
|
if (!approvalStep) return;
|
||||||
@@ -216,24 +222,30 @@ const PurchaseOrderDetail = ({
|
|||||||
case 1:
|
case 1:
|
||||||
staffRejectionModal.openModal();
|
staffRejectionModal.openModal();
|
||||||
break;
|
break;
|
||||||
|
case 2:
|
||||||
|
managerRejectionModal.openModal();
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
acceptRejectionModal.openModal();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const canShowPurchaseOrderInvoice = useMemo(() => {
|
const canShowPurchaseOrderInvoice = useMemo(() => {
|
||||||
if (!initialValues?.approval) return false;
|
if (!initialValues?.latest_approval) return false;
|
||||||
|
|
||||||
const currentStep = initialValues.approval.step_number;
|
const currentStep = initialValues.latest_approval.step_number;
|
||||||
return currentStep >= 3;
|
return currentStep >= 3;
|
||||||
}, [initialValues?.approval]);
|
}, [initialValues?.latest_approval]);
|
||||||
|
|
||||||
const canShowPenerimaanBarang = useMemo(() => {
|
const canShowPenerimaanBarang = useMemo(() => {
|
||||||
if (!initialValues?.approval) return false;
|
if (!initialValues?.latest_approval) return false;
|
||||||
|
|
||||||
const currentStep = initialValues.approval.step_number;
|
const currentStep = initialValues.latest_approval.step_number;
|
||||||
return currentStep === 5;
|
return currentStep === 5;
|
||||||
}, [initialValues?.approval]);
|
}, [initialValues?.latest_approval]);
|
||||||
|
|
||||||
const totalBeforeTax = useMemo(() => {
|
const totalBeforeTax = useMemo(() => {
|
||||||
return purchaseOrderItems.reduce(
|
return purchaseOrderItems.reduce(
|
||||||
@@ -296,6 +308,33 @@ const PurchaseOrderDetail = ({
|
|||||||
[initialValues?.id, searchParams, refetchData]
|
[initialValues?.id, searchParams, refetchData]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const createAcceptApprovalHandler = useCallback(
|
||||||
|
async (payload: CreateAcceptApprovalRequestPayload) => {
|
||||||
|
const purchaseRequestId = searchParams.get('purchaseId')
|
||||||
|
? parseInt(searchParams.get('purchaseId')!)
|
||||||
|
: initialValues?.id || 1;
|
||||||
|
|
||||||
|
if (!purchaseRequestId) {
|
||||||
|
toast.error('Purchase Request ID is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await PurchaseApi.acceptApproval.create(
|
||||||
|
purchaseRequestId,
|
||||||
|
payload
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isResponseError(res)) {
|
||||||
|
toast.error(res.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toast.success(res?.message as string);
|
||||||
|
refreshApprovals();
|
||||||
|
refetchData?.();
|
||||||
|
},
|
||||||
|
[initialValues?.id, searchParams, refreshApprovals, refetchData]
|
||||||
|
);
|
||||||
|
|
||||||
// ===== MODAL HANDLERS =====
|
// ===== MODAL HANDLERS =====
|
||||||
const handleStaffApprovalModalClose = useCallback(() => {
|
const handleStaffApprovalModalClose = useCallback(() => {
|
||||||
refreshApprovals();
|
refreshApprovals();
|
||||||
@@ -544,11 +583,6 @@ const PurchaseOrderDetail = ({
|
|||||||
accessorKey: 'transport_per_item',
|
accessorKey: 'transport_per_item',
|
||||||
cell: (props) => formatCurrency(props.getValue() as number),
|
cell: (props) => formatCurrency(props.getValue() as number),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
header: 'Transport Total',
|
|
||||||
accessorKey: 'transport_total',
|
|
||||||
cell: (props) => formatCurrency(props.getValue() as number),
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const summaryData = [
|
const summaryData = [
|
||||||
@@ -647,7 +681,7 @@ const PurchaseOrderDetail = ({
|
|||||||
<div className='space-y-4'>
|
<div className='space-y-4'>
|
||||||
<div className='group'>
|
<div className='group'>
|
||||||
<div className='flex items-start'>
|
<div className='flex items-start'>
|
||||||
<span className='font-medium text-gray-600 min-w-[140px] flex-shrink-0'>
|
<span className='font-medium text-gray-600 min-w-[140px] shrink-0'>
|
||||||
Area
|
Area
|
||||||
</span>
|
</span>
|
||||||
<span className='text-gray-900 ml-3 break-all'>
|
<span className='text-gray-900 ml-3 break-all'>
|
||||||
@@ -657,7 +691,7 @@ const PurchaseOrderDetail = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className='group'>
|
<div className='group'>
|
||||||
<div className='flex items-start'>
|
<div className='flex items-start'>
|
||||||
<span className='font-medium text-gray-600 min-w-[140px] flex-shrink-0'>
|
<span className='font-medium text-gray-600 min-w-[140px] shrink-0'>
|
||||||
Lokasi
|
Lokasi
|
||||||
</span>
|
</span>
|
||||||
<span className='text-gray-900 ml-3 break-all'>
|
<span className='text-gray-900 ml-3 break-all'>
|
||||||
@@ -671,7 +705,7 @@ const PurchaseOrderDetail = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className='group'>
|
<div className='group'>
|
||||||
<div className='flex items-start'>
|
<div className='flex items-start'>
|
||||||
<span className='font-medium text-gray-600 min-w-[140px] flex-shrink-0'>
|
<span className='font-medium text-gray-600 min-w-[140px] shrink-0'>
|
||||||
Gudang
|
Gudang
|
||||||
</span>
|
</span>
|
||||||
<span className='text-gray-900 ml-3 break-all'>
|
<span className='text-gray-900 ml-3 break-all'>
|
||||||
@@ -685,7 +719,7 @@ const PurchaseOrderDetail = ({
|
|||||||
<div className='space-y-4'>
|
<div className='space-y-4'>
|
||||||
<div className='group'>
|
<div className='group'>
|
||||||
<div className='flex items-start'>
|
<div className='flex items-start'>
|
||||||
<span className='font-medium text-gray-600 min-w-[140px] flex-shrink-0'>
|
<span className='font-medium text-gray-600 min-w-[140px] shrink-0'>
|
||||||
Nama Vendor
|
Nama Vendor
|
||||||
</span>
|
</span>
|
||||||
<span className='text-gray-900 font-medium ml-3 break-all'>
|
<span className='text-gray-900 font-medium ml-3 break-all'>
|
||||||
@@ -696,7 +730,7 @@ const PurchaseOrderDetail = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className='group'>
|
<div className='group'>
|
||||||
<div className='flex items-start'>
|
<div className='flex items-start'>
|
||||||
<span className='font-medium text-gray-600 min-w-[140px] flex-shrink-0'>
|
<span className='font-medium text-gray-600 min-w-[140px] shrink-0'>
|
||||||
Kategori Vendor
|
Kategori Vendor
|
||||||
</span>
|
</span>
|
||||||
<span className='text-gray-900 ml-3 break-all'>
|
<span className='text-gray-900 ml-3 break-all'>
|
||||||
@@ -706,18 +740,7 @@ const PurchaseOrderDetail = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className='group'>
|
<div className='group'>
|
||||||
<div className='flex items-start'>
|
<div className='flex items-start'>
|
||||||
<span className='font-medium text-gray-600 min-w-[140px] flex-shrink-0'>
|
<span className='font-medium text-gray-600 min-w-[140px] shrink-0'>
|
||||||
Tgl. Jatuh Tempo
|
|
||||||
</span>
|
|
||||||
<span className='text-gray-900 ml-3 break-all'>
|
|
||||||
: {formatDate(purchaseData.due_date, 'D MMM YYYY')} (
|
|
||||||
{purchaseData.credit_term} hari)
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='group'>
|
|
||||||
<div className='flex items-start'>
|
|
||||||
<span className='font-medium text-gray-600 min-w-[140px] flex-shrink-0'>
|
|
||||||
Nomor
|
Nomor
|
||||||
</span>
|
</span>
|
||||||
<span className='text-gray-900 ml-3 break-all'>
|
<span className='text-gray-900 ml-3 break-all'>
|
||||||
@@ -727,7 +750,7 @@ const PurchaseOrderDetail = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className='group'>
|
<div className='group'>
|
||||||
<div className='flex items-start'>
|
<div className='flex items-start'>
|
||||||
<span className='font-medium text-gray-600 min-w-[140px] flex-shrink-0'>
|
<span className='font-medium text-gray-600 min-w-[140px] shrink-0'>
|
||||||
Nomor PO
|
Nomor PO
|
||||||
</span>
|
</span>
|
||||||
<div className='ml-3'>
|
<div className='ml-3'>
|
||||||
@@ -925,6 +948,7 @@ const PurchaseOrderDetail = ({
|
|||||||
color: 'success',
|
color: 'success',
|
||||||
onClick: async (notes) => {
|
onClick: async (notes) => {
|
||||||
const payload: CreateManagerApprovalRequestPayload = {
|
const payload: CreateManagerApprovalRequestPayload = {
|
||||||
|
action: 'APPROVED',
|
||||||
notes: notes || null,
|
notes: notes || null,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1041,6 +1065,61 @@ const PurchaseOrderDetail = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Accept Rejection Modal */}
|
||||||
|
<ConfirmationModalWithNotes
|
||||||
|
ref={acceptRejectionModal.ref}
|
||||||
|
type='error'
|
||||||
|
text='Apakah Anda yakin ingin menolak (reject) penerimaan barang ini?'
|
||||||
|
placeholder='(Opsional) Masukkan alasan penolakan...'
|
||||||
|
rows={4}
|
||||||
|
closeOnBackdrop
|
||||||
|
primaryButton={{
|
||||||
|
text: 'Ya, Tolak',
|
||||||
|
color: 'error',
|
||||||
|
onClick: async (notes) => {
|
||||||
|
const payload: CreateAcceptApprovalRequestPayload = {
|
||||||
|
action: 'REJECTED',
|
||||||
|
notes: notes || null,
|
||||||
|
items: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
await createAcceptApprovalHandler(payload);
|
||||||
|
await refetchData?.();
|
||||||
|
acceptRejectionModal.closeModal();
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
secondaryButton={{
|
||||||
|
text: 'Batal',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Manager Rejection Modal */}
|
||||||
|
<ConfirmationModalWithNotes
|
||||||
|
ref={managerRejectionModal.ref}
|
||||||
|
type='error'
|
||||||
|
text='Apakah Anda yakin ingin menolak (reject) approval manajer untuk permintaan pembelian ini?'
|
||||||
|
placeholder='(Opsional) Masukkan alasan penolakan...'
|
||||||
|
rows={4}
|
||||||
|
closeOnBackdrop
|
||||||
|
primaryButton={{
|
||||||
|
text: 'Ya, Tolak',
|
||||||
|
color: 'error',
|
||||||
|
onClick: async (notes) => {
|
||||||
|
const payload: CreateManagerApprovalRequestPayload = {
|
||||||
|
action: 'REJECTED',
|
||||||
|
notes: notes || null,
|
||||||
|
};
|
||||||
|
|
||||||
|
await createManagerApprovalHandler(payload);
|
||||||
|
await refetchData?.();
|
||||||
|
managerRejectionModal.closeModal();
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
secondaryButton={{
|
||||||
|
text: 'Batal',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Delete Confirmation Modal */}
|
{/* Delete Confirmation Modal */}
|
||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
ref={deleteModal.ref}
|
ref={deleteModal.ref}
|
||||||
|
|||||||
@@ -309,9 +309,6 @@ const PurchaseOrderInvoice = ({ data }: PurchaseOrderInvoiceProps) => {
|
|||||||
{purchaseData?.supplier?.alias || ''})
|
{purchaseData?.supplier?.alias || ''})
|
||||||
</Text>
|
</Text>
|
||||||
<Text>{purchaseData?.supplier?.category || '-'}</Text>
|
<Text>{purchaseData?.supplier?.category || '-'}</Text>
|
||||||
<Text>
|
|
||||||
Credit Term: {purchaseData?.credit_term || 0} hari
|
|
||||||
</Text>
|
|
||||||
<Text>
|
<Text>
|
||||||
Due Date:{' '}
|
Due Date:{' '}
|
||||||
{purchaseData?.due_date
|
{purchaseData?.due_date
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export const PROJECT_FLOCK_KANDANGS_APPROVAL_LINE: ApprovalLine = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
step_number: 3,
|
step_number: 3,
|
||||||
step_name: 'Closed',
|
step_name: 'Selesai',
|
||||||
},
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
@@ -70,14 +70,10 @@ export const MARKETING_APPROVAL_LINE: ApprovalLine = [
|
|||||||
export const RECORDING_APPROVAL_LINE: ApprovalLine = [
|
export const RECORDING_APPROVAL_LINE: ApprovalLine = [
|
||||||
{
|
{
|
||||||
step_number: 1,
|
step_number: 1,
|
||||||
step_name: 'Grading-Telur',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
step_number: 2,
|
|
||||||
step_name: 'Pengajuan',
|
step_name: 'Pengajuan',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
step_number: 3,
|
step_number: 2,
|
||||||
step_name: 'Disetujui',
|
step_name: 'Disetujui',
|
||||||
},
|
},
|
||||||
] as const;
|
] as const;
|
||||||
@@ -85,14 +81,10 @@ export const RECORDING_APPROVAL_LINE: ApprovalLine = [
|
|||||||
export const GROWING_RECORDING_APPROVAL_LINE: ApprovalLine = [
|
export const GROWING_RECORDING_APPROVAL_LINE: ApprovalLine = [
|
||||||
{
|
{
|
||||||
step_number: 1,
|
step_number: 1,
|
||||||
step_name: 'Grading-Telur',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
step_number: 2,
|
|
||||||
step_name: 'Pengajuan',
|
step_name: 'Pengajuan',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
step_number: 3,
|
step_number: 2,
|
||||||
step_name: 'Disetujui',
|
step_name: 'Disetujui',
|
||||||
},
|
},
|
||||||
] as const;
|
] as const;
|
||||||
@@ -100,14 +92,10 @@ export const GROWING_RECORDING_APPROVAL_LINE: ApprovalLine = [
|
|||||||
export const LAYING_RECORDING_APPROVAL_LINE: ApprovalLine = [
|
export const LAYING_RECORDING_APPROVAL_LINE: ApprovalLine = [
|
||||||
{
|
{
|
||||||
step_number: 1,
|
step_number: 1,
|
||||||
step_name: 'Grading-Telur',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
step_number: 2,
|
|
||||||
step_name: 'Pengajuan',
|
step_name: 'Pengajuan',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
step_number: 3,
|
step_number: 2,
|
||||||
step_name: 'Disetujui',
|
step_name: 'Disetujui',
|
||||||
},
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
|||||||
@@ -233,14 +233,10 @@ export const APPROVAL_WORKFLOWS = [
|
|||||||
steps: [
|
steps: [
|
||||||
{
|
{
|
||||||
step_number: 1,
|
step_number: 1,
|
||||||
step_name: 'Grading-Telur',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
step_number: 2,
|
|
||||||
step_name: 'Pengajuan',
|
step_name: 'Pengajuan',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
step_number: 3,
|
step_number: 2,
|
||||||
step_name: 'Disetujui',
|
step_name: 'Disetujui',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ import {
|
|||||||
CreateRecordingPayload,
|
CreateRecordingPayload,
|
||||||
Recording,
|
Recording,
|
||||||
UpdateRecordingPayload,
|
UpdateRecordingPayload,
|
||||||
CreateGradingPayload,
|
|
||||||
UpdateGradingPayload,
|
|
||||||
NextDayRecording,
|
NextDayRecording,
|
||||||
} from '@/types/api/production/recording';
|
} from '@/types/api/production/recording';
|
||||||
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
|
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
|
||||||
@@ -64,28 +62,6 @@ export class RecordingService extends BaseApiService<
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async createGrading(
|
|
||||||
payload: CreateGradingPayload
|
|
||||||
): Promise<BaseApiResponse<unknown> | undefined> {
|
|
||||||
return await this.customRequest<BaseApiResponse<unknown>>('gradings', {
|
|
||||||
method: 'POST',
|
|
||||||
payload,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateGrading(
|
|
||||||
gradingId: number,
|
|
||||||
payload: UpdateGradingPayload
|
|
||||||
): Promise<BaseApiResponse<unknown> | undefined> {
|
|
||||||
return await this.customRequest<BaseApiResponse<unknown>>(
|
|
||||||
`gradings/${gradingId}`,
|
|
||||||
{
|
|
||||||
method: 'PUT',
|
|
||||||
payload,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteGrading(
|
async deleteGrading(
|
||||||
gradingId: number
|
gradingId: number
|
||||||
): Promise<BaseApiResponse<unknown> | undefined> {
|
): Promise<BaseApiResponse<unknown> | undefined> {
|
||||||
|
|||||||
+19
-1
@@ -56,8 +56,26 @@ export type ClosingExpense = {
|
|||||||
reference_number: string;
|
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 = {
|
export type CheckClosingResponse = {
|
||||||
unfinished_expenses: number;
|
unfinished_expenses: number;
|
||||||
stock_remaining: ProductWarehouse[];
|
stock_remaining: StockItem[];
|
||||||
expenses: ClosingExpense[];
|
expenses: ClosingExpense[];
|
||||||
};
|
};
|
||||||
|
|||||||
+6
-42
@@ -9,8 +9,7 @@ export type ProductionMetrics = {
|
|||||||
cum_intake: number;
|
cum_intake: number;
|
||||||
fcr_value: number;
|
fcr_value: number;
|
||||||
total_chick_qty: number;
|
total_chick_qty: number;
|
||||||
daily_depletion_rate?: number;
|
cum_depletion: number;
|
||||||
cum_depletion?: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BaseRecording = {
|
export type BaseRecording = {
|
||||||
@@ -18,42 +17,33 @@ export type BaseRecording = {
|
|||||||
project_flock_kandang_id: number;
|
project_flock_kandang_id: number;
|
||||||
record_datetime: string;
|
record_datetime: string;
|
||||||
day: number;
|
day: number;
|
||||||
created_by: User;
|
project_flock_category?: 'GROWING' | 'LAYING';
|
||||||
} & ProductionMetrics;
|
} & ProductionMetrics;
|
||||||
|
|
||||||
export type RecordingBW = {
|
export type RecordingBW = {
|
||||||
id: number;
|
|
||||||
recording_id: number;
|
|
||||||
avg_weight: number;
|
avg_weight: number;
|
||||||
qty: number;
|
qty: number;
|
||||||
total_weight: number;
|
total_weight: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RecordingDepletion = {
|
export type RecordingDepletion = {
|
||||||
id: number;
|
|
||||||
recording_id: number;
|
|
||||||
product_warehouse_id: number;
|
product_warehouse_id: number;
|
||||||
qty: number;
|
qty: number;
|
||||||
product_warehouse: ProductWarehouse;
|
product_warehouse: ProductWarehouse;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RecordingStock = {
|
export type RecordingStock = {
|
||||||
id: number;
|
|
||||||
recording_id: number;
|
|
||||||
product_warehouse_id: number;
|
product_warehouse_id: number;
|
||||||
usage_amount?: number;
|
usage_amount?: number;
|
||||||
usage_qty: number;
|
|
||||||
qty: number;
|
|
||||||
pending_qty: number;
|
pending_qty: number;
|
||||||
product_warehouse: ProductWarehouse;
|
product_warehouse: ProductWarehouse;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RecordingEgg = {
|
export type RecordingEgg = {
|
||||||
id: number;
|
id: number;
|
||||||
recording_id: number;
|
|
||||||
product_warehouse_id: number;
|
product_warehouse_id: number;
|
||||||
qty: number;
|
qty: number;
|
||||||
created_by: User;
|
weight: number;
|
||||||
product_warehouse: ProductWarehouse;
|
product_warehouse: ProductWarehouse;
|
||||||
gradings?: {
|
gradings?: {
|
||||||
grade: string;
|
grade: string;
|
||||||
@@ -71,19 +61,12 @@ export type GradingEgg = {
|
|||||||
|
|
||||||
export type Recording = BaseMetadata &
|
export type Recording = BaseMetadata &
|
||||||
BaseRecording & {
|
BaseRecording & {
|
||||||
project_flock_category?: 'GROWING' | 'LAYING';
|
|
||||||
approval?: BaseApproval;
|
approval?: BaseApproval;
|
||||||
egg_grading_status?: string | null;
|
created_user: User;
|
||||||
egg_grading_pending_qty?: number | null;
|
|
||||||
egg_grading_completed_qty?: number | null;
|
|
||||||
body_weights?: RecordingBW[];
|
body_weights?: RecordingBW[];
|
||||||
depletions?: RecordingDepletion[];
|
depletions?: RecordingDepletion[];
|
||||||
stocks?: RecordingStock[];
|
stocks?: RecordingStock[];
|
||||||
eggs?: RecordingEgg[];
|
eggs?: RecordingEgg[];
|
||||||
recording_bws?: RecordingBW[];
|
|
||||||
recording_depletions?: RecordingDepletion[];
|
|
||||||
recording_stocks?: RecordingStock[];
|
|
||||||
recording_eggs?: RecordingEgg[];
|
|
||||||
grading_eggs?: GradingEgg[];
|
grading_eggs?: GradingEgg[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -108,27 +91,10 @@ export type CreateGrowingRecordingPayload = {
|
|||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CreateGradingPayload = {
|
|
||||||
eggs_grading: {
|
|
||||||
recording_egg_id: number;
|
|
||||||
grade: string;
|
|
||||||
qty: number;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type UpdateGradingPayload = CreateGradingPayload;
|
|
||||||
|
|
||||||
export type CreateGradingRecordingPayload = {
|
|
||||||
eggs_grading: {
|
|
||||||
recording_egg_id: number;
|
|
||||||
grade: string;
|
|
||||||
qty: number;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type CreateEggPayload = {
|
export type CreateEggPayload = {
|
||||||
product_warehouse_id: number;
|
product_warehouse_id: number;
|
||||||
qty: number;
|
qty: number;
|
||||||
|
weight: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CreateLayingRecordingPayload = CreateGrowingRecordingPayload & {
|
export type CreateLayingRecordingPayload = CreateGrowingRecordingPayload & {
|
||||||
@@ -137,11 +103,9 @@ export type CreateLayingRecordingPayload = CreateGrowingRecordingPayload & {
|
|||||||
|
|
||||||
export type CreateRecordingPayload =
|
export type CreateRecordingPayload =
|
||||||
| CreateGrowingRecordingPayload
|
| CreateGrowingRecordingPayload
|
||||||
| CreateLayingRecordingPayload
|
| CreateLayingRecordingPayload;
|
||||||
| CreateGradingRecordingPayload;
|
|
||||||
|
|
||||||
export type UpdateGrowingRecordingPayload = CreateGrowingRecordingPayload;
|
export type UpdateGrowingRecordingPayload = CreateGrowingRecordingPayload;
|
||||||
export type UpdateLayingRecordingPayload = CreateLayingRecordingPayload;
|
export type UpdateLayingRecordingPayload = CreateLayingRecordingPayload;
|
||||||
export type UpdateGradingRecordingPayload = CreateGradingRecordingPayload;
|
|
||||||
|
|
||||||
export type UpdateRecordingPayload = CreateRecordingPayload;
|
export type UpdateRecordingPayload = CreateRecordingPayload;
|
||||||
|
|||||||
Vendored
+4
-7
@@ -42,7 +42,6 @@ export type PurchaseItem = {
|
|||||||
expedition_vendor_name?: string | null;
|
expedition_vendor_name?: string | null;
|
||||||
received_qty?: number | null;
|
received_qty?: number | null;
|
||||||
transport_per_item?: number | null;
|
transport_per_item?: number | null;
|
||||||
transport_total?: number | null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BasePurchase = {
|
export type BasePurchase = {
|
||||||
@@ -52,9 +51,7 @@ export type BasePurchase = {
|
|||||||
po_document_path?: string | null;
|
po_document_path?: string | null;
|
||||||
po_date: string;
|
po_date: string;
|
||||||
supplier: Supplier;
|
supplier: Supplier;
|
||||||
credit_term: number;
|
|
||||||
due_date: string;
|
due_date: string;
|
||||||
grand_total: number;
|
|
||||||
notes?: string | null;
|
notes?: string | null;
|
||||||
deleted_at?: string | null;
|
deleted_at?: string | null;
|
||||||
created_by: number;
|
created_by: number;
|
||||||
@@ -62,14 +59,13 @@ export type BasePurchase = {
|
|||||||
location?: Location;
|
location?: Location;
|
||||||
warehouse?: Warehouse;
|
warehouse?: Warehouse;
|
||||||
items?: PurchaseItem[];
|
items?: PurchaseItem[];
|
||||||
approval?: BaseApproval;
|
latest_approval?: BaseApproval;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Purchase = BaseMetadata & BasePurchase;
|
export type Purchase = BaseMetadata & BasePurchase;
|
||||||
|
|
||||||
export type CreatePurchaseRequestPayload = {
|
export type CreatePurchaseRequestPayload = {
|
||||||
supplier_id: number;
|
supplier_id: number;
|
||||||
credit_term: number;
|
|
||||||
notes?: string | null;
|
notes?: string | null;
|
||||||
items: {
|
items: {
|
||||||
warehouse_id: number;
|
warehouse_id: number;
|
||||||
@@ -103,11 +99,13 @@ export type UpdateStaffApprovalRequestPayload = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type CreateManagerApprovalRequestPayload = {
|
export type CreateManagerApprovalRequestPayload = {
|
||||||
|
action: 'APPROVED' | 'REJECTED';
|
||||||
notes?: string | null;
|
notes?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CreateAcceptApprovalRequestPayload = {
|
export type CreateAcceptApprovalRequestPayload = {
|
||||||
notes?: string;
|
action: 'APPROVED' | 'REJECTED';
|
||||||
|
notes?: string | null;
|
||||||
items: {
|
items: {
|
||||||
purchase_item_id: number;
|
purchase_item_id: number;
|
||||||
received_date: string;
|
received_date: string;
|
||||||
@@ -117,7 +115,6 @@ export type CreateAcceptApprovalRequestPayload = {
|
|||||||
expedition_vendor_id: number;
|
expedition_vendor_id: number;
|
||||||
received_qty: number;
|
received_qty: number;
|
||||||
transport_per_item: number;
|
transport_per_item: number;
|
||||||
transport_total: number;
|
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user