mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-21 13:55:45 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ee0c47b0a1 | |||
| 93d4ff9339 | |||
| 39cb38a23f | |||
| c1087b37fb | |||
| c4e27edd56 | |||
| b020f2b187 | |||
| 375de4c86c | |||
| 8f361c4327 | |||
| 710be88794 | |||
| 0f7a2bd796 | |||
| 0c8a833e00 | |||
| 0c42bfd70c | |||
| 85dee607e0 | |||
| 6c7e310e67 | |||
| 85f6677c2a | |||
| 811850857d | |||
| 5494cb0ff2 | |||
| 11eeac3289 |
@@ -24,8 +24,8 @@ import {
|
||||
} from '@/types/api/api-general';
|
||||
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||
|
||||
export interface OptionType {
|
||||
value: string | number;
|
||||
export interface OptionType<T = string | number> {
|
||||
value: T;
|
||||
label: string;
|
||||
className?: string;
|
||||
labelClassName?: string;
|
||||
|
||||
@@ -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<InventoryAdjustment, unknown>;
|
||||
deleteClickHandler: () => void;
|
||||
}) => {
|
||||
const popoverId = `adjustment#${props.row.original.id}`;
|
||||
const popoverAnchorName = `--anchor-adjustment#${props.row.original.id}`;
|
||||
|
||||
const closePopover = () => {
|
||||
document.getElementById(popoverId)?.hidePopover();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='relative'>
|
||||
<PopoverButton
|
||||
tabIndex={0}
|
||||
variant='ghost'
|
||||
color='none'
|
||||
popoverTarget={popoverId}
|
||||
anchorName={popoverAnchorName}
|
||||
>
|
||||
<Icon icon='material-symbols:more-vert' width={16} height={16} />
|
||||
</PopoverButton>
|
||||
|
||||
<PopoverContent
|
||||
id={popoverId}
|
||||
anchorName={popoverAnchorName}
|
||||
position={popoverPosition === 'bottom' ? 'bottom-start' : 'left'}
|
||||
className='w-full max-w-40 rounded-xl border border-base-content/5 shadow-sm'
|
||||
>
|
||||
<div className='flex flex-col bg-base-100 rounded-xl'>
|
||||
<RequirePermission permissions='lti.inventory.delete'>
|
||||
<Button
|
||||
onClick={() => {
|
||||
deleteClickHandler();
|
||||
closePopover();
|
||||
}}
|
||||
variant='ghost'
|
||||
color='error'
|
||||
className='p-3 justify-start text-sm font-semibold w-full focus-visible:text-error-content hover:text-error-content'
|
||||
>
|
||||
<Icon icon='mdi:delete-outline' width={20} height={20} />
|
||||
Delete
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const InventoryAdjustmentTable = () => {
|
||||
const { searchValue, setSearchValue, setTableState } = useUiStore();
|
||||
@@ -80,13 +140,13 @@ const InventoryAdjustmentTable = () => {
|
||||
const formik = useFormik<AdjustmentFilterType>({
|
||||
initialValues: {
|
||||
product_id: null,
|
||||
warehouse_id: null,
|
||||
warehouse: null,
|
||||
transaction_type: null,
|
||||
},
|
||||
validationSchema: AdjustmentFilterSchema,
|
||||
onSubmit: (values, { setSubmitting }) => {
|
||||
updateFilter('productFilter', values.product_id || '');
|
||||
updateFilter('warehouseFilter', values.warehouse_id || '');
|
||||
updateFilter('warehouseFilter', String(values.warehouse?.value) || '');
|
||||
updateFilter('transactionTypeFilter', values.transaction_type || '');
|
||||
filterModal.closeModal();
|
||||
setSubmitting(false);
|
||||
@@ -142,14 +202,11 @@ const InventoryAdjustmentTable = () => {
|
||||
[formik]
|
||||
);
|
||||
|
||||
const handleFilterWarehouseChange = useCallback(
|
||||
(val: OptionType | OptionType[] | null) => {
|
||||
const warehouse = val as OptionType | null;
|
||||
const warehouseId = warehouse?.value ? String(warehouse.value) : null;
|
||||
formik.setFieldValue('warehouse_id', warehouseId);
|
||||
},
|
||||
[formik]
|
||||
);
|
||||
const handleFilterWarehouseChange = (
|
||||
val: OptionType | OptionType[] | null
|
||||
) => {
|
||||
formik.setFieldValue('warehouse', val);
|
||||
};
|
||||
|
||||
const handleFilterTransactionTypeChange = useCallback(
|
||||
(val: OptionType | OptionType[] | null) => {
|
||||
@@ -170,15 +227,6 @@ const InventoryAdjustmentTable = () => {
|
||||
);
|
||||
}, [formik.values.product_id, productOptions]);
|
||||
|
||||
const warehouseIdValue = useMemo(() => {
|
||||
if (!formik.values.warehouse_id) return null;
|
||||
return (
|
||||
warehouseOptions.find(
|
||||
(opt) => String(opt.value) === formik.values.warehouse_id
|
||||
) || null
|
||||
);
|
||||
}, [formik.values.warehouse_id, warehouseOptions]);
|
||||
|
||||
const transactionTypeValue = useMemo(() => {
|
||||
if (!formik.values.transaction_type) return null;
|
||||
return (
|
||||
@@ -194,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<SortingState>([]);
|
||||
const [selectedAdjustment, setSelectedAdjustment] = useState<
|
||||
InventoryAdjustment | undefined
|
||||
>(undefined);
|
||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||
const singleDeleteModal = useModal();
|
||||
|
||||
useEffect(() => {
|
||||
updateFilter('search', searchValue);
|
||||
@@ -314,8 +389,39 @@ const InventoryAdjustmentTable = () => {
|
||||
header: 'Oleh',
|
||||
accessorFn: (row) => row.created_user?.name ?? '-',
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Aksi',
|
||||
cell: (props: CellContext<InventoryAdjustment, unknown>) => {
|
||||
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 (
|
||||
<RowOptionsMenu
|
||||
props={props}
|
||||
deleteClickHandler={deleteClickHandler}
|
||||
popoverPosition={isLast2Rows ? 'top' : 'bottom'}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
[tableFilterState.pageSize, tableFilterState.page]
|
||||
[
|
||||
tableFilterState.pageSize,
|
||||
tableFilterState.page,
|
||||
singleDeleteModal,
|
||||
setSelectedAdjustment,
|
||||
]
|
||||
);
|
||||
|
||||
const updateSortingFilter = useCallback(
|
||||
@@ -502,7 +608,7 @@ const InventoryAdjustmentTable = () => {
|
||||
label='Gudang'
|
||||
placeholder='Pilih Gudang'
|
||||
options={warehouseOptions}
|
||||
value={warehouseIdValue}
|
||||
value={formik.values.warehouse}
|
||||
onChange={handleFilterWarehouseChange}
|
||||
onInputChange={setWarehouseInputValue}
|
||||
isLoading={isLoadingWarehouseOptions}
|
||||
@@ -544,6 +650,21 @@ const InventoryAdjustmentTable = () => {
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
|
||||
<ConfirmationModal
|
||||
ref={singleDeleteModal.ref}
|
||||
type='error'
|
||||
text={`Apakah anda yakin ingin menghapus data Adjustment ini?`}
|
||||
secondaryButton={{
|
||||
text: 'Tidak',
|
||||
}}
|
||||
primaryButton={{
|
||||
text: 'Ya',
|
||||
color: 'error',
|
||||
isLoading: isDeleteLoading,
|
||||
onClick: singleDeleteHandler,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { string, object } from 'yup';
|
||||
import { OptionType } from '@/components/input/SelectInput';
|
||||
|
||||
export const AdjustmentFilterSchema = object().shape({
|
||||
product_id: string().nullable(),
|
||||
@@ -8,6 +9,6 @@ export const AdjustmentFilterSchema = object().shape({
|
||||
|
||||
export type AdjustmentFilterType = {
|
||||
product_id: string | null;
|
||||
warehouse_id: string | null;
|
||||
transaction_type: string | null;
|
||||
warehouse: OptionType<number> | null;
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
InventoryAdjustmentFormSchema,
|
||||
InventoryAdjustmentFormValues,
|
||||
} from '@/components/pages/inventory/adjustment/form/InventoryAdjustmentForm.schema';
|
||||
import { KandangApi, LocationApi } from '@/services/api/master-data';
|
||||
import { LocationApi } from '@/services/api/master-data';
|
||||
import {
|
||||
ProjectFlockApi,
|
||||
ProjectFlockKandangApi,
|
||||
@@ -32,8 +32,6 @@ import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
|
||||
import AlertErrorList from '@/components/helper/form/FormErrors';
|
||||
import { Location } from '@/types/api/master-data/location';
|
||||
import { ProjectFlock } from '@/types/api/production/project-flock';
|
||||
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
|
||||
import { Kandang } from '@/types/api/master-data/kandang';
|
||||
import { Product } from '@/types/api/master-data/product';
|
||||
import { ProjectFlockKandangLookup } from '@/types/api/production/project-flock';
|
||||
import { BaseApiResponse } from '@/types/api/api-general';
|
||||
@@ -119,40 +117,19 @@ const InventoryAdjustmentForm = ({
|
||||
}
|
||||
);
|
||||
|
||||
const { rawData: approvedProjectFlockKandangsRawData } =
|
||||
useSelect<ProjectFlockKandang>(
|
||||
ProjectFlockKandangApi.basePath,
|
||||
'id',
|
||||
'id',
|
||||
'search',
|
||||
{
|
||||
step_name: 'Disetujui',
|
||||
limit: '100',
|
||||
}
|
||||
);
|
||||
|
||||
const approvedProjectFlockKandangs = useMemo(() => {
|
||||
if (
|
||||
approvedProjectFlockKandangsRawData &&
|
||||
'data' in approvedProjectFlockKandangsRawData
|
||||
) {
|
||||
return approvedProjectFlockKandangsRawData.data as ProjectFlockKandang[];
|
||||
}
|
||||
return [];
|
||||
}, [approvedProjectFlockKandangsRawData]);
|
||||
|
||||
const {
|
||||
setInputValue: setKandangInputValue,
|
||||
options: kandangOptionsFromApi,
|
||||
isLoadingOptions: isLoadingKandangOptions,
|
||||
loadMore: loadMoreKandangs,
|
||||
} = useSelect<Kandang>(
|
||||
selectedProjectFlock ? KandangApi.basePath : '',
|
||||
'id',
|
||||
'name',
|
||||
options: projectFlockKandangOptions,
|
||||
loadMore: loadMoreProjectFlockKandangs,
|
||||
setInputValue: setProjectFlockKandangInputValue,
|
||||
isLoadingOptions: isLoadingProjectFlockKandangOptions,
|
||||
} = useSelect(
|
||||
selectedProjectFlock ? ProjectFlockKandangApi.basePath : '',
|
||||
'kandang.id',
|
||||
'kandang.name',
|
||||
'search',
|
||||
{
|
||||
location_id: selectedProjectFlockLocationId,
|
||||
step_name: 'Disetujui',
|
||||
project_flock_id: String(selectedProjectFlock?.value),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -222,26 +199,6 @@ const InventoryAdjustmentForm = ({
|
||||
return (product?.flags as string[]) || [];
|
||||
}, [selectedProduct, productOptions]);
|
||||
|
||||
const kandangOptions = useMemo(() => {
|
||||
let options: OptionType[] = [];
|
||||
|
||||
if (selectedProjectFlock) {
|
||||
const approvedKandangIds = approvedProjectFlockKandangs
|
||||
.filter((pfk) => pfk.project_flock_id === selectedProjectFlock.value)
|
||||
.map((pfk) => pfk.kandang_id);
|
||||
|
||||
options = kandangOptionsFromApi.filter((kandang) =>
|
||||
approvedKandangIds.includes(kandang.value as number)
|
||||
);
|
||||
}
|
||||
|
||||
return options;
|
||||
}, [
|
||||
selectedProjectFlock,
|
||||
kandangOptionsFromApi,
|
||||
approvedProjectFlockKandangs,
|
||||
]);
|
||||
|
||||
const formikInitialValues = useMemo<Partial<InventoryAdjustmentFormValues>>(
|
||||
() => ({
|
||||
location: null,
|
||||
@@ -693,10 +650,10 @@ const InventoryAdjustmentForm = ({
|
||||
label='Kandang'
|
||||
value={selectedKandang}
|
||||
onChange={kandangChangeHandler}
|
||||
onInputChange={setKandangInputValue}
|
||||
options={kandangOptions}
|
||||
onMenuScrollToBottom={loadMoreKandangs}
|
||||
isLoading={isLoadingKandangOptions}
|
||||
onInputChange={setProjectFlockKandangInputValue}
|
||||
options={projectFlockKandangOptions}
|
||||
onMenuScrollToBottom={loadMoreProjectFlockKandangs}
|
||||
isLoading={isLoadingProjectFlockKandangOptions}
|
||||
isError={
|
||||
formik.touched.kandang_id && Boolean(formik.errors.kandang_id)
|
||||
}
|
||||
|
||||
@@ -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<Movement, unknown>;
|
||||
deleteClickHandler: () => void;
|
||||
}) => {
|
||||
const popoverId = `movement#${props.row.original.id}`;
|
||||
const popoverAnchorName = `--anchor-movement#${props.row.original.id}`;
|
||||
@@ -83,6 +87,20 @@ const RowOptionsMenu = ({
|
||||
Detail
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
<RequirePermission permissions='lti.inventory.transfer.delete'>
|
||||
<Button
|
||||
onClick={() => {
|
||||
deleteClickHandler();
|
||||
closePopover();
|
||||
}}
|
||||
variant='ghost'
|
||||
color='error'
|
||||
className='p-3 justify-start text-sm font-semibold w-full focus-visible:text-error-content hover:text-error-content'
|
||||
>
|
||||
<Icon icon='mdi:delete-outline' width={20} height={20} />
|
||||
Delete
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</div>
|
||||
@@ -206,12 +224,37 @@ const MovementTable = () => {
|
||||
};
|
||||
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
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 (
|
||||
<RowOptionsMenu
|
||||
props={props}
|
||||
deleteClickHandler={deleteClickHandler}
|
||||
popoverPosition={isLast2Rows ? 'top' : 'bottom'}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
[tableFilterState.pageSize, tableFilterState.page]
|
||||
[
|
||||
tableFilterState.pageSize,
|
||||
tableFilterState.page,
|
||||
singleDeleteModal,
|
||||
setSelectedMovement,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -455,6 +509,21 @@ const MovementTable = () => {
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
|
||||
<ConfirmationModal
|
||||
ref={singleDeleteModal.ref}
|
||||
type='error'
|
||||
text={`Apakah anda yakin ingin menghapus data Movement ini?`}
|
||||
secondaryButton={{
|
||||
text: 'Tidak',
|
||||
}}
|
||||
primaryButton={{
|
||||
text: 'Ya',
|
||||
color: 'error',
|
||||
isLoading: isDeleteLoading,
|
||||
onClick: singleDeleteHandler,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -210,7 +210,7 @@ const RowOptionsMenu = ({
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
)}
|
||||
{!restrictionInfo.isLocked && !isApproved && !isRejected && (
|
||||
{!isApproved && !isRejected && (
|
||||
<RequirePermission permissions='lti.production.recording.approve'>
|
||||
<Button
|
||||
onClick={() => {
|
||||
@@ -226,7 +226,7 @@ const RowOptionsMenu = ({
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
)}
|
||||
{!restrictionInfo.isLocked && !isApproved && !isRejected && (
|
||||
{!isApproved && !isRejected && (
|
||||
<RequirePermission permissions='lti.production.recording.approve'>
|
||||
<Button
|
||||
onClick={() => {
|
||||
@@ -818,6 +818,10 @@ const RecordingTable = () => {
|
||||
props.row.original.project_flock?.project_flock_category ||
|
||||
'GROWING';
|
||||
const color = category === 'LAYING' ? 'info' : 'warning';
|
||||
|
||||
const isGrowingLocked =
|
||||
category === 'GROWING' && props.row.original.is_laying;
|
||||
|
||||
return (
|
||||
<div className='flex flex-col gap-1'>
|
||||
<StatusBadge color={color} text={formatTitleCase(category)} />
|
||||
@@ -826,6 +830,11 @@ const RecordingTable = () => {
|
||||
(Transisi)
|
||||
</span>
|
||||
)}
|
||||
{isGrowingLocked && (
|
||||
<span className='text-xs text-error font-medium'>
|
||||
(Penguncian)
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,7 +11,29 @@ export const getRecordingRestriction = (
|
||||
isTransition: boolean,
|
||||
currentIsLaying?: boolean
|
||||
): RecordingRestriction => {
|
||||
if (currentIsLaying && !isLaying) {
|
||||
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,
|
||||
@@ -22,16 +44,6 @@ export const getRecordingRestriction = (
|
||||
};
|
||||
}
|
||||
|
||||
if (isTransition && !isLaying) {
|
||||
return {
|
||||
canEditStock: true,
|
||||
canEditDepletion: false,
|
||||
canEditEgg: false,
|
||||
isLocked: false,
|
||||
lockReason: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
if (!isLaying && !isTransition) {
|
||||
return {
|
||||
canEditStock: true,
|
||||
|
||||
@@ -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'],
|
||||
|
||||
+11
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user