From a5fd97a1751bf546293cbd0af43bd68f96c52e62 Mon Sep 17 00:00:00 2001 From: Adnan Zahir Date: Tue, 28 Apr 2026 13:55:08 +0700 Subject: [PATCH 1/4] fix: show product options from master instead of warehouse of migration mode --- .../recording/form/RecordingForm.tsx | 117 ++++++++++++++---- 1 file changed, 95 insertions(+), 22 deletions(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 5ffa1344..a59952ec 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -31,12 +31,14 @@ import { RecordingApi, ProjectFlockApi, } from '@/services/api/production'; -import { ProductionStandardApi } from '@/services/api/master-data'; +import { ProductionStandardApi, ProductApi } from '@/services/api/master-data'; import { ProductionStandard, StandardDetails, } from '@/types/api/master-data/production-standard'; +import { Product } from '@/types/api/master-data/product'; import { LocationApi } from '@/services/api/master-data'; +import { SystemSettingsApi } from '@/services/api/system-settings'; import { ProductWarehouseApi } from '@/services/api/inventory'; import { ProductWarehouse } from '@/types/api/inventory/product-warehouse'; @@ -499,6 +501,20 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { type, ]); + // ===== MIGRATION MODE ===== + const { data: systemSettingsResponse } = useSWR( + SystemSettingsApi.basePath, + SystemSettingsApi.getAllFetcher + ); + + const isMigrationMode = useMemo(() => { + if (!isResponseSuccess(systemSettingsResponse)) return false; + const setting = systemSettingsResponse.data.find( + (s) => s.key === 'allow_negative_pakan_ovk' + ); + return setting?.value === 'true'; + }, [systemSettingsResponse]); + // ===== PAYLOAD CREATION HELPERS ===== const createGrowingPayload = useCallback( (values: RecordingGrowingFormValues) => { @@ -519,7 +535,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ? (values.stocks ?? []) .filter((s) => s.product_warehouse_id && s.qty) .map((stock) => ({ - product_warehouse_id: stock.product_warehouse_id, + // In migration mode, product_warehouse_id field holds product.id; + // send it as product_id so the backend auto-creates the warehouse entry. + ...(isMigrationMode + ? { product_id: stock.product_warehouse_id } + : { product_warehouse_id: stock.product_warehouse_id }), qty: Number(stock.qty) || 0, })) : []; @@ -531,7 +551,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ...(depletions.length > 0 && { depletions }), }; }, - [recordingRestriction.canEditStock, recordingRestriction.canEditDepletion] + [ + isMigrationMode, + recordingRestriction.canEditStock, + recordingRestriction.canEditDepletion, + ] ); const createLayingPayload = useCallback( @@ -561,7 +585,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ? values.stocks .filter((s) => s.product_warehouse_id && s.qty) .map((stock) => ({ - product_warehouse_id: stock.product_warehouse_id, + ...(isMigrationMode + ? { product_id: stock.product_warehouse_id } + : { product_warehouse_id: stock.product_warehouse_id }), qty: Number(stock.qty) || 0, })) : []; @@ -574,7 +600,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ...(eggs && eggs.length > 0 && { eggs }), }; }, - [recordingRestriction.canEditStock] + [isMigrationMode, recordingRestriction.canEditStock] ); const isRecordingEditable = useCallback((recording?: Recording) => { @@ -603,18 +629,51 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { return true; }, []); + // When migration mode ON: fetch all master PAKAN/OVK products (no warehouse entry needed). + // When migration mode OFF: fetch from product-warehouses as usual. const { setInputValue: setStockProductInputValue, - rawData: stockProducts, - isLoadingOptions: isLoadingStockProducts, - loadMore: loadMoreStockProducts, - } = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', 'search', { - flags: 'PAKAN,OVK', - limit: '100', - available_only: 'false', - location_id: stockProductsLocationId, - ...(selectedKandangId ? { kandang_id: selectedKandangId.toString() } : {}), - }); + rawData: stockProductsPW, + isLoadingOptions: isLoadingStockProductsPW, + loadMore: loadMoreStockProductsPW, + } = useSelect( + isMigrationMode ? null : ProductWarehouseApi.basePath, + 'id', + 'product.name', + 'search', + { + flags: 'PAKAN,OVK', + limit: '100', + available_only: 'false', + location_id: stockProductsLocationId, + ...(selectedKandangId + ? { kandang_id: selectedKandangId.toString() } + : {}), + } + ); + + const { + setInputValue: setStockMasterInputValue, + rawData: stockProductsMaster, + isLoadingOptions: isLoadingStockProductsMaster, + loadMore: loadMoreStockProductsMaster, + } = useSelect( + isMigrationMode ? ProductApi.basePath : null, + 'id', + 'name', + 'search', + { flags: 'PAKAN,OVK', limit: '100' } + ); + + const isLoadingStockProducts = isMigrationMode + ? isLoadingStockProductsMaster + : isLoadingStockProductsPW; + const loadMoreStockProducts = isMigrationMode + ? loadMoreStockProductsMaster + : loadMoreStockProductsPW; + const setStockInputValue = isMigrationMode + ? setStockMasterInputValue + : setStockProductInputValue; const { rawData: depletionProductsData, @@ -999,9 +1058,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { useEffect(() => { const items: Array = []; - if (isResponseSuccess(stockProducts)) { + if (!isMigrationMode && isResponseSuccess(stockProductsPW)) { items.push( - ...((stockProducts.data as unknown as ProductWarehouse[]) ?? []) + ...((stockProductsPW.data as unknown as ProductWarehouse[]) ?? []) ); } @@ -1035,7 +1094,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { mergeKnownProductWarehouses(items); }, [ - stockProducts, + isMigrationMode, + stockProductsPW, depletionProductsData, eggProductsData, initialValues, @@ -1066,9 +1126,20 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ); const unifiedStockProducts = useMemo(() => { - const options = isResponseSuccess(stockProducts) + if (isMigrationMode) { + // In migration mode, show all master PAKAN/OVK products (no warehouse context). + // value = product.id; submission will send product_id to the backend. + const options: OptionType[] = isResponseSuccess(stockProductsMaster) + ? (stockProductsMaster.data as unknown as Product[]) + .map((p) => ({ value: p.id, label: p.name })) + .sort((a, b) => a.label.localeCompare(b.label)) + : []; + return options; + } + + const options = isResponseSuccess(stockProductsPW) ? buildProductWarehouseOptions( - stockProducts.data as unknown as ProductWarehouse[] + stockProductsPW.data as unknown as ProductWarehouse[] ) : []; @@ -1085,7 +1156,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { return options; }, [ - stockProducts, + isMigrationMode, + stockProductsMaster, + stockProductsPW, buildProductWarehouseOptions, initialValues, type, @@ -2797,7 +2870,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { product.value === stock.product_warehouse_id ) || null } - onInputChange={setStockProductInputValue} + onInputChange={setStockInputValue} onChange={(selectedOption) => { const option = selectedOption as OptionType | null; From 1851f0e12f78e59382731ef3d36dc4f2bae3030f Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Tue, 28 Apr 2026 14:34:19 +0700 Subject: [PATCH 2/4] fix: add periode to project flock form values --- .../pages/production/project-flock/ProjectFlockTable.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/pages/production/project-flock/ProjectFlockTable.tsx b/src/components/pages/production/project-flock/ProjectFlockTable.tsx index bce94b08..f74787f1 100644 --- a/src/components/pages/production/project-flock/ProjectFlockTable.tsx +++ b/src/components/pages/production/project-flock/ProjectFlockTable.tsx @@ -590,6 +590,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { price: budget.price, total_price: budget.qty * budget.price, })) || [], + periode: createdProjectFlock.period ?? '-', } as ProjectFlockFormValues; }, [createdProjectFlock]); From e75246ff8d73febb3c58a798fd1a65d69172ced4 Mon Sep 17 00:00:00 2001 From: Adnan Zahir Date: Wed, 29 Apr 2026 11:22:07 +0700 Subject: [PATCH 3/4] fix: recording wont accept ovk --- .../recording/form/RecordingForm.tsx | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index a59952ec..90e705f6 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -1277,6 +1277,18 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { }; } + // In migration mode (edit), the form field `product_warehouse_id` must hold + // the master product ID (what the dropdown options use as `value`), not the + // PW entity ID returned by the API. The API response has the product ID + // nested at stock.product_warehouse.product_id. + if (isMigrationMode && type === 'edit' && initialValues?.stocks?.length) { + baseValues.stocks = initialValues.stocks.map((stock) => ({ + product_warehouse_id: + stock.product_warehouse?.product_id ?? stock.product_warehouse_id, + qty: stock.usage_amount ?? '', + })); + } + if (!recordingRestriction.canEditStock) { baseValues.stocks = []; } @@ -1297,6 +1309,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { selectedKandang, recordingRestriction.canEditStock, recordingRestriction.canEditDepletion, + isMigrationMode, ]); const formik = useFormik< @@ -1408,6 +1421,20 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { }, }); + // When migration mode activates after formik has initialized (SWR timing race), + // push the corrected stock values (product_id, not PW entity id) into the form. + useEffect(() => { + if (type !== 'edit' || !isMigrationMode || !initialValues?.stocks?.length) + return; + const migrationStocks = initialValues.stocks.map((stock) => ({ + product_warehouse_id: + stock.product_warehouse?.product_id ?? stock.product_warehouse_id, + qty: stock.usage_amount ?? '', + })); + formik.setFieldValue('stocks', migrationStocks); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isMigrationMode]); // intentionally shallow — only run when migration mode flips + // ===== HELPER FUNCTIONS ===== const { setFieldValue } = formik; From 631e3959cda9e982fd1a239f1006765c52f2e92f Mon Sep 17 00:00:00 2001 From: Adnan Zahir Date: Wed, 29 Apr 2026 12:15:02 +0700 Subject: [PATCH 4/4] fix: missing useRef --- .../recording/form/RecordingForm.tsx | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 90e705f6..3b584cfe 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useMemo, useState, useEffect, useCallback } from 'react'; +import { useMemo, useState, useEffect, useCallback, useRef } from 'react'; import { useRouter } from 'next/navigation'; import { useFormik } from 'formik'; @@ -1277,10 +1277,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { }; } - // In migration mode (edit), the form field `product_warehouse_id` must hold - // the master product ID (what the dropdown options use as `value`), not the - // PW entity ID returned by the API. The API response has the product ID - // nested at stock.product_warehouse.product_id. + // In migration mode (edit), the dropdown options use product.id as their value, + // but the API returns product_warehouse_id (PW entity ID). Remap so the dropdown + // can match the correct option. The product ID is available on the nested + // product_warehouse object returned by the API. if (isMigrationMode && type === 'edit' && initialValues?.stocks?.length) { baseValues.stocks = initialValues.stocks.map((stock) => ({ product_warehouse_id: @@ -1421,19 +1421,30 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { }, }); - // When migration mode activates after formik has initialized (SWR timing race), - // push the corrected stock values (product_id, not PW entity id) into the form. + // SWR timing fix: formik initializes before system-settings load, so isMigrationMode + // starts false. When it flips true, formikInitialValues recomputes but enableReinitialize + // is false, so formik won't pick it up. Push the corrected stock values once, and only + // once — the ref prevents re-firing if something causes isMigrationMode to re-evaluate. + const migrationEditMappingApplied = useRef(false); useEffect(() => { - if (type !== 'edit' || !isMigrationMode || !initialValues?.stocks?.length) + if ( + type !== 'edit' || + !isMigrationMode || + !initialValues?.stocks?.length || + migrationEditMappingApplied.current + ) return; - const migrationStocks = initialValues.stocks.map((stock) => ({ - product_warehouse_id: - stock.product_warehouse?.product_id ?? stock.product_warehouse_id, - qty: stock.usage_amount ?? '', - })); - formik.setFieldValue('stocks', migrationStocks); + migrationEditMappingApplied.current = true; + formik.setFieldValue( + 'stocks', + initialValues.stocks.map((stock) => ({ + product_warehouse_id: + stock.product_warehouse?.product_id ?? stock.product_warehouse_id, + qty: stock.usage_amount ?? '', + })) + ); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isMigrationMode]); // intentionally shallow — only run when migration mode flips + }, [isMigrationMode]); // ===== HELPER FUNCTIONS ===== const { setFieldValue } = formik;