mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
Merge branch 'development' into 'production'
Development See merge request mbugroup/lti-web-client!373
This commit is contained in:
@@ -45,3 +45,6 @@ next-env.d.ts
|
||||
|
||||
# claude
|
||||
.claude
|
||||
|
||||
# rtk
|
||||
rtk.exe
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
npm run format
|
||||
npm run lint
|
||||
npx tsc --noEmit
|
||||
npm run typecheck
|
||||
|
||||
+2
-1
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -566,23 +566,31 @@ const useSelect = <T,>(
|
||||
setSize(size + 1);
|
||||
};
|
||||
|
||||
let formattedSuccessRawData: SuccessApiResponse<T[]> | 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<T[]> | 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,
|
||||
|
||||
@@ -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 || '-';
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -746,7 +746,7 @@ const MarketingTable = () => {
|
||||
}
|
||||
columns={[
|
||||
{
|
||||
header: 'Kandang',
|
||||
header: 'Gudang Fisik',
|
||||
accessorFn(row) {
|
||||
return row.product_warehouse.warehouse.name;
|
||||
},
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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<typeof DeliveryOrderSchema>;
|
||||
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,
|
||||
|
||||
@@ -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={{
|
||||
|
||||
+19
-7
@@ -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<SalesOrderProductSchemaTy
|
||||
Yup.object({
|
||||
id: Yup.number(),
|
||||
vehicle_number: Yup.string().required('Nomor Kendaraan wajib diisi!'),
|
||||
kandang: Yup.object({
|
||||
warehouse: Yup.object({
|
||||
value: Yup.number()
|
||||
.min(1, 'Kandang wajib diisi!')
|
||||
.required('Kandang wajib diisi!'),
|
||||
label: Yup.string().required('Kandang wajib diisi!'),
|
||||
.min(1, 'Gudang fisik wajib diisi!')
|
||||
.required('Gudang fisik wajib diisi!'),
|
||||
label: Yup.string().required('Gudang fisik wajib diisi!'),
|
||||
}).nullable(),
|
||||
kandang_id: Yup.number()
|
||||
.min(1, 'Kandang wajib diisi!')
|
||||
.required('Kandang wajib diisi!'),
|
||||
warehouse_id: Yup.number()
|
||||
.min(1, 'Gudang fisik wajib diisi!')
|
||||
.required('Gudang fisik wajib diisi!'),
|
||||
kandang: Yup.object({
|
||||
value: Yup.number().min(1).required(),
|
||||
label: Yup.string().required(),
|
||||
})
|
||||
.nullable()
|
||||
.optional(),
|
||||
kandang_id: Yup.number().optional(),
|
||||
product_warehouse: Yup.object({
|
||||
value: Yup.number()
|
||||
.min(1, 'Produk wajib diisi!')
|
||||
|
||||
+88
-36
@@ -7,7 +7,7 @@ import {
|
||||
} from '@/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema';
|
||||
import { RefObject, useEffect, useMemo, useState } from 'react';
|
||||
import { OptionType, useSelect } from '@/components/input/SelectInput';
|
||||
import { Kandang } from '@/types/api/master-data/kandang';
|
||||
import { Warehouse } from '@/types/api/master-data/warehouse';
|
||||
import { WarehouseApi } from '@/services/api/master-data';
|
||||
import { ProductWarehouse } from '@/types/api/inventory/product-warehouse';
|
||||
import { ProductWarehouseApi } from '@/services/api/inventory';
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
import { Icon } from '@iconify/react';
|
||||
import Dropdown from '@/components/Dropdown';
|
||||
import { handleMarketingCalculation } from '@/lib/marketing-calculation';
|
||||
import { getProductWarehouseOptionLabel } from '@/lib/product-warehouse';
|
||||
|
||||
const SalesOrderProductForm = ({
|
||||
initialValues,
|
||||
@@ -84,9 +85,13 @@ const SalesOrderProductForm = ({
|
||||
enableReinitialize: true,
|
||||
initialValues: {
|
||||
vehicle_number: initialValues?.vehicle_number || '',
|
||||
warehouse_id:
|
||||
initialValues?.warehouse_id ?? initialValues?.kandang_id ?? undefined,
|
||||
warehouse: initialValues?.warehouse ?? initialValues?.kandang ?? null,
|
||||
kandang_id: initialValues?.kandang_id || undefined,
|
||||
kandang: initialValues?.kandang || null,
|
||||
product_warehouse: initialValues?.product_warehouse || null,
|
||||
product_warehouse_data: initialValues?.product_warehouse_data || null,
|
||||
product_warehouse_id: initialValues?.product_warehouse_id || undefined,
|
||||
unit_price: initialValues?.unit_price || '',
|
||||
total_weight: initialValues?.total_weight || '',
|
||||
@@ -132,11 +137,11 @@ const SalesOrderProductForm = ({
|
||||
|
||||
// ===== Options =====
|
||||
const {
|
||||
options: kandangSourceOptions,
|
||||
isLoadingOptions: isLoadingKandangSourceOptions,
|
||||
setInputValue: setKandangInputValue,
|
||||
loadMore: loadMoreKandang,
|
||||
} = useSelect<Kandang>(WarehouseApi.basePath, 'id', 'name');
|
||||
options: warehouseOptions,
|
||||
isLoadingOptions: isLoadingWarehouseOptions,
|
||||
setInputValue: setWarehouseSearchValue,
|
||||
loadMore: loadMoreWarehouses,
|
||||
} = useSelect<Warehouse>(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 (
|
||||
<>
|
||||
<form
|
||||
@@ -348,22 +399,22 @@ const SalesOrderProductForm = ({
|
||||
errorMessage={formik.errors.vehicle_number}
|
||||
/>
|
||||
|
||||
{/* Gudang */}
|
||||
{/* Gudang Fisik */}
|
||||
<SelectInputRadio
|
||||
required
|
||||
label='Gudang'
|
||||
options={kandangSourceOptions}
|
||||
isLoading={isLoadingKandangSourceOptions}
|
||||
value={formik.values.kandang}
|
||||
onChange={kandangChangeHandler}
|
||||
label='Gudang Fisik'
|
||||
options={warehouseOptions}
|
||||
isLoading={isLoadingWarehouseOptions}
|
||||
value={formik.values.warehouse}
|
||||
onChange={warehouseChangeHandler}
|
||||
isClearable
|
||||
onInputChange={setKandangInputValue}
|
||||
onMenuScrollToBottom={loadMoreKandang}
|
||||
onInputChange={setWarehouseSearchValue}
|
||||
onMenuScrollToBottom={loadMoreWarehouses}
|
||||
isError={
|
||||
formik.touched.kandang_id && Boolean(formik.errors.kandang_id)
|
||||
formik.touched.warehouse_id && Boolean(formik.errors.warehouse_id)
|
||||
}
|
||||
errorMessage={formik.errors.kandang_id}
|
||||
placeholder='Pilih Gudang'
|
||||
errorMessage={formik.errors.warehouse_id}
|
||||
placeholder='Pilih 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...'
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -104,9 +104,10 @@ const DeliveryOrderProductTable = ({
|
||||
</tr>
|
||||
<>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Gudang</td>
|
||||
<td className='text-sm px-4 py-3'>Gudang Fisik</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{doItem?.warehouse?.name ||
|
||||
item.marketing_product?.warehouse?.label ||
|
||||
item.marketing_product?.product_warehouse_data?.warehouse?.name}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -235,7 +236,7 @@ const DeliveryOrderProductTable = ({
|
||||
</tr>
|
||||
<>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Gudang</td>
|
||||
<td className='text-sm px-4 py-3'>Gudang Fisik</td>
|
||||
<td className='text-sm px-4 py-3'>{item.warehouse?.name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -73,8 +73,10 @@ const SalesOrderProductTable = ({
|
||||
<td className='text-sm px-4 py-3'>{item.vehicle_number}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Gudang</td>
|
||||
<td className='text-sm px-4 py-3'>{item.kandang?.label}</td>
|
||||
<td className='text-sm px-4 py-3'>Gudang Fisik</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{item.warehouse?.label ?? item.kandang?.label}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Kategori</td>
|
||||
|
||||
@@ -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<StockSchema> = 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<DepletionSchema> = 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<CreateGrowingRecordingPayload['depletions']>[0]
|
||||
) => ({
|
||||
product_warehouse_id: depletion.product_warehouse_id,
|
||||
source_product_warehouse_id: depletion.source_product_warehouse_id,
|
||||
qty: depletion.qty,
|
||||
})
|
||||
) ?? [
|
||||
|
||||
@@ -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<StandardDetails>[] = [
|
||||
const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
// ===== HOOKS & ROUTER =====
|
||||
const router = useRouter();
|
||||
const { mutate } = useSWRConfig();
|
||||
|
||||
// ===== STATE MANAGEMENT =====
|
||||
const [selectedRecordDate, setSelectedRecordDate] = useState<string>(
|
||||
@@ -202,15 +207,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
useState<string>('');
|
||||
const [stockProductsLocationId, setStockProductsLocationId] =
|
||||
useState<string>('');
|
||||
const [stockProductsKandangId, setStockProductsKandangId] =
|
||||
useState<string>('');
|
||||
const [depletionProductsLocationId, setDepletionProductsLocationId] =
|
||||
useState<string>('');
|
||||
const [depletionProductsKandangId, setDepletionProductsKandangId] =
|
||||
useState<string>('');
|
||||
const [eggProductsLocationId, setEggProductsLocationId] =
|
||||
useState<string>('');
|
||||
const [eggProductsKandangId, setEggProductsKandangId] = useState<string>('');
|
||||
// const [eggProductsLocationId, setEggProductsLocationId] =
|
||||
// useState<string>('');
|
||||
const [knownProductWarehouses, setKnownProductWarehouses] = useState<
|
||||
Record<number, ProductWarehouse>
|
||||
>({});
|
||||
|
||||
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<number>();
|
||||
const mergeKnownProductWarehouses = useCallback(
|
||||
(items: Array<ProductWarehouse | null | undefined>) => {
|
||||
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<ProductWarehouse | null | undefined> = [];
|
||||
|
||||
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='.'
|
||||
|
||||
@@ -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 = () => {
|
||||
</div>
|
||||
|
||||
{/* ===== MODAL COMPONENTS ===== */}
|
||||
|
||||
<PurchaseFilterModal
|
||||
ref={filterModal.ref}
|
||||
onSubmit={filterSubmitHandler}
|
||||
|
||||
@@ -81,7 +81,7 @@ const getTableColumns = (
|
||||
},
|
||||
{
|
||||
key: 'warehouse',
|
||||
header: 'Gudang',
|
||||
header: 'Gudang Fisik',
|
||||
flex: 1.2,
|
||||
align: 'left',
|
||||
cell: ({ row }) => row.warehouse?.name ?? '-',
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -508,7 +508,7 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
|
||||
},
|
||||
{
|
||||
id: 'warehouse',
|
||||
header: 'Gudang',
|
||||
header: 'Gudang Fisik',
|
||||
accessorKey: 'warehouse',
|
||||
cell: ({ row }) => row.original.warehouse.name,
|
||||
footer: () => <div className='font-semibold text-gray-900'>-</div>,
|
||||
@@ -858,8 +858,8 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
|
||||
|
||||
{/* Warehouse Filter */}
|
||||
<SelectInput
|
||||
label='Gudang'
|
||||
placeholder='Pilih Gudang'
|
||||
label='Gudang Fisik'
|
||||
placeholder='Pilih Gudang Fisik'
|
||||
options={warehouseOptions}
|
||||
isLoading={isLoadingWarehouses}
|
||||
value={warehouseValue}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { ProductWarehouse } from '@/types/api/inventory/product-warehouse';
|
||||
import { Warehouse } from '@/types/api/master-data/warehouse';
|
||||
|
||||
export const getWarehouseScopeLabel = (
|
||||
warehouse?: Warehouse | null
|
||||
): string => {
|
||||
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;
|
||||
};
|
||||
+4
-3
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
+2
@@ -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;
|
||||
}[];
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user