diff --git a/.gitignore b/.gitignore index e47b8ec3..9e1fadc8 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,6 @@ next-env.d.ts # claude .claude + +# rtk +rtk.exe diff --git a/.husky/pre-commit b/.husky/pre-commit index ff51d55a..ac8a41c7 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,3 +1,3 @@ npm run format npm run lint -npx tsc --noEmit \ No newline at end of file +npm run typecheck diff --git a/package.json b/package.json index 34c07ec3..90d941ce 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,10 @@ "build": "next build --turbopack", "start": "next start", "lint": "eslint", + "typecheck": "next typegen && tsc --noEmit", "prepare": "husky", "format": "prettier --write .", - "pre-commit": "npm run format && npm run lint && npx tsc --noEmit && npm run build" + "pre-commit": "npm run format && npm run lint && npm run typecheck && npm run build" }, "dependencies": { "@react-pdf/renderer": "^4.3.1", diff --git a/src/app/production/recording/detail/edit/page.tsx b/src/app/production/recording/detail/edit/page.tsx index ad6c6a9a..aa5f1f46 100644 --- a/src/app/production/recording/detail/edit/page.tsx +++ b/src/app/production/recording/detail/edit/page.tsx @@ -11,10 +11,13 @@ const RecordingEdit = () => { const searchParams = useSearchParams(); const recordingId = searchParams.get('recordingId'); + const recordingDetailKey = recordingId + ? ['recording-detail', recordingId] + : null; const { data: recording, isLoading: isLoadingRecording } = useSWR( - recordingId, - (id: string) => RecordingApi.getSingle(parseInt(id)) + recordingDetailKey, + ([, id]: [string, string]) => RecordingApi.getSingle(parseInt(id)) ); if (!recordingId) { diff --git a/src/app/production/recording/detail/page.tsx b/src/app/production/recording/detail/page.tsx index 194365a3..136c4283 100644 --- a/src/app/production/recording/detail/page.tsx +++ b/src/app/production/recording/detail/page.tsx @@ -11,10 +11,13 @@ const RecordingDetail = () => { const searchParams = useSearchParams(); const recordingId = searchParams.get('recordingId'); + const recordingDetailKey = recordingId + ? ['recording-detail', recordingId] + : null; const { data: recording, isLoading: isLoadingRecording } = useSWR( - recordingId, - (id: string) => RecordingApi.getSingle(parseInt(id)) + recordingDetailKey, + ([, id]: [string, string]) => RecordingApi.getSingle(parseInt(id)) ); if (!recordingId) { diff --git a/src/components/input/SelectInput.tsx b/src/components/input/SelectInput.tsx index 32f8dbcd..c1736fc5 100644 --- a/src/components/input/SelectInput.tsx +++ b/src/components/input/SelectInput.tsx @@ -566,23 +566,31 @@ const useSelect = ( setSize(size + 1); }; - let formattedSuccessRawData: SuccessApiResponse | undefined = undefined; - let formattedErrorRawData: ErrorApiResponse | undefined = undefined; - const latestPagesIndex = pages?.length ? pages.length - 1 : 0; - if (isResponseSuccess(pages?.[latestPagesIndex])) { - formattedSuccessRawData = { - ...pages?.[latestPagesIndex], - data: - pages?.flatMap((page) => (isResponseSuccess(page) ? page.data : [])) ?? - [], - }; - } + const { formattedSuccessRawData, formattedErrorRawData } = useMemo(() => { + let successData: SuccessApiResponse | undefined = undefined; + let errorData: ErrorApiResponse | undefined = undefined; - if (isResponseError(pages?.[latestPagesIndex])) { - formattedErrorRawData = pages?.[latestPagesIndex]; - } + if (isResponseSuccess(pages?.[latestPagesIndex])) { + successData = { + ...pages![latestPagesIndex], + data: + pages?.flatMap((page) => + isResponseSuccess(page) ? page.data : [] + ) ?? [], + }; + } + + if (isResponseError(pages?.[latestPagesIndex])) { + errorData = pages![latestPagesIndex]; + } + + return { + formattedSuccessRawData: successData, + formattedErrorRawData: errorData, + }; + }, [pages, latestPagesIndex]); return { inputValue, diff --git a/src/components/pages/closing/table/SalesClosingTable.tsx b/src/components/pages/closing/table/SalesClosingTable.tsx index e362f1e0..2e3e7fdf 100644 --- a/src/components/pages/closing/table/SalesClosingTable.tsx +++ b/src/components/pages/closing/table/SalesClosingTable.tsx @@ -276,7 +276,7 @@ const SalesClosingTable = ({ projectFlockId }: SalesClosingTableProps) => { { id: 'kandang', accessorKey: 'kandang', - header: 'Kandang', + header: 'Kandang Atribusi', cell: (props) => { const kandang = props.getValue() as Kandang; return kandang?.name || '-'; diff --git a/src/components/pages/closing/table/sapronak/OutgoingSapronaksTable.tsx b/src/components/pages/closing/table/sapronak/OutgoingSapronaksTable.tsx index 23e3e8b0..e31c29a9 100644 --- a/src/components/pages/closing/table/sapronak/OutgoingSapronaksTable.tsx +++ b/src/components/pages/closing/table/sapronak/OutgoingSapronaksTable.tsx @@ -127,11 +127,11 @@ const ClosingOutgoingSapronaksTable = ({ }, { accessorKey: 'source_warehouse', - header: 'Gudang Asal', + header: 'Gudang Asal (Fisik)', }, { accessorKey: 'destination_warehouse', - header: 'Gudang Tujuan', + header: 'Gudang Tujuan (Fisik)', }, { accessorKey: 'quantity', diff --git a/src/components/pages/marketing/MarketingTable.tsx b/src/components/pages/marketing/MarketingTable.tsx index 540a3eca..911c9e9a 100644 --- a/src/components/pages/marketing/MarketingTable.tsx +++ b/src/components/pages/marketing/MarketingTable.tsx @@ -746,7 +746,7 @@ const MarketingTable = () => { } columns={[ { - header: 'Kandang', + header: 'Gudang Fisik', accessorFn(row) { return row.product_warehouse.warehouse.name; }, diff --git a/src/components/pages/marketing/SalesOrderFormModal.tsx b/src/components/pages/marketing/SalesOrderFormModal.tsx index d80b98c5..8fc4a031 100644 --- a/src/components/pages/marketing/SalesOrderFormModal.tsx +++ b/src/components/pages/marketing/SalesOrderFormModal.tsx @@ -207,7 +207,6 @@ const SalesOrderFormModal = ({ return { vehicle_number: product.vehicle_number as string, - kandang_id: product.kandang_id as number, product_warehouse_id: product.product_warehouse_id as number, unit_price: parseFloat(String(product.unit_price || 0)), total_weight: parseFloat(String(product.total_weight || 0)), diff --git a/src/components/pages/marketing/form/MarketingForm.schema.ts b/src/components/pages/marketing/form/MarketingForm.schema.ts index 17b6d78c..144ec6ff 100644 --- a/src/components/pages/marketing/form/MarketingForm.schema.ts +++ b/src/components/pages/marketing/form/MarketingForm.schema.ts @@ -13,6 +13,7 @@ import { Marketing, } from '@/types/api/marketing/marketing'; import { formatDate, formatTitleCase } from '@/lib/helper'; +import { getProductWarehouseOptionLabel } from '@/lib/product-warehouse'; type MarketingSchemaType = { customer_id: number | undefined; @@ -97,17 +98,21 @@ export type DeliveryOrderFormValues = Yup.InferType; export const SalesProductToFieldValues = ( product: BaseSalesOrder ): SalesOrderProductFormValues => { + const warehouseOption = { + value: product.product_warehouse.warehouse.id, + label: product.product_warehouse.warehouse.name, + }; + return { id: product.id, vehicle_number: product.vehicle_number, + warehouse_id: product.product_warehouse.warehouse.id, + warehouse: warehouseOption, kandang_id: product.product_warehouse.warehouse.id, - kandang: { - value: product.product_warehouse.warehouse.id, - label: product.product_warehouse.warehouse.name, - }, + kandang: warehouseOption, product_warehouse: { value: product.product_warehouse.id, - label: product.product_warehouse.product.name, + label: getProductWarehouseOptionLabel(product.product_warehouse), }, product_warehouse_data: product.product_warehouse, product_warehouse_id: product.product_warehouse.id, @@ -142,6 +147,10 @@ export const DeliveryProductToFieldValues = ( const soId = salesOrders.find( (so) => so.product_warehouse.id === item.product_warehouse.id )?.id; + const warehouseOption = { + value: item.product_warehouse.warehouse.id, + label: item.product_warehouse.warehouse.name, + }; return { id: soId, unit_price: item.unit_price, @@ -156,15 +165,15 @@ export const DeliveryProductToFieldValues = ( marketing_product: { id: soId, vehicle_number: item.vehicle_number, + warehouse_id: item.product_warehouse.warehouse.id, + warehouse: warehouseOption, kandang_id: item.product_warehouse.warehouse.id, - kandang: { - value: item.product_warehouse.warehouse.id, - label: item.product_warehouse.warehouse.name, - }, + kandang: warehouseOption, product_warehouse: { value: item.product_warehouse.id, - label: item.product_warehouse.product.name, + label: getProductWarehouseOptionLabel(item.product_warehouse), }, + product_warehouse_data: item.product_warehouse, product_warehouse_id: item.product_warehouse.id, unit_price: item.unit_price, total_weight: item.total_weight, diff --git a/src/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.tsx b/src/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.tsx index c8bae43a..66b50600 100644 --- a/src/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.tsx +++ b/src/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.tsx @@ -112,7 +112,7 @@ const DeliveryOrderProductForm = ({ if (!Boolean(item.qty)) { return { value: item.id, - label: `${item.marketing_product?.product_warehouse?.label} - ${item.marketing_product?.kandang?.label}`, + label: `${item.marketing_product?.product_warehouse?.label} - ${item.marketing_product?.warehouse?.label ?? item.marketing_product?.kandang?.label}`, } as OptionType; } else { return null; @@ -333,7 +333,7 @@ const DeliveryOrderProductForm = ({ if (initialValues?.marketing_product_id) { setSelectedProduct({ value: initialValues?.id, - label: `${initialValues?.marketing_product?.product_warehouse?.label} - ${initialValues?.marketing_product?.kandang?.label}`, + label: `${initialValues?.marketing_product?.product_warehouse?.label} - ${initialValues?.marketing_product?.warehouse?.label ?? initialValues?.marketing_product?.kandang?.label}`, } as OptionType); } } @@ -472,7 +472,11 @@ const DeliveryOrderProductForm = ({ text={ exisitingValues?.find( (item) => item.id === selectedProduct?.value - )?.marketing_product?.kandang?.label ?? '' + )?.marketing_product?.warehouse?.label ?? + exisitingValues?.find( + (item) => item.id === selectedProduct?.value + )?.marketing_product?.kandang?.label ?? + '' } color='success' className={{ diff --git a/src/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema.ts b/src/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema.ts index 390756e7..fcf96941 100644 --- a/src/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema.ts +++ b/src/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema.ts @@ -3,6 +3,11 @@ import * as Yup from 'yup'; type SalesOrderProductSchemaType = { id?: number | undefined; + warehouse_id?: number; + warehouse?: { + value: number; + label: string; + } | null; kandang_id?: number; kandang?: { value: number; @@ -44,15 +49,22 @@ export const SalesOrderProductSchema: Yup.ObjectSchema(WarehouseApi.basePath, 'id', 'name'); + options: warehouseOptions, + isLoadingOptions: isLoadingWarehouseOptions, + setInputValue: setWarehouseSearchValue, + loadMore: loadMoreWarehouses, + } = useSelect(WarehouseApi.basePath, 'id', 'name'); // Options Week dari minggu 1 - 22 // const optionsWeek = useMemo(() => { @@ -147,7 +152,6 @@ const SalesOrderProductForm = ({ // }, []); const { - options: warehouseSourceOptions, rawData: warehouseSourceRawData, isLoadingOptions: isLoadingWarehouseSourceOptions, setInputValue: setWarehouseInputValue, @@ -156,32 +160,69 @@ const SalesOrderProductForm = ({ ProductWarehouseApi.basePath, 'id', 'product.name', - '', + 'search', { - warehouse_id: formik.values.kandang_id?.toString() ?? '', + limit: '100', + available_only: 'true', + warehouse_id: formik.values.warehouse_id?.toString() ?? '', type: formik.values.marketing_type?.value.toLocaleUpperCase() ?? '', } ); const productOptionsFiltered = useMemo(() => { - return warehouseSourceOptions.filter( - (product) => - !exisitingValues - ?.map((item) => item.product_warehouse_id) - .includes(product.value) + if (!isResponseSuccess(warehouseSourceRawData)) { + return initialValues?.product_warehouse + ? [initialValues.product_warehouse] + : []; + } + + const selectedProductIds = new Set( + exisitingValues + ?.filter((item) => item.id !== initialValues?.id) + .map((item) => Number(item.product_warehouse_id)) + .filter((item) => item > 0) ?? [] ); - }, [warehouseSourceOptions, exisitingValues]); + + const options = warehouseSourceRawData.data + .filter((item: ProductWarehouse) => !selectedProductIds.has(item.id)) + .map((item: ProductWarehouse) => ({ + value: item.id, + label: getProductWarehouseOptionLabel(item), + })); + + if ( + initialValues?.product_warehouse && + initialValues?.product_warehouse_id + ) { + const exists = options.find( + (option) => + Number(option.value) === Number(initialValues.product_warehouse_id) + ); + if (!exists) { + options.push(initialValues.product_warehouse); + } + } + + return options; + }, [warehouseSourceRawData, exisitingValues, initialValues]); // ===== Handler ===== - const kandangChangeHandler = (val: OptionType | OptionType[] | null) => { - formik.setFieldValue('kandang', val as OptionType); - formik.setFieldValue('kandang_id', (val as OptionType)?.value); + const warehouseChangeHandler = (val: OptionType | OptionType[] | null) => { + const warehouse = (val as OptionType | null) ?? null; + + formik.setFieldValue('warehouse', warehouse); + formik.setFieldValue('warehouse_id', warehouse?.value); + formik.setFieldValue('kandang', warehouse); + formik.setFieldValue('kandang_id', warehouse?.value); formik.setFieldValue('product_warehouse_id', null); formik.setFieldValue('product_warehouse', null); + formik.setFieldValue('product_warehouse_data', null); formik.setFieldValue('qty', ''); }; - const warehouseChangeHandler = (val: OptionType | OptionType[] | null) => { + const productWarehouseChangeHandler = ( + val: OptionType | OptionType[] | null + ) => { formik.setFieldValue('product_warehouse', val as OptionType); const newId = (val as OptionType)?.value; formik.setFieldValue('product_warehouse_id', newId); @@ -191,6 +232,7 @@ const SalesOrderProductForm = ({ (item: ProductWarehouse) => item.id === newId ); setSelectedProductWarehouse(productWarehouse || null); + formik.setFieldValue('product_warehouse_data', productWarehouse || null); formik.setFieldValue('qty', productWarehouse?.quantity); formik.setFieldValue('uom', productWarehouse?.product?.uom?.name || ''); if ( @@ -204,6 +246,8 @@ const SalesOrderProductForm = ({ } handleBlurField('qty'); } else { + setSelectedProductWarehouse(null); + formik.setFieldValue('product_warehouse_data', null); formik.setFieldValue('qty', ''); formik.setFieldValue('uom', ''); formik.setFieldValue('week', null); @@ -217,9 +261,12 @@ const SalesOrderProductForm = ({ formik.resetForm({ values: { vehicle_number: '', + warehouse_id: undefined, + warehouse: null, kandang_id: undefined, kandang: null, product_warehouse: null, + product_warehouse_data: null, product_warehouse_id: undefined, unit_price: '', total_weight: '', @@ -310,6 +357,10 @@ const SalesOrderProductForm = ({ handleBlurField('week'); }, [formik.values.week]); + useEffect(() => { + setSelectedProductWarehouse(initialValues?.product_warehouse_data || null); + }, [initialValues?.product_warehouse_data]); + return ( <>
- {/* Gudang */} + {/* Gudang Fisik */} {/* Kategori */} @@ -374,8 +425,9 @@ const SalesOrderProductForm = ({ value={formik.values.marketing_type} onChange={(val) => { formik.setFieldValue('marketing_type', val); - warehouseChangeHandler(null); + productWarehouseChangeHandler(null); formik.setFieldValue('product_warehouse', null); + formik.setFieldValue('product_warehouse_data', null); formik.setFieldValue('product_warehouse_id', null); formik.setFieldValue('convertion_unit', null); formik.setFieldValue('weight_per_convertion', null); @@ -392,18 +444,18 @@ const SalesOrderProductForm = ({ options={productOptionsFiltered} isLoading={isLoadingWarehouseSourceOptions} value={formik.values.product_warehouse} - onChange={warehouseChangeHandler} + onChange={productWarehouseChangeHandler} onInputChange={setWarehouseInputValue} onMenuScrollToBottom={loadMoreWarehouse} isClearable placeholder={ - formik.values.kandang_id + formik.values.warehouse_id ? productOptionsFiltered.length == 0 ? 'Tidak ada produk yang tersedia' : 'Pilih produk' - : 'Pilih Kandang Terlebih Dahulu' + : 'Pilih Gudang Fisik Terlebih Dahulu' } - isDisabled={!formik.values.kandang_id} + isDisabled={!formik.values.warehouse_id} isError={ formik.touched.product_warehouse_id && Boolean(formik.errors.product_warehouse_id) @@ -706,7 +758,7 @@ const SalesOrderProductForm = ({ formik.touched.unit_price && Boolean(formik.errors.unit_price) } errorMessage={formik.errors.unit_price} - placeholder='Masukan Harga Satuan' + placeholder='Masukan Harga Satuan...' /> )} diff --git a/src/components/pages/marketing/form/table-view/DeliveryOrderProductTable.tsx b/src/components/pages/marketing/form/table-view/DeliveryOrderProductTable.tsx index 71a6040c..5051d631 100644 --- a/src/components/pages/marketing/form/table-view/DeliveryOrderProductTable.tsx +++ b/src/components/pages/marketing/form/table-view/DeliveryOrderProductTable.tsx @@ -104,9 +104,10 @@ const DeliveryOrderProductTable = ({ <> - Gudang + Gudang Fisik {doItem?.warehouse?.name || + item.marketing_product?.warehouse?.label || item.marketing_product?.product_warehouse_data?.warehouse?.name} @@ -235,7 +236,7 @@ const DeliveryOrderProductTable = ({ <> - Gudang + Gudang Fisik {item.warehouse?.name} diff --git a/src/components/pages/marketing/form/table-view/SalesOrderProductTable.tsx b/src/components/pages/marketing/form/table-view/SalesOrderProductTable.tsx index 70282648..f40f9151 100644 --- a/src/components/pages/marketing/form/table-view/SalesOrderProductTable.tsx +++ b/src/components/pages/marketing/form/table-view/SalesOrderProductTable.tsx @@ -73,8 +73,10 @@ const SalesOrderProductTable = ({ {item.vehicle_number} - Gudang - {item.kandang?.label} + Gudang Fisik + + {item.warehouse?.label ?? item.kandang?.label} + Kategori diff --git a/src/components/pages/production/recording/form/RecordingForm.schema.ts b/src/components/pages/production/recording/form/RecordingForm.schema.ts index b39f94ca..e4cf7a49 100644 --- a/src/components/pages/production/recording/form/RecordingForm.schema.ts +++ b/src/components/pages/production/recording/form/RecordingForm.schema.ts @@ -34,6 +34,7 @@ type RecordingGrowingFormSchemaType = { }[]; depletions: { product_warehouse_id?: number; + source_product_warehouse_id?: number; qty?: number | string; }[]; }; @@ -53,6 +54,7 @@ export type StockSchema = { export type DepletionSchema = { product_warehouse_id?: number; + source_product_warehouse_id?: number; qty?: number | string; }; @@ -69,7 +71,7 @@ const StockObjectSchema: Yup.ObjectSchema = Yup.object({ .typeError('Produk harus berupa angka!'), qty: Yup.number() .required('Jumlah penggunaan wajib diisi!') - .min(1, 'Jumlah penggunaan tidak boleh 0!') + .moreThan(0, 'Jumlah penggunaan harus lebih dari 0!') .typeError('Jumlah penggunaan harus berupa angka!'), }); @@ -77,6 +79,9 @@ const DepletionObjectSchema: Yup.ObjectSchema = Yup.object({ product_warehouse_id: Yup.number() .optional() .typeError('Depletions harus berupa angka!'), + source_product_warehouse_id: Yup.number() + .optional() + .typeError('Gudang sumber harus berupa angka!'), qty: Yup.number() .optional() .typeError('Jumlah depletions harus berupa angka!'), @@ -259,6 +264,7 @@ export const getRecordingGrowingFormInitialValues = ( depletion: NonNullable[0] ) => ({ product_warehouse_id: depletion.product_warehouse_id, + source_product_warehouse_id: depletion.source_product_warehouse_id, qty: depletion.qty, }) ) ?? [ diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 0d62fd0b..2a044874 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -4,7 +4,7 @@ import { useMemo, useState, useEffect, useCallback } from 'react'; import { useRouter } from 'next/navigation'; import { useFormik } from 'formik'; -import useSWR from 'swr'; +import useSWR, { useSWRConfig } from 'swr'; import { Icon } from '@iconify/react'; import Button from '@/components/Button'; @@ -71,6 +71,10 @@ import { import { isResponseSuccess, isResponseError } from '@/lib/api-helper'; import { formatDate, formatNumber, cn } from '@/lib/helper'; +import { + getProductWarehouseOptionLabel, + isProductWarehouseSelectableForKandang, +} from '@/lib/product-warehouse'; import toast from 'react-hot-toast'; import ApprovalSteps, { useApprovalSteps, @@ -179,6 +183,7 @@ const productionStandardColumns: ColumnDef[] = [ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { // ===== HOOKS & ROUTER ===== const router = useRouter(); + const { mutate } = useSWRConfig(); // ===== STATE MANAGEMENT ===== const [selectedRecordDate, setSelectedRecordDate] = useState( @@ -202,15 +207,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { useState(''); const [stockProductsLocationId, setStockProductsLocationId] = useState(''); - const [stockProductsKandangId, setStockProductsKandangId] = - useState(''); const [depletionProductsLocationId, setDepletionProductsLocationId] = useState(''); - const [depletionProductsKandangId, setDepletionProductsKandangId] = - useState(''); - const [eggProductsLocationId, setEggProductsLocationId] = - useState(''); - const [eggProductsKandangId, setEggProductsKandangId] = useState(''); + // const [eggProductsLocationId, setEggProductsLocationId] = + // useState(''); + const [knownProductWarehouses, setKnownProductWarehouses] = useState< + Record + >({}); const [isApproveLoading, setIsApproveLoading] = useState(false); const [isRejectLoading, setIsRejectLoading] = useState(false); @@ -317,11 +320,12 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { setRecordingFormErrorMessage(res.message); return; } + await mutate(['recording-detail', recordingId.toString()]); toast.success(res?.message as string); router.refresh(); router.push('/production/recording'); }, - [router] + [mutate, router] ); const deleteRecordingClickHandler = useCallback(() => { @@ -448,22 +452,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ? projectFlockKandangDetailData.data : undefined; - const selectedProjectFlockKandangId = useMemo(() => { - if (type === 'add') { - return projectFlockKandangLookup?.project_flock_kandang_id ?? null; + const selectedKandangId = useMemo(() => { + if (!selectedKandang?.value) { + return null; } - return ( - projectFlockKandangDetail?.id ?? - initialValues?.project_flock?.project_flock_kandang_id ?? - null - ); - }, [ - type, - projectFlockKandangLookup, - projectFlockKandangDetail, - initialValues, - ]); + return Number(selectedKandang.value); + }, [selectedKandang]); // ===== TRANSITION RESTRICTION LOGIC ===== const isTransitionPeriod = useMemo(() => { @@ -512,6 +507,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ?.filter((d) => d.product_warehouse_id && d.qty) .map((depletion) => ({ product_warehouse_id: depletion.product_warehouse_id!, + ...(depletion.source_product_warehouse_id && { + source_product_warehouse_id: + depletion.source_product_warehouse_id, + }), qty: Number(depletion.qty) || 0, })) : []; @@ -541,6 +540,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ?.filter((d) => d.product_warehouse_id && d.qty) .map((depletion) => ({ product_warehouse_id: depletion.product_warehouse_id!, + ...(depletion.source_product_warehouse_id && { + source_product_warehouse_id: depletion.source_product_warehouse_id, + }), qty: Number(depletion.qty) || 0, })); @@ -602,24 +604,27 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { }, []); const { - options: stockProductOptions, setInputValue: setStockProductInputValue, rawData: stockProducts, isLoadingOptions: isLoadingStockProducts, loadMore: loadMoreStockProducts, } = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', 'search', { flags: 'PAKAN,OVK', + limit: '100', + available_only: 'true', location_id: stockProductsLocationId, - kandang_id: stockProductsKandangId, + ...(selectedKandangId ? { kandang_id: selectedKandangId.toString() } : {}), }); const { rawData: depletionProductsData, isLoadingOptions: isLoadingDepletionProducts, loadMore: loadMoreDepletionProducts, - } = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', '', { + } = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', 'search', { + limit: '100', + available_only: 'false', location_id: depletionProductsLocationId, - kandang_id: depletionProductsKandangId, + ...(selectedKandangId ? { kandang_id: selectedKandangId.toString() } : {}), type: 'AYAM', }); @@ -684,9 +689,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { isLoadingOptions: isLoadingEggProducts, loadMore: loadMoreEggProducts, } = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', 'search', { + limit: '100', + available_only: 'false', type: 'TELUR', - location_id: eggProductsLocationId, - kandang_id: eggProductsKandangId, + // location_id: eggProductsLocationId, + ...(selectedKandangId ? { kandang_id: selectedKandangId.toString() } : {}), }); const approvedProjectFlockKandangsUrl = useMemo(() => { @@ -934,39 +941,134 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { projectFlockKandangDetail, ]); - const isProductWarehouseBelongsToSelectedProjectFlockKandang = useCallback( - (productWarehouse: ProductWarehouse) => { - if (!selectedProjectFlockKandangId) return false; + const appendProductWarehouseOption = useCallback( + (options: OptionType[], productWarehouse?: ProductWarehouse | null) => { + if (!productWarehouse) { + return; + } - return ( - productWarehouse.project_flock_kandang?.id === - selectedProjectFlockKandangId + const existingOption = options.find( + (opt) => Number(opt.value) === productWarehouse.id ); + + if (!existingOption) { + options.push({ + value: productWarehouse.id, + label: getProductWarehouseOptionLabel(productWarehouse), + }); + } }, - [selectedProjectFlockKandangId] + [] ); - const scopedStockProductIds = useMemo(() => { - if (!isResponseSuccess(stockProducts) || !selectedProjectFlockKandangId) { - return new Set(); + const mergeKnownProductWarehouses = useCallback( + (items: Array) => { + if (items.length === 0) { + return; + } + + setKnownProductWarehouses((prev) => { + const next = { ...prev }; + let changed = false; + + items.forEach((item) => { + if (!item?.id) { + return; + } + + const existing = next[item.id]; + if (existing !== item) { + // Check deep equality to avoid triggering state changes + // when identical data comes from different sources (e.g. initialValues vs SWR) + if ( + !existing || + JSON.stringify(existing) !== JSON.stringify(item) + ) { + next[item.id] = item; + changed = true; + } + } + }); + + return changed ? next : prev; + }); + }, + [] + ); + + useEffect(() => { + const items: Array = []; + + if (isResponseSuccess(stockProducts)) { + items.push( + ...((stockProducts.data as unknown as ProductWarehouse[]) ?? []) + ); } - const data = stockProducts.data as unknown as ProductWarehouse[]; - return new Set( - data - .filter(isProductWarehouseBelongsToSelectedProjectFlockKandang) - .map((product) => product.id) - ); + if (isResponseSuccess(depletionProductsData)) { + items.push( + ...((depletionProductsData.data as unknown as ProductWarehouse[]) ?? []) + ); + } + + if (isResponseSuccess(eggProductsData)) { + items.push( + ...((eggProductsData.data as unknown as ProductWarehouse[]) ?? []) + ); + } + + initialValues?.stocks?.forEach((stock) => { + items.push( + (stock.product_warehouse as ProductWarehouse | undefined) ?? null + ); + }); + initialValues?.depletions?.forEach((depletion) => { + items.push( + (depletion.product_warehouse as ProductWarehouse | undefined) ?? null + ); + }); + initialValues?.eggs?.forEach((egg) => { + items.push( + (egg.product_warehouse as ProductWarehouse | undefined) ?? null + ); + }); + + mergeKnownProductWarehouses(items); }, [ stockProducts, - selectedProjectFlockKandangId, - isProductWarehouseBelongsToSelectedProjectFlockKandang, + depletionProductsData, + eggProductsData, + initialValues, + mergeKnownProductWarehouses, ]); + const getKnownProductWarehouse = useCallback( + (productWarehouseId: number) => + knownProductWarehouses[productWarehouseId] ?? null, + [knownProductWarehouses] + ); + + const buildProductWarehouseOptions = useCallback( + (productWarehouses: ProductWarehouse[]) => + productWarehouses + .filter((productWarehouse) => + isProductWarehouseSelectableForKandang( + productWarehouse, + selectedKandangId + ) + ) + .map((productWarehouse) => ({ + value: productWarehouse.id, + label: getProductWarehouseOptionLabel(productWarehouse), + })) + .sort((a, b) => a.label.localeCompare(b.label)), + [selectedKandangId] + ); + const unifiedStockProducts = useMemo(() => { - const options = selectedProjectFlockKandangId - ? stockProductOptions.filter((option) => - scopedStockProductIds.has(Number(option.value)) + const options = isResponseSuccess(stockProducts) + ? buildProductWarehouseOptions( + stockProducts.data as unknown as ProductWarehouse[] ) : []; @@ -977,113 +1079,61 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { type !== 'add' ) { initialValues.stocks?.forEach((stock) => { - if (stock.product_warehouse && stock.product_warehouse.product) { - const existingOption = options.find( - (opt) => opt.value === stock.product_warehouse_id - ); - if (!existingOption) { - options.push({ - value: stock.product_warehouse_id, - label: stock.product_warehouse.product.name, - }); - } - } + appendProductWarehouseOption(options, stock.product_warehouse); }); } return options; }, [ - stockProductOptions, + stockProducts, + buildProductWarehouseOptions, initialValues, type, - selectedProjectFlockKandangId, - scopedStockProductIds, + appendProductWarehouseOption, ]); const depletionProducts = useMemo(() => { - const options: OptionType[] = []; - - if ( - isResponseSuccess(depletionProductsData) && - selectedProjectFlockKandangId - ) { - const data = depletionProductsData.data as unknown as ProductWarehouse[]; - data - .filter(isProductWarehouseBelongsToSelectedProjectFlockKandang) - .forEach((product) => { - options.push({ - value: product.id, - label: product.product.name, - }); - }); - } + const options = isResponseSuccess(depletionProductsData) + ? buildProductWarehouseOptions( + depletionProductsData.data as unknown as ProductWarehouse[] + ) + : []; if (initialValues && initialValues.depletions && type !== 'add') { initialValues.depletions.forEach((depletion) => { - if ( - depletion.product_warehouse && - depletion.product_warehouse.product - ) { - const existingOption = options.find( - (opt) => opt.value === depletion.product_warehouse_id - ); - if (!existingOption) { - options.push({ - value: depletion.product_warehouse_id, - label: depletion.product_warehouse.product.name, - }); - } - } + appendProductWarehouseOption(options, depletion.product_warehouse); }); } return options; }, [ depletionProductsData, + buildProductWarehouseOptions, initialValues, type, - selectedProjectFlockKandangId, - isProductWarehouseBelongsToSelectedProjectFlockKandang, + appendProductWarehouseOption, ]); const eggProducts = useMemo(() => { - const options: OptionType[] = []; - - if (isResponseSuccess(eggProductsData) && selectedProjectFlockKandangId) { - const data = eggProductsData.data as unknown as ProductWarehouse[]; - data - .filter(isProductWarehouseBelongsToSelectedProjectFlockKandang) - .forEach((product) => { - options.push({ - value: product.id, - label: product.product.name, - }); - }); - } + const options = isResponseSuccess(eggProductsData) + ? buildProductWarehouseOptions( + eggProductsData.data as unknown as ProductWarehouse[] + ) + : []; if (initialValues && initialValues.eggs && type !== 'add') { initialValues.eggs.forEach((egg) => { - if (egg.product_warehouse && egg.product_warehouse.product) { - const existingOption = options.find( - (opt) => opt.value === egg.product_warehouse_id - ); - if (!existingOption) { - options.push({ - value: egg.product_warehouse_id, - label: egg.product_warehouse.product.name, - }); - } - } + appendProductWarehouseOption(options, egg.product_warehouse); }); } return options; }, [ eggProductsData, + buildProductWarehouseOptions, initialValues, type, - selectedProjectFlockKandangId, - isProductWarehouseBelongsToSelectedProjectFlockKandang, + appendProductWarehouseOption, ]); // ===== FORMIK SETUP ===== @@ -1291,12 +1341,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const getAvailableStock = useCallback( (productWarehouseId: number) => { if ((type as 'add' | 'edit' | 'detail') === 'detail') return 0; - if (!isResponseSuccess(stockProducts)) return 0; - const data = stockProducts.data as unknown as ProductWarehouse[]; - const productWarehouse = data.find((pw) => pw.id === productWarehouseId); + const productWarehouse = getKnownProductWarehouse(productWarehouseId); return productWarehouse?.quantity ?? 0; }, - [stockProducts, type] + [getKnownProductWarehouse, type] ); const getStockUsageError = useCallback( @@ -1390,10 +1438,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const getProductFlagBadgeAdornment = useCallback( (productWarehouseId: number) => { - if (!isResponseSuccess(stockProducts)) return null; - - const data = stockProducts.data as unknown as ProductWarehouse[]; - const productWarehouse = data.find((pw) => pw.id === productWarehouseId); + const productWarehouse = getKnownProductWarehouse(productWarehouseId); if (!productWarehouse) return null; const hasPakanFlag = productWarehouse.product.flags?.includes('PAKAN'); @@ -1409,7 +1454,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { return null; }, - [stockProducts] + [getKnownProductWarehouse] ); const getProductUomSuffix = useCallback( @@ -1434,23 +1479,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { } } - let rawData; - if (dataSource === 'stock') { - rawData = stockProducts; - } else if (dataSource === 'depletion') { - rawData = depletionProductsData; - } else if (dataSource === 'egg') { - rawData = eggProductsData; - } - - if (!isResponseSuccess(rawData)) return null; - - const data = rawData.data as unknown as ProductWarehouse[]; - const productWarehouse = data.find((pw) => pw.id === productWarehouseId); + const productWarehouse = getKnownProductWarehouse(productWarehouseId); return productWarehouse?.product.uom.name || null; }, - [stockProducts, depletionProductsData, eggProductsData, initialValues, type] + [getKnownProductWarehouse, initialValues, type] ); const getAvailableStockProductOptions = useCallback( @@ -1566,10 +1599,52 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { formik.setFieldValue('location_id', locationId); setSelectedLocation(location); + formik.setFieldTouched('project_flock', false, false); + formik.setFieldValue('project_flock', null); + formik.setFieldTouched('project_flock_id', false, false); + formik.setFieldValue('project_flock_id', 0); + formik.setFieldTouched('kandang', false, false); + formik.setFieldValue('kandang', null); + formik.setFieldTouched('kandang_id', false, false); + formik.setFieldValue('kandang_id', 0); + formik.setFieldTouched('project_flock_kandang', false, false); + formik.setFieldValue('project_flock_kandang', null); + formik.setFieldTouched('project_flock_kandang_id', false, false); + formik.setFieldValue('project_flock_kandang_id', 0); + formik.setFieldTouched('stocks', false, false); + formik.setFieldValue('stocks', [ + { + product_warehouse_id: 0, + qty: '', + }, + ]); + formik.setFieldTouched('depletions', false, false); + formik.setFieldValue('depletions', [ + { + product_warehouse_id: 0, + qty: '', + }, + ]); + if (isLayingCategory) { + formik.setFieldTouched('eggs', false, false); + formik.setFieldValue('eggs', [ + { + product_warehouse_id: 0, + qty: '', + weight: '', + }, + ]); + } + setSelectedStocks([]); + setSelectedDepletions([]); + setSelectedEggs([]); setSelectedProjectFlock(null); setSelectedKandang(null); setProductionStandards(null); setNextDayRecording(null); + setStockProductsLocationId(''); + setDepletionProductsLocationId(''); + // setEggProductsLocationId(''); if (duplicateErrorShown) { toast.dismiss(); setDuplicateErrorShown(false); @@ -1592,10 +1667,48 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { formik.setFieldTouched('project_flock_id', true); formik.setFieldValue('project_flock_id', projectFlockId); + formik.setFieldTouched('kandang', false, false); + formik.setFieldValue('kandang', null); + formik.setFieldTouched('kandang_id', false, false); + formik.setFieldValue('kandang_id', 0); + formik.setFieldTouched('project_flock_kandang', false, false); + formik.setFieldValue('project_flock_kandang', null); + formik.setFieldTouched('project_flock_kandang_id', false, false); + formik.setFieldValue('project_flock_kandang_id', 0); + formik.setFieldTouched('stocks', false, false); + formik.setFieldValue('stocks', [ + { + product_warehouse_id: 0, + qty: '', + }, + ]); + formik.setFieldTouched('depletions', false, false); + formik.setFieldValue('depletions', [ + { + product_warehouse_id: 0, + qty: '', + }, + ]); + if (isLayingCategory) { + formik.setFieldTouched('eggs', false, false); + formik.setFieldValue('eggs', [ + { + product_warehouse_id: 0, + qty: '', + weight: '', + }, + ]); + } + setSelectedStocks([]); + setSelectedDepletions([]); + setSelectedEggs([]); setSelectedProjectFlock(projectFlock); setSelectedKandang(null); setProductionStandards(null); setNextDayRecording(null); + setStockProductsLocationId(''); + setDepletionProductsLocationId(''); + // setEggProductsLocationId(''); if (duplicateErrorShown) { toast.dismiss(); setDuplicateErrorShown(false); @@ -1615,6 +1728,33 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { formik.setFieldTouched('kandang_id', true); formik.setFieldValue('kandang_id', kandangId); + formik.setFieldTouched('stocks', false, false); + formik.setFieldValue('stocks', [ + { + product_warehouse_id: 0, + qty: '', + }, + ]); + formik.setFieldTouched('depletions', false, false); + formik.setFieldValue('depletions', [ + { + product_warehouse_id: 0, + qty: '', + }, + ]); + if (isLayingCategory) { + formik.setFieldTouched('eggs', false, false); + formik.setFieldValue('eggs', [ + { + product_warehouse_id: 0, + qty: '', + weight: '', + }, + ]); + } + setSelectedStocks([]); + setSelectedDepletions([]); + setSelectedEggs([]); setSelectedKandang(kandang); setProductionStandards(null); setNextDayRecording(null); @@ -1628,18 +1768,12 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { } if (selectedLocation && kandang) { setStockProductsLocationId(selectedLocation.value.toString()); - setStockProductsKandangId(kandang.value.toString()); setDepletionProductsLocationId(selectedLocation.value.toString()); - setDepletionProductsKandangId(kandang.value.toString()); - setEggProductsLocationId(selectedLocation.value.toString()); - setEggProductsKandangId(kandang.value.toString()); + // setEggProductsLocationId(selectedLocation.value.toString()); } else { setStockProductsLocationId(''); - setStockProductsKandangId(''); setDepletionProductsLocationId(''); - setDepletionProductsKandangId(''); - setEggProductsLocationId(''); - setEggProductsKandangId(''); + // setEggProductsLocationId(''); } formik.setFieldTouched('project_flock_kandang', true); formik.setFieldTouched('project_flock_kandang_id', true); @@ -1746,11 +1880,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { setSelectedKandang(kandangOption); setStockProductsLocationId(location.id.toString()); - setStockProductsKandangId(kandang.id.toString()); setDepletionProductsLocationId(location.id.toString()); - setDepletionProductsKandangId(kandang.id.toString()); - setEggProductsLocationId(location.id.toString()); - setEggProductsKandangId(kandang.id.toString()); + // setEggProductsLocationId(location.id.toString()); if ( formik.values.project_flock_kandang_id !== @@ -2724,7 +2855,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { value={stock.qty ?? ''} onChange={handleStockUsageQtyChangeWrapper(idx)} onBlur={formik.handleBlur} - decimalScale={0} + decimalScale={3} allowNegative={false} thousandSeparator=',' decimalSeparator='.' diff --git a/src/components/pages/purchase/PurchaseTable.tsx b/src/components/pages/purchase/PurchaseTable.tsx index d1ae638c..d074a583 100644 --- a/src/components/pages/purchase/PurchaseTable.tsx +++ b/src/components/pages/purchase/PurchaseTable.tsx @@ -478,6 +478,7 @@ const PurchaseTable = () => { 'filter_by', 'sort_by', ]} + fieldGroups={[['startDate', 'endDate']]} onClick={filterModal.openModal} className='px-3 py-2.5' /> @@ -539,6 +540,7 @@ const PurchaseTable = () => { {/* ===== MODAL COMPONENTS ===== */} + row.warehouse?.name ?? '-', diff --git a/src/components/pages/report/marketing/export/DailyMarketingExportXLSX.tsx b/src/components/pages/report/marketing/export/DailyMarketingExportXLSX.tsx index d43213f1..14c81bec 100644 --- a/src/components/pages/report/marketing/export/DailyMarketingExportXLSX.tsx +++ b/src/components/pages/report/marketing/export/DailyMarketingExportXLSX.tsx @@ -30,7 +30,7 @@ export const generateDailyMarketingExcel = async ( { header: 'Tanggal Jual', key: 'soDate', width: 15 }, { header: 'Tanggal Realisasi', key: 'realizationDate', width: 18 }, { header: 'Aging', key: 'aging', width: 10 }, - { header: 'Gudang', key: 'warehouse', width: 25 }, + { header: 'Gudang Fisik', key: 'warehouse', width: 25 }, { header: 'Pelanggan', key: 'customer', width: 25 }, { header: 'No. DO', key: 'doNumber', width: 15 }, { header: 'Sales/Marketing', key: 'sales', width: 20 }, @@ -97,7 +97,7 @@ export const generateDailyMarketingExcel = async ( }); } - worksheet.columns.forEach((column) => { + worksheet.columns.forEach((column: { width?: number }) => { if (column.width && column.width < 10) { column.width = 10; } diff --git a/src/components/pages/report/marketing/tab/DailyMarketingTab.tsx b/src/components/pages/report/marketing/tab/DailyMarketingTab.tsx index 49bb798e..1d9dc750 100644 --- a/src/components/pages/report/marketing/tab/DailyMarketingTab.tsx +++ b/src/components/pages/report/marketing/tab/DailyMarketingTab.tsx @@ -508,7 +508,7 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => { }, { id: 'warehouse', - header: 'Gudang', + header: 'Gudang Fisik', accessorKey: 'warehouse', cell: ({ row }) => row.original.warehouse.name, footer: () =>
-
, @@ -858,8 +858,8 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => { {/* Warehouse Filter */} { + if (!warehouse) { + return 'Gudang'; + } + + if (warehouse.type === 'KANDANG') { + return warehouse.kandang?.name + ? `Kandang ${warehouse.kandang.name}` + : 'Gudang Kandang'; + } + + if (warehouse.type === 'LOKASI') { + return 'Gudang Farm'; + } + + return 'Gudang Area'; +}; + +export const getProductWarehouseOptionLabel = ( + productWarehouse?: ProductWarehouse | null +): string => { + if (!productWarehouse) { + return ''; + } + + const productName = productWarehouse.product?.name || 'Produk'; + const warehouseName = productWarehouse.warehouse?.name || 'Gudang'; + const warehouseScope = getWarehouseScopeLabel(productWarehouse.warehouse); + + return `${productName} • ${warehouseName} (${warehouseScope})`; +}; + +export const isProductWarehouseSelectableForKandang = ( + productWarehouse: ProductWarehouse, + kandangId?: number | null +): boolean => { + const warehouse = productWarehouse.warehouse; + + if (!warehouse) { + return false; + } + + if (warehouse.type === 'LOKASI') { + return true; + } + + if (warehouse.type === 'KANDANG') { + return ( + Boolean(kandangId) && + (warehouse.kandang?.id === kandangId || + productWarehouse.project_flock_kandang?.kandang_id === kandangId) + ); + } + + return false; +}; diff --git a/src/types/api/marketing/marketing.d.ts b/src/types/api/marketing/marketing.d.ts index a867d983..743493f7 100644 --- a/src/types/api/marketing/marketing.d.ts +++ b/src/types/api/marketing/marketing.d.ts @@ -5,7 +5,6 @@ import { CreatedUser, } from '@/types/api/api-general'; import { ProductWarehouse } from '@/types/api/inventory/product-warehouse'; -import { Kandang } from '@/types/api/master-data/kandang'; import { Warehouse } from '@/types/api/master-data/warehouse'; /** @@ -110,7 +109,8 @@ export type BaseCreateMarketingPayload = { export type BaseCreateMarketingProductPayload = { vehicle_number: string; - kandang_id: string | number | undefined; + warehouse_id?: string | number | undefined; + kandang_id?: string | number | undefined; product_warehouse_id: string | number | undefined; unit_price: string | number | undefined; total_weight: string | number | undefined; @@ -136,7 +136,8 @@ export type CreateSalesOrderPayload = BaseCreateMarketingPayload & { export type CreateSalesOrderProductPayload = BaseCreateMarketingProductPayload & { id?: number; - kandang?: Kandang | undefined; + warehouse?: Warehouse | undefined; + kandang?: Warehouse | undefined; product_warehouse?: ProductWarehouse | undefined; }; diff --git a/src/types/api/production/recording.d.ts b/src/types/api/production/recording.d.ts index 8ce0ef15..04392ae4 100644 --- a/src/types/api/production/recording.d.ts +++ b/src/types/api/production/recording.d.ts @@ -55,6 +55,7 @@ export type BaseRecording = { export type RecordingDepletion = { product_warehouse_id: number; + source_product_warehouse_id?: number; qty: number; product_warehouse: ProductWarehouse; }; @@ -114,6 +115,7 @@ export type CreateGrowingRecordingPayload = { }[]; depletions?: { product_warehouse_id?: number; + source_product_warehouse_id?: number; qty?: number; }[]; };