From 8d92da75cfd858650b06db8a34b71b1144a80936 Mon Sep 17 00:00:00 2001 From: Adnan Zahir Date: Wed, 1 Apr 2026 10:14:05 +0700 Subject: [PATCH] codex: initiated changes --- .husky/pre-commit | 2 +- package.json | 3 +- .../pages/closing/table/SalesClosingTable.tsx | 2 +- .../table/sapronak/OutgoingSapronaksTable.tsx | 4 +- .../pages/marketing/MarketingTable.tsx | 2 +- .../pages/marketing/SalesOrderFormModal.tsx | 1 - .../marketing/form/MarketingForm.schema.ts | 29 ++- .../delivery-order/DeliverOrderProduct.tsx | 10 +- .../sales-order/SalesOrderProduct.schema.ts | 26 ++- .../sales-order/SalesOrderProductForm.tsx | 118 ++++++++--- .../table-view/DeliveryOrderProductTable.tsx | 5 +- .../table-view/SalesOrderProductTable.tsx | 6 +- .../recording/form/RecordingForm.schema.ts | 6 + .../recording/form/RecordingForm.tsx | 199 ++++++------------ .../export/DailyMarketingExportPDF.tsx | 2 +- .../export/DailyMarketingExportXLSX.tsx | 4 +- .../marketing/tab/DailyMarketingTab.tsx | 6 +- src/lib/product-warehouse.ts | 57 +++++ src/types/api/marketing/marketing.d.ts | 7 +- src/types/api/production/recording.d.ts | 2 + 20 files changed, 287 insertions(+), 204 deletions(-) create mode 100644 src/lib/product-warehouse.ts 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/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, @@ -158,30 +162,65 @@ const SalesOrderProductForm = ({ 'product.name', '', { - warehouse_id: formik.values.kandang_id?.toString() ?? '', + 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 +230,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 +244,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 +259,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 +355,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 +423,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 +442,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) 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..2a78ffe0 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; }; @@ -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..4595c05c 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -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, @@ -202,15 +206,10 @@ 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 [isApproveLoading, setIsApproveLoading] = useState(false); const [isRejectLoading, setIsRejectLoading] = useState(false); @@ -448,22 +447,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 +502,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 +535,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,7 +599,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { }, []); const { - options: stockProductOptions, setInputValue: setStockProductInputValue, rawData: stockProducts, isLoadingOptions: isLoadingStockProducts, @@ -610,7 +606,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { } = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', 'search', { flags: 'PAKAN,OVK', location_id: stockProductsLocationId, - kandang_id: stockProductsKandangId, }); const { @@ -619,7 +614,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { loadMore: loadMoreDepletionProducts, } = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', '', { location_id: depletionProductsLocationId, - kandang_id: depletionProductsKandangId, type: 'AYAM', }); @@ -686,7 +680,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { } = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', 'search', { type: 'TELUR', location_id: eggProductsLocationId, - kandang_id: eggProductsKandangId, }); const approvedProjectFlockKandangsUrl = useMemo(() => { @@ -934,39 +927,46 @@ 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 data = stockProducts.data as unknown as ProductWarehouse[]; - return new Set( - data - .filter(isProductWarehouseBelongsToSelectedProjectFlockKandang) - .map((product) => product.id) - ); - }, [ - stockProducts, - selectedProjectFlockKandangId, - isProductWarehouseBelongsToSelectedProjectFlockKandang, - ]); + const buildProductWarehouseOptions = useCallback( + (productWarehouses: ProductWarehouse[]) => + productWarehouses + .filter((productWarehouse) => + isProductWarehouseSelectableForKandang( + productWarehouse, + selectedKandangId + ) + ) + .map((productWarehouse) => ({ + value: productWarehouse.id, + label: getProductWarehouseOptionLabel(productWarehouse), + })), + [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 +977,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 ===== @@ -1628,18 +1576,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()); } else { setStockProductsLocationId(''); - setStockProductsKandangId(''); setDepletionProductsLocationId(''); - setDepletionProductsKandangId(''); setEggProductsLocationId(''); - setEggProductsKandangId(''); } formik.setFieldTouched('project_flock_kandang', true); formik.setFieldTouched('project_flock_kandang_id', true); @@ -1746,11 +1688,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()); if ( formik.values.project_flock_kandang_id !== diff --git a/src/components/pages/report/marketing/export/DailyMarketingExportPDF.tsx b/src/components/pages/report/marketing/export/DailyMarketingExportPDF.tsx index c5e1a3a5..81c527f4 100644 --- a/src/components/pages/report/marketing/export/DailyMarketingExportPDF.tsx +++ b/src/components/pages/report/marketing/export/DailyMarketingExportPDF.tsx @@ -81,7 +81,7 @@ const getTableColumns = ( }, { key: 'warehouse', - header: 'Gudang', + header: 'Gudang Fisik', flex: 1.2, align: 'left', cell: ({ row }) => 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; + } + + 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; }[]; };