diff --git a/src/components/pages/inventory/adjustment/InventoryAdjustmentTable.tsx b/src/components/pages/inventory/adjustment/InventoryAdjustmentTable.tsx index f8bd4443..ed34efc2 100644 --- a/src/components/pages/inventory/adjustment/InventoryAdjustmentTable.tsx +++ b/src/components/pages/inventory/adjustment/InventoryAdjustmentTable.tsx @@ -8,7 +8,7 @@ import { useState, } from 'react'; import { usePathname } from 'next/navigation'; -import useSWR from 'swr'; +import useSWR, { mutate } from 'swr'; import { Icon } from '@iconify/react'; import { ColumnDef, ColumnSort, SortingState } from '@tanstack/react-table'; import { useFormik } from 'formik'; @@ -26,6 +26,10 @@ import { InventoryAdjustmentApi } from '@/services/api/inventory'; import { WarehouseApi, ProductApi } from '@/services/api/master-data'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useUiStore } from '@/stores/ui/ui.store'; +import ConfirmationModal from '@/components/modal/ConfirmationModal'; +import PopoverButton from '@/components/popover/PopoverButton'; +import PopoverContent from '@/components/popover/PopoverContent'; +import toast from 'react-hot-toast'; import { InventoryAdjustment } from '@/types/api/inventory/adjustment'; import { Warehouse } from '@/types/api/master-data/warehouse'; import { TRANSACTION_SUBTYPE_OPTIONS } from '@/config/constant'; @@ -38,6 +42,62 @@ import { AdjustmentFilterType, } from '@/components/pages/inventory/adjustment/filter/AdjustmentFilter'; import SelectInputRadio from '@/components/input/SelectInputRadio'; +import { CellContext } from '@tanstack/react-table'; + +const RowOptionsMenu = ({ + popoverPosition = 'bottom', + props, + deleteClickHandler, +}: { + popoverPosition: 'bottom' | 'top'; + props: CellContext; + deleteClickHandler: () => void; +}) => { + const popoverId = `adjustment#${props.row.original.id}`; + const popoverAnchorName = `--anchor-adjustment#${props.row.original.id}`; + + const closePopover = () => { + document.getElementById(popoverId)?.hidePopover(); + }; + + return ( +
+ + + + + +
+ + + +
+
+
+ ); +}; const InventoryAdjustmentTable = () => { const { searchValue, setSearchValue, setTableState } = useUiStore(); @@ -182,12 +242,39 @@ const InventoryAdjustmentTable = () => { formik.validateForm(); }; - const { data: inventoryAdjustments, isLoading } = useSWR( + const { + data: inventoryAdjustments, + isLoading, + mutate: refreshAdjustments, + } = useSWR( `${InventoryAdjustmentApi.basePath}${getTableFilterQueryString()}`, InventoryAdjustmentApi.getAllFetcher ); + const singleDeleteHandler = async () => { + setIsDeleteLoading(true); + + const response = await InventoryAdjustmentApi.delete( + selectedAdjustment?.id as number + ); + + singleDeleteModal.closeModal(); + setIsDeleteLoading(false); + + if (isResponseSuccess(response)) { + toast.success(response?.message || 'Successfully delete Adjustment!'); + refreshAdjustments(); + } else { + toast.error(response?.message || 'Failed to delete Adjustment'); + } + }; + const [sorting, setSorting] = useState([]); + const [selectedAdjustment, setSelectedAdjustment] = useState< + InventoryAdjustment | undefined + >(undefined); + const [isDeleteLoading, setIsDeleteLoading] = useState(false); + const singleDeleteModal = useModal(); useEffect(() => { updateFilter('search', searchValue); @@ -302,8 +389,39 @@ const InventoryAdjustmentTable = () => { header: 'Oleh', accessorFn: (row) => row.created_user?.name ?? '-', }, + { + id: 'actions', + header: 'Aksi', + cell: (props: CellContext) => { + const currentPageSize = + props.table.getPaginationRowModel().rows.length; + const currentPageRows = props.table.getPaginationRowModel().flatRows; + const currentRowRelativeIndex = + currentPageRows.findIndex((r) => r.id === props.row.id) + 1; + + const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2; + + const deleteClickHandler = () => { + setSelectedAdjustment(props.row.original); + singleDeleteModal.openModal(); + }; + + return ( + + ); + }, + }, ], - [tableFilterState.pageSize, tableFilterState.page] + [ + tableFilterState.pageSize, + tableFilterState.page, + singleDeleteModal, + setSelectedAdjustment, + ] ); const updateSortingFilter = useCallback( @@ -532,6 +650,21 @@ const InventoryAdjustmentTable = () => { + + ); }; diff --git a/src/components/pages/inventory/movement/MovementTable.tsx b/src/components/pages/inventory/movement/MovementTable.tsx index f953099d..2b6f11e6 100644 --- a/src/components/pages/inventory/movement/MovementTable.tsx +++ b/src/components/pages/inventory/movement/MovementTable.tsx @@ -8,7 +8,7 @@ import { useState, } from 'react'; import { usePathname } from 'next/navigation'; -import useSWR from 'swr'; +import useSWR, { mutate } from 'swr'; import { SortingState, CellContext, ColumnDef } from '@tanstack/react-table'; import { useFormik } from 'formik'; @@ -21,6 +21,8 @@ import { cn } from '@/lib/helper'; import { isResponseSuccess } from '@/lib/api-helper'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useUiStore } from '@/stores/ui/ui.store'; +import ConfirmationModal from '@/components/modal/ConfirmationModal'; +import toast from 'react-hot-toast'; import Button from '@/components/Button'; import DebouncedTextInput from '@/components/input/DebouncedTextInput'; import SelectInput, { useSelect } from '@/components/input/SelectInput'; @@ -41,9 +43,11 @@ import { const RowOptionsMenu = ({ popoverPosition = 'bottom', props, + deleteClickHandler, }: { popoverPosition: 'bottom' | 'top'; props: CellContext; + deleteClickHandler: () => void; }) => { const popoverId = `movement#${props.row.original.id}`; const popoverAnchorName = `--anchor-movement#${props.row.original.id}`; @@ -83,6 +87,20 @@ const RowOptionsMenu = ({ Detail + + + @@ -206,12 +224,37 @@ const MovementTable = () => { }; const [sorting, setSorting] = useState([]); + const [selectedMovement, setSelectedMovement] = useState< + Movement | undefined + >(undefined); + const [isDeleteLoading, setIsDeleteLoading] = useState(false); + const singleDeleteModal = useModal(); - const { data: movements, isLoading } = useSWR( + const { + data: movements, + isLoading, + mutate: refreshMovements, + } = useSWR( `${MovementApi.basePath}${getTableFilterQueryString()}`, MovementApi.getAllFetcher ); + const singleDeleteHandler = async () => { + setIsDeleteLoading(true); + + const response = await MovementApi.delete(selectedMovement?.id as number); + + singleDeleteModal.closeModal(); + setIsDeleteLoading(false); + + if (isResponseSuccess(response)) { + toast.success(response?.message || 'Successfully delete Movement!'); + refreshMovements(); + } else { + toast.error(response?.message || 'Failed to delete Movement'); + } + }; + useEffect(() => { updateFilter('search', searchValue); }, [searchValue, updateFilter]); @@ -275,16 +318,27 @@ const MovementTable = () => { const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2; + const deleteClickHandler = () => { + setSelectedMovement(props.row.original); + singleDeleteModal.openModal(); + }; + return ( ); }, }, ], - [tableFilterState.pageSize, tableFilterState.page] + [ + tableFilterState.pageSize, + tableFilterState.page, + singleDeleteModal, + setSelectedMovement, + ] ); return ( @@ -455,6 +509,21 @@ const MovementTable = () => { + + ); }; diff --git a/src/components/pages/inventory/movement/form/MovementForm.tsx b/src/components/pages/inventory/movement/form/MovementForm.tsx index f723e763..1907d498 100644 --- a/src/components/pages/inventory/movement/form/MovementForm.tsx +++ b/src/components/pages/inventory/movement/form/MovementForm.tsx @@ -82,6 +82,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { warehouse_id: number; warehouse_name: string; quantity: number; + transfer_available_qty?: number; } // ===== USE SELECT HOOKS ===== @@ -379,6 +380,8 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { warehouse_id: formik.values.source_warehouse_id ? formik.values.source_warehouse_id.toString() : '', + transfer_context: 'inventory_transfer', + stock_mode: 'exclude_chickin', } ); @@ -391,6 +394,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { warehouse_id: pw.warehouse.id, warehouse_name: pw.warehouse.name, quantity: pw.quantity, + transfer_available_qty: pw.transfer_available_qty, })) : []; }, [productWarehouses]); @@ -834,6 +838,22 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { }, [formik.values.products, formik.values.deliveries]); const getAvailableStock = useCallback( + (productId: number) => { + if (type === 'detail') return 0; + const productWarehouse = productWarehouseOptions.find( + (pw) => pw.product_id === productId + ); + + return ( + productWarehouse?.transfer_available_qty ?? + productWarehouse?.quantity ?? + 0 + ); + }, + [productWarehouseOptions, type] + ); + + const getTotalStock = useCallback( (productId: number) => { if (type === 'detail') return 0; const productWarehouse = productWarehouseOptions.find( @@ -844,6 +864,16 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { [productWarehouseOptions, type] ); + const hasAvailableQty = useCallback( + (productId: number) => { + const productWarehouse = productWarehouseOptions.find( + (pw) => pw.product_id === productId + ); + return productWarehouse?.transfer_available_qty !== undefined; + }, + [productWarehouseOptions] + ); + const getProductQtyBottomLabel = useCallback( (productIdx: number) => { if (type === 'detail') return undefined; @@ -851,16 +881,31 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { if (!product || !product.product_id) return undefined; const availableStock = getAvailableStock(product.product_id); + const totalStock = getTotalStock(product.product_id); const requestedQty = Number(product.product_qty) || 0; const remainingStock = availableStock - requestedQty; + const isAyamProduct = hasAvailableQty(product.product_id); if (requestedQty > 0) { + if (isAyamProduct) { + return `Sisa: ${formatNumber(remainingStock)} (Total: ${formatNumber(totalStock)})`; + } return `Sisa: ${formatNumber(remainingStock)}`; } + if (isAyamProduct) { + return `Tersedia: ${formatNumber(availableStock)} (Total: ${formatNumber(totalStock)})`; + } + return `Tersedia: ${formatNumber(availableStock)}`; }, - [formik.values.products, getAvailableStock, type] + [ + formik.values.products, + getAvailableStock, + getTotalStock, + hasAvailableQty, + type, + ] ); const getDeliveryProductQtyBottomLabel = useCallback( @@ -922,15 +967,26 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { if (!product || !product.product_id) return null; const availableStock = getAvailableStock(product.product_id); + const totalStock = getTotalStock(product.product_id); const requestedQty = Number(product.product_qty) || 0; + const isAyamProduct = hasAvailableQty(product.product_id); if (requestedQty > availableStock) { + if (isAyamProduct) { + return `Qty melebihi stok tersedia! Maksimal: ${formatNumber(availableStock)} (Total: ${formatNumber(totalStock)}, terpakai untuk chickin: ${formatNumber(totalStock - availableStock)})`; + } return `Qty melebihi stok tersedia! Maksimal: ${formatNumber(availableStock)}`; } return null; }, - [formik.values.products, getAvailableStock, type] + [ + formik.values.products, + getAvailableStock, + getTotalStock, + hasAvailableQty, + type, + ] ); const validateDeliveryQty = useCallback( diff --git a/src/components/pages/production/recording/RecordingTable.tsx b/src/components/pages/production/recording/RecordingTable.tsx index 1b7a326d..cea30502 100644 --- a/src/components/pages/production/recording/RecordingTable.tsx +++ b/src/components/pages/production/recording/RecordingTable.tsx @@ -21,6 +21,7 @@ import SelectInput, { useSelect } from '@/components/input/SelectInput'; import DebouncedTextInput from '@/components/input/DebouncedTextInput'; import PopoverButton from '@/components/popover/PopoverButton'; import PopoverContent from '@/components/popover/PopoverContent'; +import Tooltip from '@/components/Tooltip'; import { useFormik } from 'formik'; import { AreaApi } from '@/services/api/master-data'; import { LocationApi } from '@/services/api/master-data'; @@ -36,6 +37,7 @@ import { import RecordingTableSkeleton from '@/components/pages/production/recording/skeleton/RecordingTableSkeleton'; import Table from '@/components/Table'; import { type Recording } from '@/types/api/production/recording'; +import { getRecordingRestriction } from './recording-utils'; import { RecordingApi } from '@/services/api/production'; import { isResponseSuccess } from '@/lib/api-helper'; import { useTableFilter } from '@/services/hooks/useTableFilter'; @@ -105,30 +107,75 @@ const RowOptionsMenu = ({ }; const isRecordingEditable = (recording: Recording) => { - if ( - recording.executed_at && - recording.project_flock?.project_flock_category === 'GROWING' - ) { + const isGrowingCategory = + recording.project_flock?.project_flock_category === 'GROWING'; + const isGrowingLockedByLaying = isGrowingCategory && recording.is_laying; + if (isGrowingLockedByLaying) { + return false; + } + + const currentIsLaying = + recording.project_flock?.project_flock_category === 'LAYING'; + + const restriction = getRecordingRestriction( + recording.is_laying, + recording.is_transition, + currentIsLaying + ); + + if (restriction.isLocked) { return false; } return true; }; + const getRecordingRestrictionInfo = (recording: Recording) => { + const isGrowingCategory = + recording.project_flock?.project_flock_category === 'GROWING'; + const isGrowingLockedByLaying = isGrowingCategory && recording.is_laying; + if (isGrowingLockedByLaying) { + return { + canEditStock: false, + canEditDepletion: false, + canEditEgg: false, + isLocked: true, + lockReason: + 'Recording Growing tidak dapat diubah karena sudah masuk fase laying dan dipakai pada recording laying', + }; + } + + const currentIsLaying = + recording.project_flock?.project_flock_category === 'LAYING'; + + return getRecordingRestriction( + recording.is_laying, + recording.is_transition, + currentIsLaying + ); + }; + const isApproved = isRecordingApproved(props.row.original); const isRejected = isRecordingRejected(props.row.original); const isEditable = isRecordingEditable(props.row.original); + const restrictionInfo = getRecordingRestrictionInfo(props.row.original); return (
- - - + + + + { const singleDeleteHandler = async () => { setIsDeleteLoading(true); - await RecordingApi.delete(selectedRecording?.id as number); - refreshRecordings(); + const response = await RecordingApi.delete(selectedRecording?.id as number); singleDeleteModal.closeModal(); - toast.success('Successfully delete Recording!'); setIsDeleteLoading(false); + + if (isResponseSuccess(response)) { + toast.success(response?.message || 'Successfully delete Recording!'); + refreshRecordings(); + } else { + toast.error(response?.message || 'Failed to delete Recording'); + } }; const approveHandler = async (notes: string) => { @@ -761,11 +813,30 @@ const RecordingTable = () => { { header: 'Kategori', cell: (props) => { + const isTransition = props.row.original.is_transition; const category = - props.row.original.project_flock?.project_flock_category; - if (!category) return '-'; + props.row.original.project_flock?.project_flock_category || + 'GROWING'; const color = category === 'LAYING' ? 'info' : 'warning'; - return ; + + const isGrowingLocked = + category === 'GROWING' && props.row.original.is_laying; + + return ( +
+ + {isTransition && ( + + (Transisi) + + )} + {isGrowingLocked && ( + + (Penguncian) + + )} +
+ ); }, }, { diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index af4ab78b..c9236d19 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -70,7 +70,7 @@ import { } from '@/components/pages/production/recording/form/RecordingForm.schema'; import { isResponseSuccess, isResponseError } from '@/lib/api-helper'; -import { formatDate, formatNumber } from '@/lib/helper'; +import { formatDate, formatNumber, cn } from '@/lib/helper'; import toast from 'react-hot-toast'; import ApprovalSteps, { useApprovalSteps, @@ -79,7 +79,9 @@ import { GROWING_RECORDING_APPROVAL_LINE, LAYING_RECORDING_APPROVAL_LINE, } from '@/config/approval-line'; +import { PROJECT_FLOCK_STATUS } from '@/config/constant'; import { useFormikErrorList } from '@/services/hooks/useFormikErrorList'; +import { getRecordingRestriction } from '../recording-utils'; interface RecordingFormProps { type?: 'add' | 'edit' | 'detail'; @@ -242,6 +244,23 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const [isProductionStandardModalOpen, setIsProductionStandardModalOpen] = useState(false); + const calculateWeek = useCallback( + (day: number): number => { + if ( + productionStandards?.details && + productionStandards.details.length > 0 + ) { + const firstWeek = productionStandards.details[0].week; + + const weekOffset = Math.ceil(day / 7) - 1; + return firstWeek + weekOffset; + } + + return Math.ceil(day / 7); + }, + [productionStandards] + ); + useEffect(() => { const checkProductionStandardModalOpen = () => { const isOpen = productionStandardModal.ref.current?.open || false; @@ -272,73 +291,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { return recording?.approval?.action === 'REJECTED'; }, []); - const isRecordingEditable = useCallback((recording?: Recording) => { - if ( - recording?.executed_at && - recording?.project_flock?.project_flock_category === 'GROWING' - ) { - return false; - } - return true; - }, []); - - // ===== PAYLOAD CREATION HELPERS ===== - const createGrowingPayload = useCallback( - (values: RecordingGrowingFormValues) => { - const depletions = values.depletions - ?.filter((d) => d.product_warehouse_id && d.qty) - .map((depletion) => ({ - product_warehouse_id: depletion.product_warehouse_id!, - qty: Number(depletion.qty) || 0, - })); - - return { - project_flock_kandang_id: values.project_flock_kandang_id, - record_date: values.record_date, - stocks: (values.stocks ?? []).map((stock) => ({ - product_warehouse_id: stock.product_warehouse_id, - qty: Number(stock.qty) || 0, - })), - ...(depletions && depletions.length > 0 && { depletions }), - }; - }, - [] - ); - - const createLayingPayload = useCallback( - (values: RecordingLayingFormValues) => { - const depletions = values.depletions - ?.filter((d) => d.product_warehouse_id && d.qty) - .map((depletion) => ({ - product_warehouse_id: depletion.product_warehouse_id!, - qty: Number(depletion.qty) || 0, - })); - - const eggs = values.eggs - ?.filter((e) => e.product_warehouse_id && e.qty && e.weight) - .map((egg) => ({ - product_warehouse_id: egg.product_warehouse_id!, - qty: Number(egg.qty) || 0, - weight: - typeof egg.weight === 'number' - ? egg.weight - : parseFloat(String(egg.weight)) || 0, - })); - - return { - project_flock_kandang_id: values.project_flock_kandang_id, - record_date: values.record_date, - stocks: values.stocks.map((stock) => ({ - product_warehouse_id: stock.product_warehouse_id, - qty: Number(stock.qty) || 0, - })), - ...(depletions && depletions.length > 0 && { depletions }), - ...(eggs && eggs.length > 0 && { eggs }), - }; - }, - [] - ); - // ===== FORM HANDLERS ===== const createRecordingHandler = useCallback( async ( @@ -380,11 +332,17 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { if (!initialValues?.id) return; setIsDeleteLoading(true); - await RecordingApi.delete(initialValues.id); + const response = await RecordingApi.delete(initialValues.id); + deleteModal.closeModal(); - toast.success('Successfully delete Recording!'); setIsDeleteLoading(false); - router.push('/production/recording'); + + if (isResponseSuccess(response)) { + toast.success(response?.message || 'Successfully delete Recording!'); + router.push('/production/recording'); + } else { + toast.error(response?.message || 'Failed to delete Recording'); + } }, [deleteModal, initialValues?.id, router]); // ===== API DATA FETCHING ===== @@ -403,16 +361,19 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { loadMore: loadMoreProjectFlocks, } = useSelect(ProjectFlockApi.basePath, 'id', 'flock_name', 'search', { location_id: selectedProjectFlockLocationId, + status: PROJECT_FLOCK_STATUS.AKTIF, }); const projectFlockKandangLookupUrl = useMemo(() => { - if (!selectedProjectFlock || !selectedKandang) return null; + if (!selectedProjectFlock || !selectedKandang || !selectedRecordDate) + return null; const params = new URLSearchParams({ project_flock_id: selectedProjectFlock.value.toString(), kandang_id: selectedKandang.value.toString(), + record_date: selectedRecordDate, }); return `${ProjectFlockApi.basePath}/kandangs/lookup?${params.toString()}`; - }, [selectedProjectFlock, selectedKandang]); + }, [selectedProjectFlock, selectedKandang, selectedRecordDate]); const { data: projectFlockKandangLookupData } = useSWR( projectFlockKandangLookupUrl, @@ -444,13 +405,24 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { () => ProductionStandardApi.getSingle(productionStandardId!) ); + const { data: productionStandardForAdd } = useSWR( + type === 'add' && productionStandardId + ? `production-standard-add-${productionStandardId}` + : null, + () => ProductionStandardApi.getSingle(productionStandardId!) + ); + useEffect(() => { if (productionStandard?.status === 'success') { setProductionStandards( productionStandard.data as ProductionStandard | null ); + } else if (productionStandardForAdd?.status === 'success') { + setProductionStandards( + productionStandardForAdd.data as ProductionStandard | null + ); } - }, [productionStandard]); + }, [productionStandard, productionStandardForAdd]); const projectFlockKandangDetailUrl = useMemo(() => { if ( @@ -476,6 +448,159 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ? projectFlockKandangDetailData.data : undefined; + const selectedProjectFlockKandangId = useMemo(() => { + if (type === 'add') { + return projectFlockKandangLookup?.project_flock_kandang_id ?? null; + } + + return ( + projectFlockKandangDetail?.id ?? + initialValues?.project_flock?.project_flock_kandang_id ?? + null + ); + }, [ + type, + projectFlockKandangLookup, + projectFlockKandangDetail, + initialValues, + ]); + + // ===== TRANSITION RESTRICTION LOGIC ===== + const isTransitionPeriod = useMemo(() => { + return ( + initialValues?.is_transition ?? + projectFlockKandangLookup?.is_transition ?? + false + ); + }, [initialValues, projectFlockKandangLookup]); + + const recordingRestriction = useMemo(() => { + let isLaying: boolean; + if (initialValues?.is_laying !== undefined) { + isLaying = initialValues.is_laying; + } else if (projectFlockKandangLookup?.is_laying !== undefined) { + isLaying = projectFlockKandangLookup.is_laying; + } else { + isLaying = + projectFlockKandangDetail?.project_flock?.category === 'LAYING' || + false; + } + + const isTransition = + initialValues?.is_transition ?? + projectFlockKandangLookup?.is_transition ?? + false; + + const currentIsLaying = + type === 'edit' + ? projectFlockKandangDetail?.project_flock?.category === 'LAYING' + : projectFlockKandangLookup?.project_flock?.category === 'LAYING'; + + return getRecordingRestriction(isLaying, isTransition, currentIsLaying); + }, [ + initialValues, + projectFlockKandangLookup, + projectFlockKandangDetail, + type, + ]); + + // ===== PAYLOAD CREATION HELPERS ===== + const createGrowingPayload = useCallback( + (values: RecordingGrowingFormValues) => { + const depletions = recordingRestriction.canEditDepletion + ? values.depletions + ?.filter((d) => d.product_warehouse_id && d.qty) + .map((depletion) => ({ + product_warehouse_id: depletion.product_warehouse_id!, + qty: Number(depletion.qty) || 0, + })) + : []; + + const stocks = recordingRestriction.canEditStock + ? (values.stocks ?? []) + .filter((s) => s.product_warehouse_id && s.qty) + .map((stock) => ({ + product_warehouse_id: stock.product_warehouse_id, + qty: Number(stock.qty) || 0, + })) + : []; + + return { + project_flock_kandang_id: values.project_flock_kandang_id, + record_date: values.record_date, + ...(stocks.length > 0 && { stocks }), + ...(depletions.length > 0 && { depletions }), + }; + }, + [recordingRestriction.canEditStock, recordingRestriction.canEditDepletion] + ); + + const createLayingPayload = useCallback( + (values: RecordingLayingFormValues) => { + const depletions = values.depletions + ?.filter((d) => d.product_warehouse_id && d.qty) + .map((depletion) => ({ + product_warehouse_id: depletion.product_warehouse_id!, + qty: Number(depletion.qty) || 0, + })); + + const eggs = values.eggs + ?.filter((e) => e.product_warehouse_id && e.qty && e.weight) + .map((egg) => ({ + product_warehouse_id: egg.product_warehouse_id!, + qty: Number(egg.qty) || 0, + weight: + typeof egg.weight === 'number' + ? egg.weight + : parseFloat(String(egg.weight)) || 0, + })); + + const stocks = recordingRestriction.canEditStock + ? values.stocks + .filter((s) => s.product_warehouse_id && s.qty) + .map((stock) => ({ + product_warehouse_id: stock.product_warehouse_id, + qty: Number(stock.qty) || 0, + })) + : []; + + return { + project_flock_kandang_id: values.project_flock_kandang_id, + record_date: values.record_date, + ...(stocks.length > 0 && { stocks }), + ...(depletions && depletions.length > 0 && { depletions }), + ...(eggs && eggs.length > 0 && { eggs }), + }; + }, + [recordingRestriction.canEditStock] + ); + + const isRecordingEditable = useCallback((recording?: Recording) => { + if (!recording) return true; + + const isGrowingCategory = + recording.project_flock?.project_flock_category === 'GROWING'; + const isGrowingLockedByLaying = isGrowingCategory && recording.is_laying; + if (isGrowingLockedByLaying) { + return false; + } + + const currentIsLaying = + recording.project_flock?.project_flock_category === 'LAYING'; + + const restriction = getRecordingRestriction( + recording.is_laying, + recording.is_transition, + currentIsLaying + ); + + if (restriction.isLocked) { + return false; + } + + return true; + }, []); + const { options: stockProductOptions, rawData: stockProducts, @@ -581,15 +706,28 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { return approvedProjectFlockKandangsData.data; }, [approvedProjectFlockKandangsData]); - const isLayingCategory = - initialValues?.project_flock?.project_flock_category === 'LAYING' || - projectFlockKandangLookup?.project_flock?.category === 'LAYING' || - projectFlockKandangDetail?.project_flock?.category === 'LAYING'; + const isLayingCategory = useMemo(() => { + // Priority 1: initialValues category (for edit/detail mode) + if (initialValues?.project_flock?.project_flock_category !== undefined) { + return initialValues.project_flock.project_flock_category === 'LAYING'; + } - const isGrowingCategory = - initialValues?.project_flock?.project_flock_category === 'GROWING' || - projectFlockKandangLookup?.project_flock?.category === 'GROWING' || - projectFlockKandangDetail?.project_flock?.category === 'GROWING'; + // Priority 2: projectFlockKandangLookup category (for add mode) + if (projectFlockKandangLookup?.project_flock?.category !== undefined) { + return projectFlockKandangLookup.project_flock.category === 'LAYING'; + } + + // Priority 3: projectFlockKandangDetail (fallback for edit/detail mode) + return ( + projectFlockKandangDetail?.project_flock?.category === 'LAYING' || false + ); + }, [ + initialValues?.project_flock?.project_flock_category, + projectFlockKandangLookup, + projectFlockKandangDetail, + ]); + + const isGrowingCategory = !isLayingCategory; const recordingApprovalLines = useMemo(() => { if (isLayingCategory) { @@ -637,8 +775,36 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { return options; }, [locationOptions, projectFlockKandangDetail, type]); + const isProjectFlockActive = useCallback((projectFlock: ProjectFlock) => { + const approvalStepName = projectFlock.approval?.step_name + ?.trim() + .toLowerCase(); + if (approvalStepName) { + return approvalStepName === PROJECT_FLOCK_STATUS.AKTIF.toLowerCase(); + } + + return ( + projectFlock.status?.trim().toLowerCase() === + PROJECT_FLOCK_STATUS.AKTIF.toLowerCase() + ); + }, []); + + const activeProjectFlockIDs = useMemo(() => { + if (!isResponseSuccess(projectFlocksRawData)) return new Set(); + + const data = projectFlocksRawData.data as ProjectFlock[]; + return new Set( + data + .filter((projectFlock) => isProjectFlockActive(projectFlock)) + .map((projectFlock) => projectFlock.id) + ); + }, [projectFlocksRawData, isProjectFlockActive]); + const enhancedProjectFlockOptions = useMemo(() => { - const options = [...projectFlockOptions]; + const options = projectFlockOptions.filter((option) => { + if (type !== 'add') return true; + return activeProjectFlockIDs.has(Number(option.value)); + }); if (projectFlockKandangDetail && (type === 'edit' || type === 'detail')) { const currentProjectFlock = projectFlockKandangDetail.project_flock; @@ -654,7 +820,12 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { } return options; - }, [projectFlockOptions, projectFlockKandangDetail, type]); + }, [ + projectFlockOptions, + projectFlockKandangDetail, + type, + activeProjectFlockIDs, + ]); const kandangOptions = useMemo(() => { let options: OptionType[] = []; @@ -762,8 +933,41 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { projectFlockKandangDetail, ]); + const isProductWarehouseBelongsToSelectedProjectFlockKandang = useCallback( + (productWarehouse: ProductWarehouse) => { + if (!selectedProjectFlockKandangId) return false; + + return ( + productWarehouse.project_flock_kandang?.id === + selectedProjectFlockKandangId + ); + }, + [selectedProjectFlockKandangId] + ); + + const scopedStockProductIds = useMemo(() => { + if (!isResponseSuccess(stockProducts) || !selectedProjectFlockKandangId) { + return new Set(); + } + + const data = stockProducts.data as unknown as ProductWarehouse[]; + return new Set( + data + .filter(isProductWarehouseBelongsToSelectedProjectFlockKandang) + .map((product) => product.id) + ); + }, [ + stockProducts, + selectedProjectFlockKandangId, + isProductWarehouseBelongsToSelectedProjectFlockKandang, + ]); + const unifiedStockProducts = useMemo(() => { - const options = [...stockProductOptions]; + const options = selectedProjectFlockKandangId + ? stockProductOptions.filter((option) => + scopedStockProductIds.has(Number(option.value)) + ) + : []; if ( initialValues && @@ -787,19 +991,30 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { } return options; - }, [stockProductOptions, initialValues, type]); + }, [ + stockProductOptions, + initialValues, + type, + selectedProjectFlockKandangId, + scopedStockProductIds, + ]); const depletionProducts = useMemo(() => { const options: OptionType[] = []; - if (isResponseSuccess(depletionProductsData) && selectedKandang) { + if ( + isResponseSuccess(depletionProductsData) && + selectedProjectFlockKandangId + ) { const data = depletionProductsData.data as unknown as ProductWarehouse[]; - data.forEach((product) => { - options.push({ - value: product.id, - label: product.product.name, + data + .filter(isProductWarehouseBelongsToSelectedProjectFlockKandang) + .forEach((product) => { + options.push({ + value: product.id, + label: product.product.name, + }); }); - }); } if (initialValues && initialValues.depletions && type !== 'add') { @@ -822,19 +1037,27 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { } return options; - }, [depletionProductsData, initialValues, type, selectedKandang]); + }, [ + depletionProductsData, + initialValues, + type, + selectedProjectFlockKandangId, + isProductWarehouseBelongsToSelectedProjectFlockKandang, + ]); const eggProducts = useMemo(() => { const options: OptionType[] = []; - if (isResponseSuccess(eggProductsData) && selectedKandang) { + if (isResponseSuccess(eggProductsData) && selectedProjectFlockKandangId) { const data = eggProductsData.data as unknown as ProductWarehouse[]; - data.forEach((product) => { - options.push({ - value: product.id, - label: product.product.name, + data + .filter(isProductWarehouseBelongsToSelectedProjectFlockKandang) + .forEach((product) => { + options.push({ + value: product.id, + label: product.product.name, + }); }); - }); } if (initialValues && initialValues.eggs && type !== 'add') { @@ -854,7 +1077,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { } return options; - }, [eggProductsData, initialValues, type, selectedKandang]); + }, [ + eggProductsData, + initialValues, + type, + selectedProjectFlockKandangId, + isProductWarehouseBelongsToSelectedProjectFlockKandang, + ]); // ===== FORMIK SETUP ===== const formikInitialValues = useMemo(() => { @@ -924,6 +1153,14 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { }; } + if (!recordingRestriction.canEditStock) { + baseValues.stocks = []; + } + + if (!recordingRestriction.canEditDepletion) { + baseValues.depletions = []; + } + return baseValues; }, [ initialValues, @@ -934,6 +1171,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { selectedLocation, selectedProjectFlock, selectedKandang, + recordingRestriction.canEditStock, + recordingRestriction.canEditDepletion, ]); const formik = useFormik< @@ -954,7 +1193,36 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ? UpdateRecordingGrowingFormSchema : RecordingGrowingFormSchema; } - return schema.clone().concat( + + if (!recordingRestriction.canEditStock) { + schema = schema.shape({ + stocks: Yup.array() + .of( + Yup.object({ + product_warehouse_id: Yup.number().optional(), + qty: Yup.number().optional(), + }) + ) + .optional() + .default([]) as unknown as Yup.Schema, + }); + } + + if (!recordingRestriction.canEditDepletion) { + schema = schema.shape({ + depletions: Yup.array() + .of( + Yup.object({ + product_warehouse_id: Yup.number().optional(), + qty: Yup.number().optional(), + }) + ) + .optional() + .default([]) as unknown as Yup.Schema, + }); + } + + return schema.concat( Yup.object().shape({ project_flock_kandang_id: Yup.number().test( 'not-already-recorded', @@ -1299,6 +1567,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { setSelectedLocation(location); setSelectedProjectFlock(null); setSelectedKandang(null); + setProductionStandards(null); + setNextDayRecording(null); if (duplicateErrorShown) { toast.dismiss(); setDuplicateErrorShown(false); @@ -1323,6 +1593,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { setSelectedProjectFlock(projectFlock); setSelectedKandang(null); + setProductionStandards(null); + setNextDayRecording(null); if (duplicateErrorShown) { toast.dismiss(); setDuplicateErrorShown(false); @@ -1343,6 +1615,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { formik.setFieldValue('kandang_id', kandangId); setSelectedKandang(kandang); + setProductionStandards(null); + setNextDayRecording(null); if (duplicateErrorShown) { toast.dismiss(); setDuplicateErrorShown(false); @@ -1497,7 +1771,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { }, [ projectFlockKandangDetail, type, - enhancedProjectFlockOptions, formik.values.project_flock_kandang_id, setFieldValue, ]); @@ -1879,10 +2152,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {

{type === 'add' ? nextDayRecording - ? `Hari ke-${nextDayRecording.next_day} (Minggu ke-${Math.ceil(nextDayRecording.next_day / 7)})` + ? `Hari ke-${nextDayRecording.next_day} (Minggu ke-${calculateWeek(nextDayRecording.next_day)})` : '-' : initialValues?.day - ? `Hari ke-${initialValues.day} (Minggu ke-${Math.ceil(initialValues.day / 7)})` + ? `Hari ke-${initialValues.day} (Minggu ke-${calculateWeek(initialValues.day)})` : '-'}

@@ -1956,18 +2229,18 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
Kategori

- - {initialValues.project_flock?.project_flock_category} - + {(() => { + const category = + initialValues.project_flock?.project_flock_category || + 'GROWING'; + const color = + category === 'LAYING' ? 'info' : 'warning'; + return ( + + {category} + + ); + })()}

@@ -2103,9 +2376,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { {type === 'detail' && initialValues && (
{/* FCR Section */} @@ -2196,8 +2467,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { {/* Egg Production Section - Only for LAYING category */} {type === 'detail' && initialValues && - initialValues.project_flock?.project_flock_category === - 'LAYING' && ( + initialValues.is_laying && (
@@ -2292,239 +2562,293 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
)} - {/* Stocks Table */} - -
- - - - {(type as 'add' | 'edit' | 'detail') !== 'detail' && ( - - )} - - - {(type as 'add' | 'edit' | 'detail') !== 'detail' && ( - - )} - - - - {formik.values.stocks?.map((stock, idx) => ( - + {/* Stocks Table - Only show if can edit stock or has data */} + {(recordingRestriction.canEditStock || + (type === 'detail' && formik.values.stocks?.length > 0)) && ( + +
+
- 0 - } - onChange={( - e: React.ChangeEvent - ) => { - if (e.target.checked) { - setSelectedStocks( - formik.values.stocks?.map((_, idx) => idx) ?? [] - ); - } else { - setSelectedStocks([]); - } - }} - classNames={{ - wrapper: 'flex justify-center', - checkbox: 'checkbox checkbox-sm', - }} - /> - - Persediaan - - * - - - Jumlah Pakai - - * - - Action
+ + {(type as 'add' | 'edit' | 'detail') !== 'detail' && ( - )} - - + + {(type as 'add' | 'edit' | 'detail') !== 'detail' && ( - + )} - ))} - -
+ 0 + } onChange={( e: React.ChangeEvent ) => { if (e.target.checked) { - setSelectedStocks([...selectedStocks, idx]); - } else { setSelectedStocks( - selectedStocks.filter((i) => i !== idx) + formik.values.stocks?.map((_, idx) => idx) ?? + [] ); + } else { + setSelectedStocks([]); } }} + disabled={!recordingRestriction.canEditStock} classNames={{ wrapper: 'flex justify-center', checkbox: 'checkbox checkbox-sm', }} /> - + - - product.value === stock.product_warehouse_id - ) || null - } - onChange={(selectedOption) => { - const option = selectedOption as OptionType | null; - formik.setFieldValue( - `stocks.${idx}.product_warehouse_id`, - option?.value || 0 - ); - }} - options={getAvailableStockProductOptions(idx)} - placeholder={ - !formik.values.project_flock_kandang_id - ? 'Pilih kandang terlebih dahulu' - : 'Pilih Produk' - } - isLoading={isLoadingStockProducts} - onMenuScrollToBottom={loadMoreStockProducts} - isError={ - isRepeaterInputError( - 'stocks', - 'product_warehouse_id', - idx - ).isError - } - errorMessage={ - isRepeaterInputError( - 'stocks', - 'product_warehouse_id', - idx - ).errorMessage - } - className={{ - wrapper: 'w-full min-w-48', - }} - isSearchable - isDisabled={ - type === 'detail' || - !formik.values.project_flock_kandang_id - } - isClearable={type !== 'detail'} - inputPrefix={ - stock.product_warehouse_id - ? getProductFlagBadgeAdornment( - stock.product_warehouse_id - ) - : undefined - } - /> - -
- - {getStockUsageAdornment(idx)} -
-
+ Persediaan + + * + + + Jumlah Pakai + + * + + -
- -
-
Action
-
- {(type as 'add' | 'edit' | 'detail') !== 'detail' && ( -
- {selectedStocks.length > 0 && ( - - )} - + + + {formik.values.stocks?.map((stock, idx) => ( + + {(type as 'add' | 'edit' | 'detail') !== 'detail' && ( + + + ) => { + if (e.target.checked) { + setSelectedStocks([...selectedStocks, idx]); + } else { + setSelectedStocks( + selectedStocks.filter((i) => i !== idx) + ); + } + }} + disabled={!recordingRestriction.canEditStock} + classNames={{ + wrapper: 'flex justify-center', + checkbox: 'checkbox checkbox-sm', + }} + /> + + )} + + + product.value === stock.product_warehouse_id + ) || null + } + onChange={(selectedOption) => { + const option = + selectedOption as OptionType | null; + formik.setFieldValue( + `stocks.${idx}.product_warehouse_id`, + option?.value || 0 + ); + }} + options={getAvailableStockProductOptions(idx)} + placeholder={ + !formik.values.project_flock_kandang_id + ? 'Pilih kandang terlebih dahulu' + : 'Pilih Produk' + } + isLoading={isLoadingStockProducts} + onMenuScrollToBottom={loadMoreStockProducts} + isError={ + isRepeaterInputError( + 'stocks', + 'product_warehouse_id', + idx + ).isError + } + errorMessage={ + isRepeaterInputError( + 'stocks', + 'product_warehouse_id', + idx + ).errorMessage + } + className={{ + wrapper: 'w-full min-w-48', + }} + isSearchable + isDisabled={ + type === 'detail' || + !formik.values.project_flock_kandang_id || + !recordingRestriction.canEditStock + } + isClearable={type !== 'detail'} + inputPrefix={ + stock.product_warehouse_id + ? getProductFlagBadgeAdornment( + stock.product_warehouse_id + ) + : undefined + } + /> + + +
+ + {getStockUsageAdornment(idx)} +
+ + {(type as 'add' | 'edit' | 'detail') !== 'detail' && ( + +
+ +
+ + )} + + ))} + +
- )} -
+ {(type as 'add' | 'edit' | 'detail') !== 'detail' && ( +
+ {selectedStocks.length > 0 && + recordingRestriction.canEditStock && ( + + )} + + + +
+ )} + + )} + + {/* Transition Warning Banner -- MOVED UP -- */} + {isTransitionPeriod && ( +
+ + + {isLayingCategory + ? 'Masa Transisi Laying: Hanya Deplesi yang dapat diisi. Stock (Pakan/OVK) tidak dapat diinput.' + : 'Masa Transisi Growing: Hanya Stock (Pakan/OVK) yang dapat diisi. Deplesi tidak dapat diinput.'} + +
+ )} + + {/* Locked Recording Warning */} + {recordingRestriction.isLocked && ( +
+ + {recordingRestriction.lockReason} +
+ )} {/* Depletions Table */} {((type as 'add' | 'edit' | 'detail') !== 'detail' || @@ -2532,7 +2856,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { @@ -2562,6 +2890,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { setSelectedDepletions([]); } }} + disabled={!recordingRestriction.canEditDepletion} classNames={{ wrapper: 'flex justify-center', checkbox: 'checkbox checkbox-sm', @@ -2598,6 +2927,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ); } }} + disabled={!recordingRestriction.canEditDepletion} classNames={{ wrapper: 'flex justify-center', checkbox: 'checkbox checkbox-sm', @@ -2623,7 +2953,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ); }} options={getAvailableDepletionProductOptions(idx)} - placeholder='Pilih Kondisi' + placeholder={ + !formik.values.project_flock_kandang_id + ? 'Pilih kandang terlebih dahulu' + : 'Pilih Kondisi' + } isLoading={isLoadingDepletionProducts} onMenuScrollToBottom={loadMoreDepletionProducts} isError={ @@ -2640,7 +2974,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { idx ).errorMessage } - isDisabled={type === 'detail'} + isDisabled={ + type === 'detail' || + !formik.values.project_flock_kandang_id || + !recordingRestriction.canEditDepletion + } className={{ wrapper: 'w-full min-w-48', }} @@ -2679,7 +3017,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ) : null } - disabled={type === 'detail'} + disabled={ + type === 'detail' || + !recordingRestriction.canEditDepletion + } /> {(type as 'add' | 'edit' | 'detail') !== 'detail' && ( @@ -2689,6 +3030,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { type='button' color='error' onClick={() => removeDepletion(idx)} + disabled={ + !recordingRestriction.canEditDepletion + } > {
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
- {selectedDepletions.length > 0 && ( + {selectedDepletions.length > 0 && + recordingRestriction.canEditDepletion && ( + + )} + - )} - +
)} @@ -2847,7 +3205,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ); }} options={getAvailableEggProductOptions(idx)} - placeholder='Pilih Kondisi Telur' + placeholder={ + !formik.values.project_flock_kandang_id + ? 'Pilih kandang terlebih dahulu' + : 'Pilih Kondisi Telur' + } isLoading={isLoadingEggProducts} onMenuScrollToBottom={loadMoreEggProducts} isError={ @@ -2864,7 +3226,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { idx ).errorMessage } - isDisabled={type === 'detail'} + isDisabled={ + type === 'detail' || + !formik.values.project_flock_kandang_id + } className={{ wrapper: 'w-full min-w-48', }} @@ -2969,6 +3334,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { color='success' onClick={addEgg} className='w-fit' + disabled={!formik.values.project_flock_kandang_id} > Tambah Telur diff --git a/src/components/pages/production/recording/recording-utils.ts b/src/components/pages/production/recording/recording-utils.ts new file mode 100644 index 00000000..fe7d07f8 --- /dev/null +++ b/src/components/pages/production/recording/recording-utils.ts @@ -0,0 +1,73 @@ +export type RecordingRestriction = { + canEditStock: boolean; + canEditDepletion: boolean; + canEditEgg: boolean; + isLocked: boolean; + lockReason?: string; +}; + +export const getRecordingRestriction = ( + isLaying: boolean, + isTransition: boolean, + currentIsLaying?: boolean +): RecordingRestriction => { + if (isTransition && !isLaying) { + const isLayingKandangInTransition = currentIsLaying === true; + + if (isLayingKandangInTransition) { + return { + canEditStock: false, + canEditDepletion: true, + canEditEgg: true, + isLocked: false, + lockReason: undefined, + }; + } else { + return { + canEditStock: true, + canEditDepletion: false, + canEditEgg: false, + isLocked: false, + lockReason: undefined, + }; + } + } + + if (!isLaying && !isTransition && currentIsLaying) { + return { + canEditStock: false, + canEditDepletion: false, + canEditEgg: false, + isLocked: true, + lockReason: + 'Recording Growing telah terkunci karena Project Flock sudah masuk fase Laying', + }; + } + + if (!isLaying && !isTransition) { + return { + canEditStock: true, + canEditDepletion: true, + canEditEgg: false, + isLocked: false, + lockReason: undefined, + }; + } + if (isLaying && !isTransition) { + return { + canEditStock: true, + canEditDepletion: true, + canEditEgg: true, + isLocked: false, + lockReason: undefined, + }; + } + + return { + canEditStock: false, + canEditDepletion: false, + canEditEgg: false, + isLocked: true, + lockReason: 'Kondisi transisi tidak valid', + }; +}; diff --git a/src/config/constant.ts b/src/config/constant.ts index ca0682b4..99594b65 100644 --- a/src/config/constant.ts +++ b/src/config/constant.ts @@ -555,6 +555,12 @@ export const APPROVAL_WORKFLOWS = { ], }; +export const PROJECT_FLOCK_STATUS = { + PENGAJUAN: APPROVAL_WORKFLOWS.PROJECT_FLOCKS[0].step_name, + AKTIF: APPROVAL_WORKFLOWS.PROJECT_FLOCKS[1].step_name, + SELESAI: APPROVAL_WORKFLOWS.PROJECT_FLOCKS[2].step_name, +} as const; + export const ACCEPTED_FILE_TYPE = { PDF: { 'application/pdf': ['.pdf'], diff --git a/src/types/api/inventory/product-warehouse.d.ts b/src/types/api/inventory/product-warehouse.d.ts index 060be2ab..726cc135 100644 --- a/src/types/api/inventory/product-warehouse.d.ts +++ b/src/types/api/inventory/product-warehouse.d.ts @@ -9,8 +9,19 @@ export type BaseProductWarehouse = { warehouse_id: number; uom: Uom; quantity: number; + transfer_available_qty?: number; product: Product; warehouse: Warehouse; + project_flock_kandang?: { + id: number; + project_flock_id: number; + kandang_id: number; + period: number; + project_flock?: { + id: number; + flock_name: string; + }; + }; week?: number | null; }; diff --git a/src/types/api/production/project-flock.d.ts b/src/types/api/production/project-flock.d.ts index 204e7b49..41a6a1c0 100644 --- a/src/types/api/production/project-flock.d.ts +++ b/src/types/api/production/project-flock.d.ts @@ -74,6 +74,8 @@ export type ProjectFlockKandangLookup = { available_quantity?: number; population: number; chick_in_date: string; + is_transition: boolean; + is_laying: boolean; }; export type ProjectFlockAvailableQuantity = { diff --git a/src/types/api/production/recording.d.ts b/src/types/api/production/recording.d.ts index b78642b8..8ce0ef15 100644 --- a/src/types/api/production/recording.d.ts +++ b/src/types/api/production/recording.d.ts @@ -49,7 +49,8 @@ export type BaseRecording = { project_flock: ProjectFlock; record_datetime: string; day: number; - executed_at: string; + is_transition: boolean; + is_laying: boolean; } & ProductionMetrics; export type RecordingDepletion = {