codex: initiated changes

This commit is contained in:
Adnan Zahir
2026-04-01 10:14:05 +07:00
parent 7bee13124d
commit 8d92da75cf
20 changed files with 287 additions and 204 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
npm run format npm run format
npm run lint npm run lint
npx tsc --noEmit npm run typecheck
+2 -1
View File
@@ -7,9 +7,10 @@
"build": "next build --turbopack", "build": "next build --turbopack",
"start": "next start", "start": "next start",
"lint": "eslint", "lint": "eslint",
"typecheck": "next typegen && tsc --noEmit",
"prepare": "husky", "prepare": "husky",
"format": "prettier --write .", "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": { "dependencies": {
"@react-pdf/renderer": "^4.3.1", "@react-pdf/renderer": "^4.3.1",
@@ -276,7 +276,7 @@ const SalesClosingTable = ({ projectFlockId }: SalesClosingTableProps) => {
{ {
id: 'kandang', id: 'kandang',
accessorKey: 'kandang', accessorKey: 'kandang',
header: 'Kandang', header: 'Kandang Atribusi',
cell: (props) => { cell: (props) => {
const kandang = props.getValue() as Kandang; const kandang = props.getValue() as Kandang;
return kandang?.name || '-'; return kandang?.name || '-';
@@ -127,11 +127,11 @@ const ClosingOutgoingSapronaksTable = ({
}, },
{ {
accessorKey: 'source_warehouse', accessorKey: 'source_warehouse',
header: 'Gudang Asal', header: 'Gudang Asal (Fisik)',
}, },
{ {
accessorKey: 'destination_warehouse', accessorKey: 'destination_warehouse',
header: 'Gudang Tujuan', header: 'Gudang Tujuan (Fisik)',
}, },
{ {
accessorKey: 'quantity', accessorKey: 'quantity',
@@ -746,7 +746,7 @@ const MarketingTable = () => {
} }
columns={[ columns={[
{ {
header: 'Kandang', header: 'Gudang Fisik',
accessorFn(row) { accessorFn(row) {
return row.product_warehouse.warehouse.name; return row.product_warehouse.warehouse.name;
}, },
@@ -207,7 +207,6 @@ const SalesOrderFormModal = ({
return { return {
vehicle_number: product.vehicle_number as string, vehicle_number: product.vehicle_number as string,
kandang_id: product.kandang_id as number,
product_warehouse_id: product.product_warehouse_id as number, product_warehouse_id: product.product_warehouse_id as number,
unit_price: parseFloat(String(product.unit_price || 0)), unit_price: parseFloat(String(product.unit_price || 0)),
total_weight: parseFloat(String(product.total_weight || 0)), total_weight: parseFloat(String(product.total_weight || 0)),
@@ -13,6 +13,7 @@ import {
Marketing, Marketing,
} from '@/types/api/marketing/marketing'; } from '@/types/api/marketing/marketing';
import { formatDate, formatTitleCase } from '@/lib/helper'; import { formatDate, formatTitleCase } from '@/lib/helper';
import { getProductWarehouseOptionLabel } from '@/lib/product-warehouse';
type MarketingSchemaType = { type MarketingSchemaType = {
customer_id: number | undefined; customer_id: number | undefined;
@@ -97,17 +98,21 @@ export type DeliveryOrderFormValues = Yup.InferType<typeof DeliveryOrderSchema>;
export const SalesProductToFieldValues = ( export const SalesProductToFieldValues = (
product: BaseSalesOrder product: BaseSalesOrder
): SalesOrderProductFormValues => { ): SalesOrderProductFormValues => {
const warehouseOption = {
value: product.product_warehouse.warehouse.id,
label: product.product_warehouse.warehouse.name,
};
return { return {
id: product.id, id: product.id,
vehicle_number: product.vehicle_number, vehicle_number: product.vehicle_number,
warehouse_id: product.product_warehouse.warehouse.id,
warehouse: warehouseOption,
kandang_id: product.product_warehouse.warehouse.id, kandang_id: product.product_warehouse.warehouse.id,
kandang: { kandang: warehouseOption,
value: product.product_warehouse.warehouse.id,
label: product.product_warehouse.warehouse.name,
},
product_warehouse: { product_warehouse: {
value: product.product_warehouse.id, value: product.product_warehouse.id,
label: product.product_warehouse.product.name, label: getProductWarehouseOptionLabel(product.product_warehouse),
}, },
product_warehouse_data: product.product_warehouse, product_warehouse_data: product.product_warehouse,
product_warehouse_id: product.product_warehouse.id, product_warehouse_id: product.product_warehouse.id,
@@ -142,6 +147,10 @@ export const DeliveryProductToFieldValues = (
const soId = salesOrders.find( const soId = salesOrders.find(
(so) => so.product_warehouse.id === item.product_warehouse.id (so) => so.product_warehouse.id === item.product_warehouse.id
)?.id; )?.id;
const warehouseOption = {
value: item.product_warehouse.warehouse.id,
label: item.product_warehouse.warehouse.name,
};
return { return {
id: soId, id: soId,
unit_price: item.unit_price, unit_price: item.unit_price,
@@ -156,15 +165,15 @@ export const DeliveryProductToFieldValues = (
marketing_product: { marketing_product: {
id: soId, id: soId,
vehicle_number: item.vehicle_number, vehicle_number: item.vehicle_number,
warehouse_id: item.product_warehouse.warehouse.id,
warehouse: warehouseOption,
kandang_id: item.product_warehouse.warehouse.id, kandang_id: item.product_warehouse.warehouse.id,
kandang: { kandang: warehouseOption,
value: item.product_warehouse.warehouse.id,
label: item.product_warehouse.warehouse.name,
},
product_warehouse: { product_warehouse: {
value: item.product_warehouse.id, 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, product_warehouse_id: item.product_warehouse.id,
unit_price: item.unit_price, unit_price: item.unit_price,
total_weight: item.total_weight, total_weight: item.total_weight,
@@ -112,7 +112,7 @@ const DeliveryOrderProductForm = ({
if (!Boolean(item.qty)) { if (!Boolean(item.qty)) {
return { return {
value: item.id, 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; } as OptionType;
} else { } else {
return null; return null;
@@ -333,7 +333,7 @@ const DeliveryOrderProductForm = ({
if (initialValues?.marketing_product_id) { if (initialValues?.marketing_product_id) {
setSelectedProduct({ setSelectedProduct({
value: initialValues?.id, 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); } as OptionType);
} }
} }
@@ -472,7 +472,11 @@ const DeliveryOrderProductForm = ({
text={ text={
exisitingValues?.find( exisitingValues?.find(
(item) => item.id === selectedProduct?.value (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' color='success'
className={{ className={{
@@ -3,6 +3,11 @@ import * as Yup from 'yup';
type SalesOrderProductSchemaType = { type SalesOrderProductSchemaType = {
id?: number | undefined; id?: number | undefined;
warehouse_id?: number;
warehouse?: {
value: number;
label: string;
} | null;
kandang_id?: number; kandang_id?: number;
kandang?: { kandang?: {
value: number; value: number;
@@ -44,15 +49,22 @@ export const SalesOrderProductSchema: Yup.ObjectSchema<SalesOrderProductSchemaTy
Yup.object({ Yup.object({
id: Yup.number(), id: Yup.number(),
vehicle_number: Yup.string().required('Nomor Kendaraan wajib diisi!'), vehicle_number: Yup.string().required('Nomor Kendaraan wajib diisi!'),
kandang: Yup.object({ warehouse: Yup.object({
value: Yup.number() value: Yup.number()
.min(1, 'Kandang wajib diisi!') .min(1, 'Gudang fisik wajib diisi!')
.required('Kandang wajib diisi!'), .required('Gudang fisik wajib diisi!'),
label: Yup.string().required('Kandang wajib diisi!'), label: Yup.string().required('Gudang fisik wajib diisi!'),
}).nullable(), }).nullable(),
kandang_id: Yup.number() warehouse_id: Yup.number()
.min(1, 'Kandang wajib diisi!') .min(1, 'Gudang fisik wajib diisi!')
.required('Kandang 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({ product_warehouse: Yup.object({
value: Yup.number() value: Yup.number()
.min(1, 'Produk wajib diisi!') .min(1, 'Produk wajib diisi!')
@@ -7,7 +7,7 @@ import {
} from '@/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema'; } from '@/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema';
import { RefObject, useEffect, useMemo, useState } from 'react'; import { RefObject, useEffect, useMemo, useState } from 'react';
import { OptionType, useSelect } from '@/components/input/SelectInput'; 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 { WarehouseApi } from '@/services/api/master-data';
import { ProductWarehouse } from '@/types/api/inventory/product-warehouse'; import { ProductWarehouse } from '@/types/api/inventory/product-warehouse';
import { ProductWarehouseApi } from '@/services/api/inventory'; import { ProductWarehouseApi } from '@/services/api/inventory';
@@ -31,6 +31,7 @@ import {
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import Dropdown from '@/components/Dropdown'; import Dropdown from '@/components/Dropdown';
import { handleMarketingCalculation } from '@/lib/marketing-calculation'; import { handleMarketingCalculation } from '@/lib/marketing-calculation';
import { getProductWarehouseOptionLabel } from '@/lib/product-warehouse';
const SalesOrderProductForm = ({ const SalesOrderProductForm = ({
initialValues, initialValues,
@@ -84,9 +85,13 @@ const SalesOrderProductForm = ({
enableReinitialize: true, enableReinitialize: true,
initialValues: { initialValues: {
vehicle_number: initialValues?.vehicle_number || '', 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_id: initialValues?.kandang_id || undefined,
kandang: initialValues?.kandang || null, kandang: initialValues?.kandang || null,
product_warehouse: initialValues?.product_warehouse || null, product_warehouse: initialValues?.product_warehouse || null,
product_warehouse_data: initialValues?.product_warehouse_data || null,
product_warehouse_id: initialValues?.product_warehouse_id || undefined, product_warehouse_id: initialValues?.product_warehouse_id || undefined,
unit_price: initialValues?.unit_price || '', unit_price: initialValues?.unit_price || '',
total_weight: initialValues?.total_weight || '', total_weight: initialValues?.total_weight || '',
@@ -132,11 +137,11 @@ const SalesOrderProductForm = ({
// ===== Options ===== // ===== Options =====
const { const {
options: kandangSourceOptions, options: warehouseOptions,
isLoadingOptions: isLoadingKandangSourceOptions, isLoadingOptions: isLoadingWarehouseOptions,
setInputValue: setKandangInputValue, setInputValue: setWarehouseSearchValue,
loadMore: loadMoreKandang, loadMore: loadMoreWarehouses,
} = useSelect<Kandang>(WarehouseApi.basePath, 'id', 'name'); } = useSelect<Warehouse>(WarehouseApi.basePath, 'id', 'name');
// Options Week dari minggu 1 - 22 // Options Week dari minggu 1 - 22
// const optionsWeek = useMemo(() => { // const optionsWeek = useMemo(() => {
@@ -147,7 +152,6 @@ const SalesOrderProductForm = ({
// }, []); // }, []);
const { const {
options: warehouseSourceOptions,
rawData: warehouseSourceRawData, rawData: warehouseSourceRawData,
isLoadingOptions: isLoadingWarehouseSourceOptions, isLoadingOptions: isLoadingWarehouseSourceOptions,
setInputValue: setWarehouseInputValue, setInputValue: setWarehouseInputValue,
@@ -158,30 +162,65 @@ const SalesOrderProductForm = ({
'product.name', 'product.name',
'', '',
{ {
warehouse_id: formik.values.kandang_id?.toString() ?? '', warehouse_id: formik.values.warehouse_id?.toString() ?? '',
type: formik.values.marketing_type?.value.toLocaleUpperCase() ?? '', type: formik.values.marketing_type?.value.toLocaleUpperCase() ?? '',
} }
); );
const productOptionsFiltered = useMemo(() => { const productOptionsFiltered = useMemo(() => {
return warehouseSourceOptions.filter( if (!isResponseSuccess(warehouseSourceRawData)) {
(product) => return initialValues?.product_warehouse
!exisitingValues ? [initialValues.product_warehouse]
?.map((item) => item.product_warehouse_id) : [];
.includes(product.value) }
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 ===== // ===== Handler =====
const kandangChangeHandler = (val: OptionType | OptionType[] | null) => { const warehouseChangeHandler = (val: OptionType | OptionType[] | null) => {
formik.setFieldValue('kandang', val as OptionType); const warehouse = (val as OptionType | null) ?? null;
formik.setFieldValue('kandang_id', (val as OptionType)?.value);
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_id', null);
formik.setFieldValue('product_warehouse', null); formik.setFieldValue('product_warehouse', null);
formik.setFieldValue('product_warehouse_data', null);
formik.setFieldValue('qty', ''); formik.setFieldValue('qty', '');
}; };
const warehouseChangeHandler = (val: OptionType | OptionType[] | null) => { const productWarehouseChangeHandler = (
val: OptionType | OptionType[] | null
) => {
formik.setFieldValue('product_warehouse', val as OptionType); formik.setFieldValue('product_warehouse', val as OptionType);
const newId = (val as OptionType)?.value; const newId = (val as OptionType)?.value;
formik.setFieldValue('product_warehouse_id', newId); formik.setFieldValue('product_warehouse_id', newId);
@@ -191,6 +230,7 @@ const SalesOrderProductForm = ({
(item: ProductWarehouse) => item.id === newId (item: ProductWarehouse) => item.id === newId
); );
setSelectedProductWarehouse(productWarehouse || null); setSelectedProductWarehouse(productWarehouse || null);
formik.setFieldValue('product_warehouse_data', productWarehouse || null);
formik.setFieldValue('qty', productWarehouse?.quantity); formik.setFieldValue('qty', productWarehouse?.quantity);
formik.setFieldValue('uom', productWarehouse?.product?.uom?.name || ''); formik.setFieldValue('uom', productWarehouse?.product?.uom?.name || '');
if ( if (
@@ -204,6 +244,8 @@ const SalesOrderProductForm = ({
} }
handleBlurField('qty'); handleBlurField('qty');
} else { } else {
setSelectedProductWarehouse(null);
formik.setFieldValue('product_warehouse_data', null);
formik.setFieldValue('qty', ''); formik.setFieldValue('qty', '');
formik.setFieldValue('uom', ''); formik.setFieldValue('uom', '');
formik.setFieldValue('week', null); formik.setFieldValue('week', null);
@@ -217,9 +259,12 @@ const SalesOrderProductForm = ({
formik.resetForm({ formik.resetForm({
values: { values: {
vehicle_number: '', vehicle_number: '',
warehouse_id: undefined,
warehouse: null,
kandang_id: undefined, kandang_id: undefined,
kandang: null, kandang: null,
product_warehouse: null, product_warehouse: null,
product_warehouse_data: null,
product_warehouse_id: undefined, product_warehouse_id: undefined,
unit_price: '', unit_price: '',
total_weight: '', total_weight: '',
@@ -310,6 +355,10 @@ const SalesOrderProductForm = ({
handleBlurField('week'); handleBlurField('week');
}, [formik.values.week]); }, [formik.values.week]);
useEffect(() => {
setSelectedProductWarehouse(initialValues?.product_warehouse_data || null);
}, [initialValues?.product_warehouse_data]);
return ( return (
<> <>
<form <form
@@ -348,22 +397,22 @@ const SalesOrderProductForm = ({
errorMessage={formik.errors.vehicle_number} errorMessage={formik.errors.vehicle_number}
/> />
{/* Gudang */} {/* Gudang Fisik */}
<SelectInputRadio <SelectInputRadio
required required
label='Gudang' label='Gudang Fisik'
options={kandangSourceOptions} options={warehouseOptions}
isLoading={isLoadingKandangSourceOptions} isLoading={isLoadingWarehouseOptions}
value={formik.values.kandang} value={formik.values.warehouse}
onChange={kandangChangeHandler} onChange={warehouseChangeHandler}
isClearable isClearable
onInputChange={setKandangInputValue} onInputChange={setWarehouseSearchValue}
onMenuScrollToBottom={loadMoreKandang} onMenuScrollToBottom={loadMoreWarehouses}
isError={ isError={
formik.touched.kandang_id && Boolean(formik.errors.kandang_id) formik.touched.warehouse_id && Boolean(formik.errors.warehouse_id)
} }
errorMessage={formik.errors.kandang_id} errorMessage={formik.errors.warehouse_id}
placeholder='Pilih Gudang' placeholder='Pilih Gudang Fisik'
/> />
{/* Kategori */} {/* Kategori */}
@@ -374,8 +423,9 @@ const SalesOrderProductForm = ({
value={formik.values.marketing_type} value={formik.values.marketing_type}
onChange={(val) => { onChange={(val) => {
formik.setFieldValue('marketing_type', val); formik.setFieldValue('marketing_type', val);
warehouseChangeHandler(null); productWarehouseChangeHandler(null);
formik.setFieldValue('product_warehouse', null); formik.setFieldValue('product_warehouse', null);
formik.setFieldValue('product_warehouse_data', null);
formik.setFieldValue('product_warehouse_id', null); formik.setFieldValue('product_warehouse_id', null);
formik.setFieldValue('convertion_unit', null); formik.setFieldValue('convertion_unit', null);
formik.setFieldValue('weight_per_convertion', null); formik.setFieldValue('weight_per_convertion', null);
@@ -392,18 +442,18 @@ const SalesOrderProductForm = ({
options={productOptionsFiltered} options={productOptionsFiltered}
isLoading={isLoadingWarehouseSourceOptions} isLoading={isLoadingWarehouseSourceOptions}
value={formik.values.product_warehouse} value={formik.values.product_warehouse}
onChange={warehouseChangeHandler} onChange={productWarehouseChangeHandler}
onInputChange={setWarehouseInputValue} onInputChange={setWarehouseInputValue}
onMenuScrollToBottom={loadMoreWarehouse} onMenuScrollToBottom={loadMoreWarehouse}
isClearable isClearable
placeholder={ placeholder={
formik.values.kandang_id formik.values.warehouse_id
? productOptionsFiltered.length == 0 ? productOptionsFiltered.length == 0
? 'Tidak ada produk yang tersedia' ? 'Tidak ada produk yang tersedia'
: 'Pilih produk' : 'Pilih produk'
: 'Pilih Kandang Terlebih Dahulu' : 'Pilih Gudang Fisik Terlebih Dahulu'
} }
isDisabled={!formik.values.kandang_id} isDisabled={!formik.values.warehouse_id}
isError={ isError={
formik.touched.product_warehouse_id && formik.touched.product_warehouse_id &&
Boolean(formik.errors.product_warehouse_id) Boolean(formik.errors.product_warehouse_id)
@@ -104,9 +104,10 @@ const DeliveryOrderProductTable = ({
</tr> </tr>
<> <>
<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'> <td className='text-sm px-4 py-3'>
{doItem?.warehouse?.name || {doItem?.warehouse?.name ||
item.marketing_product?.warehouse?.label ||
item.marketing_product?.product_warehouse_data?.warehouse?.name} item.marketing_product?.product_warehouse_data?.warehouse?.name}
</td> </td>
</tr> </tr>
@@ -235,7 +236,7 @@ const DeliveryOrderProductTable = ({
</tr> </tr>
<> <>
<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> <td className='text-sm px-4 py-3'>{item.warehouse?.name}</td>
</tr> </tr>
<tr> <tr>
@@ -73,8 +73,10 @@ const SalesOrderProductTable = ({
<td className='text-sm px-4 py-3'>{item.vehicle_number}</td> <td className='text-sm px-4 py-3'>{item.vehicle_number}</td>
</tr> </tr>
<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.kandang?.label}</td> <td className='text-sm px-4 py-3'>
{item.warehouse?.label ?? item.kandang?.label}
</td>
</tr> </tr>
<tr> <tr>
<td className='text-sm px-4 py-3'>Kategori</td> <td className='text-sm px-4 py-3'>Kategori</td>
@@ -34,6 +34,7 @@ type RecordingGrowingFormSchemaType = {
}[]; }[];
depletions: { depletions: {
product_warehouse_id?: number; product_warehouse_id?: number;
source_product_warehouse_id?: number;
qty?: number | string; qty?: number | string;
}[]; }[];
}; };
@@ -53,6 +54,7 @@ export type StockSchema = {
export type DepletionSchema = { export type DepletionSchema = {
product_warehouse_id?: number; product_warehouse_id?: number;
source_product_warehouse_id?: number;
qty?: number | string; qty?: number | string;
}; };
@@ -77,6 +79,9 @@ const DepletionObjectSchema: Yup.ObjectSchema<DepletionSchema> = Yup.object({
product_warehouse_id: Yup.number() product_warehouse_id: Yup.number()
.optional() .optional()
.typeError('Depletions harus berupa angka!'), .typeError('Depletions harus berupa angka!'),
source_product_warehouse_id: Yup.number()
.optional()
.typeError('Gudang sumber harus berupa angka!'),
qty: Yup.number() qty: Yup.number()
.optional() .optional()
.typeError('Jumlah depletions harus berupa angka!'), .typeError('Jumlah depletions harus berupa angka!'),
@@ -259,6 +264,7 @@ export const getRecordingGrowingFormInitialValues = (
depletion: NonNullable<CreateGrowingRecordingPayload['depletions']>[0] depletion: NonNullable<CreateGrowingRecordingPayload['depletions']>[0]
) => ({ ) => ({
product_warehouse_id: depletion.product_warehouse_id, product_warehouse_id: depletion.product_warehouse_id,
source_product_warehouse_id: depletion.source_product_warehouse_id,
qty: depletion.qty, qty: depletion.qty,
}) })
) ?? [ ) ?? [
@@ -71,6 +71,10 @@ import {
import { isResponseSuccess, isResponseError } from '@/lib/api-helper'; import { isResponseSuccess, isResponseError } from '@/lib/api-helper';
import { formatDate, formatNumber, cn } from '@/lib/helper'; import { formatDate, formatNumber, cn } from '@/lib/helper';
import {
getProductWarehouseOptionLabel,
isProductWarehouseSelectableForKandang,
} from '@/lib/product-warehouse';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import ApprovalSteps, { import ApprovalSteps, {
useApprovalSteps, useApprovalSteps,
@@ -202,15 +206,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
useState<string>(''); useState<string>('');
const [stockProductsLocationId, setStockProductsLocationId] = const [stockProductsLocationId, setStockProductsLocationId] =
useState<string>(''); useState<string>('');
const [stockProductsKandangId, setStockProductsKandangId] =
useState<string>('');
const [depletionProductsLocationId, setDepletionProductsLocationId] = const [depletionProductsLocationId, setDepletionProductsLocationId] =
useState<string>(''); useState<string>('');
const [depletionProductsKandangId, setDepletionProductsKandangId] =
useState<string>('');
const [eggProductsLocationId, setEggProductsLocationId] = const [eggProductsLocationId, setEggProductsLocationId] =
useState<string>(''); useState<string>('');
const [eggProductsKandangId, setEggProductsKandangId] = useState<string>('');
const [isApproveLoading, setIsApproveLoading] = useState(false); const [isApproveLoading, setIsApproveLoading] = useState(false);
const [isRejectLoading, setIsRejectLoading] = useState(false); const [isRejectLoading, setIsRejectLoading] = useState(false);
@@ -448,22 +447,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
? projectFlockKandangDetailData.data ? projectFlockKandangDetailData.data
: undefined; : undefined;
const selectedProjectFlockKandangId = useMemo(() => { const selectedKandangId = useMemo(() => {
if (type === 'add') { if (!selectedKandang?.value) {
return projectFlockKandangLookup?.project_flock_kandang_id ?? null; return null;
} }
return ( return Number(selectedKandang.value);
projectFlockKandangDetail?.id ?? }, [selectedKandang]);
initialValues?.project_flock?.project_flock_kandang_id ??
null
);
}, [
type,
projectFlockKandangLookup,
projectFlockKandangDetail,
initialValues,
]);
// ===== TRANSITION RESTRICTION LOGIC ===== // ===== TRANSITION RESTRICTION LOGIC =====
const isTransitionPeriod = useMemo(() => { const isTransitionPeriod = useMemo(() => {
@@ -512,6 +502,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
?.filter((d) => d.product_warehouse_id && d.qty) ?.filter((d) => d.product_warehouse_id && d.qty)
.map((depletion) => ({ .map((depletion) => ({
product_warehouse_id: depletion.product_warehouse_id!, 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, qty: Number(depletion.qty) || 0,
})) }))
: []; : [];
@@ -541,6 +535,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
?.filter((d) => d.product_warehouse_id && d.qty) ?.filter((d) => d.product_warehouse_id && d.qty)
.map((depletion) => ({ .map((depletion) => ({
product_warehouse_id: depletion.product_warehouse_id!, 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, qty: Number(depletion.qty) || 0,
})); }));
@@ -602,7 +599,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
}, []); }, []);
const { const {
options: stockProductOptions,
setInputValue: setStockProductInputValue, setInputValue: setStockProductInputValue,
rawData: stockProducts, rawData: stockProducts,
isLoadingOptions: isLoadingStockProducts, isLoadingOptions: isLoadingStockProducts,
@@ -610,7 +606,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
} = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', 'search', { } = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', 'search', {
flags: 'PAKAN,OVK', flags: 'PAKAN,OVK',
location_id: stockProductsLocationId, location_id: stockProductsLocationId,
kandang_id: stockProductsKandangId,
}); });
const { const {
@@ -619,7 +614,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
loadMore: loadMoreDepletionProducts, loadMore: loadMoreDepletionProducts,
} = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', '', { } = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', '', {
location_id: depletionProductsLocationId, location_id: depletionProductsLocationId,
kandang_id: depletionProductsKandangId,
type: 'AYAM', type: 'AYAM',
}); });
@@ -686,7 +680,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
} = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', 'search', { } = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', 'search', {
type: 'TELUR', type: 'TELUR',
location_id: eggProductsLocationId, location_id: eggProductsLocationId,
kandang_id: eggProductsKandangId,
}); });
const approvedProjectFlockKandangsUrl = useMemo(() => { const approvedProjectFlockKandangsUrl = useMemo(() => {
@@ -934,39 +927,46 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
projectFlockKandangDetail, projectFlockKandangDetail,
]); ]);
const isProductWarehouseBelongsToSelectedProjectFlockKandang = useCallback( const appendProductWarehouseOption = useCallback(
(productWarehouse: ProductWarehouse) => { (options: OptionType[], productWarehouse?: ProductWarehouse | null) => {
if (!selectedProjectFlockKandangId) return false; if (!productWarehouse) {
return;
}
return ( const existingOption = options.find(
productWarehouse.project_flock_kandang?.id === (opt) => Number(opt.value) === productWarehouse.id
selectedProjectFlockKandangId
); );
if (!existingOption) {
options.push({
value: productWarehouse.id,
label: getProductWarehouseOptionLabel(productWarehouse),
});
}
}, },
[selectedProjectFlockKandangId] []
); );
const scopedStockProductIds = useMemo(() => { const buildProductWarehouseOptions = useCallback(
if (!isResponseSuccess(stockProducts) || !selectedProjectFlockKandangId) { (productWarehouses: ProductWarehouse[]) =>
return new Set<number>(); productWarehouses
} .filter((productWarehouse) =>
isProductWarehouseSelectableForKandang(
const data = stockProducts.data as unknown as ProductWarehouse[]; productWarehouse,
return new Set( selectedKandangId
data )
.filter(isProductWarehouseBelongsToSelectedProjectFlockKandang) )
.map((product) => product.id) .map((productWarehouse) => ({
); value: productWarehouse.id,
}, [ label: getProductWarehouseOptionLabel(productWarehouse),
stockProducts, })),
selectedProjectFlockKandangId, [selectedKandangId]
isProductWarehouseBelongsToSelectedProjectFlockKandang, );
]);
const unifiedStockProducts = useMemo(() => { const unifiedStockProducts = useMemo(() => {
const options = selectedProjectFlockKandangId const options = isResponseSuccess(stockProducts)
? stockProductOptions.filter((option) => ? buildProductWarehouseOptions(
scopedStockProductIds.has(Number(option.value)) stockProducts.data as unknown as ProductWarehouse[]
) )
: []; : [];
@@ -977,113 +977,61 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
type !== 'add' type !== 'add'
) { ) {
initialValues.stocks?.forEach((stock) => { initialValues.stocks?.forEach((stock) => {
if (stock.product_warehouse && stock.product_warehouse.product) { appendProductWarehouseOption(options, stock.product_warehouse);
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,
});
}
}
}); });
} }
return options; return options;
}, [ }, [
stockProductOptions, stockProducts,
buildProductWarehouseOptions,
initialValues, initialValues,
type, type,
selectedProjectFlockKandangId, appendProductWarehouseOption,
scopedStockProductIds,
]); ]);
const depletionProducts = useMemo(() => { const depletionProducts = useMemo(() => {
const options: OptionType[] = []; const options = isResponseSuccess(depletionProductsData)
? buildProductWarehouseOptions(
if ( depletionProductsData.data as unknown as ProductWarehouse[]
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,
});
});
}
if (initialValues && initialValues.depletions && type !== 'add') { if (initialValues && initialValues.depletions && type !== 'add') {
initialValues.depletions.forEach((depletion) => { initialValues.depletions.forEach((depletion) => {
if ( appendProductWarehouseOption(options, depletion.product_warehouse);
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,
});
}
}
}); });
} }
return options; return options;
}, [ }, [
depletionProductsData, depletionProductsData,
buildProductWarehouseOptions,
initialValues, initialValues,
type, type,
selectedProjectFlockKandangId, appendProductWarehouseOption,
isProductWarehouseBelongsToSelectedProjectFlockKandang,
]); ]);
const eggProducts = useMemo(() => { const eggProducts = useMemo(() => {
const options: OptionType[] = []; const options = isResponseSuccess(eggProductsData)
? buildProductWarehouseOptions(
if (isResponseSuccess(eggProductsData) && selectedProjectFlockKandangId) { eggProductsData.data as unknown as ProductWarehouse[]
const data = eggProductsData.data as unknown as ProductWarehouse[]; )
data : [];
.filter(isProductWarehouseBelongsToSelectedProjectFlockKandang)
.forEach((product) => {
options.push({
value: product.id,
label: product.product.name,
});
});
}
if (initialValues && initialValues.eggs && type !== 'add') { if (initialValues && initialValues.eggs && type !== 'add') {
initialValues.eggs.forEach((egg) => { initialValues.eggs.forEach((egg) => {
if (egg.product_warehouse && egg.product_warehouse.product) { appendProductWarehouseOption(options, egg.product_warehouse);
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,
});
}
}
}); });
} }
return options; return options;
}, [ }, [
eggProductsData, eggProductsData,
buildProductWarehouseOptions,
initialValues, initialValues,
type, type,
selectedProjectFlockKandangId, appendProductWarehouseOption,
isProductWarehouseBelongsToSelectedProjectFlockKandang,
]); ]);
// ===== FORMIK SETUP ===== // ===== FORMIK SETUP =====
@@ -1628,18 +1576,12 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
} }
if (selectedLocation && kandang) { if (selectedLocation && kandang) {
setStockProductsLocationId(selectedLocation.value.toString()); setStockProductsLocationId(selectedLocation.value.toString());
setStockProductsKandangId(kandang.value.toString());
setDepletionProductsLocationId(selectedLocation.value.toString()); setDepletionProductsLocationId(selectedLocation.value.toString());
setDepletionProductsKandangId(kandang.value.toString());
setEggProductsLocationId(selectedLocation.value.toString()); setEggProductsLocationId(selectedLocation.value.toString());
setEggProductsKandangId(kandang.value.toString());
} else { } else {
setStockProductsLocationId(''); setStockProductsLocationId('');
setStockProductsKandangId('');
setDepletionProductsLocationId(''); setDepletionProductsLocationId('');
setDepletionProductsKandangId('');
setEggProductsLocationId(''); setEggProductsLocationId('');
setEggProductsKandangId('');
} }
formik.setFieldTouched('project_flock_kandang', true); formik.setFieldTouched('project_flock_kandang', true);
formik.setFieldTouched('project_flock_kandang_id', true); formik.setFieldTouched('project_flock_kandang_id', true);
@@ -1746,11 +1688,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
setSelectedKandang(kandangOption); setSelectedKandang(kandangOption);
setStockProductsLocationId(location.id.toString()); setStockProductsLocationId(location.id.toString());
setStockProductsKandangId(kandang.id.toString());
setDepletionProductsLocationId(location.id.toString()); setDepletionProductsLocationId(location.id.toString());
setDepletionProductsKandangId(kandang.id.toString());
setEggProductsLocationId(location.id.toString()); setEggProductsLocationId(location.id.toString());
setEggProductsKandangId(kandang.id.toString());
if ( if (
formik.values.project_flock_kandang_id !== formik.values.project_flock_kandang_id !==
@@ -81,7 +81,7 @@ const getTableColumns = (
}, },
{ {
key: 'warehouse', key: 'warehouse',
header: 'Gudang', header: 'Gudang Fisik',
flex: 1.2, flex: 1.2,
align: 'left', align: 'left',
cell: ({ row }) => row.warehouse?.name ?? '-', cell: ({ row }) => row.warehouse?.name ?? '-',
@@ -30,7 +30,7 @@ export const generateDailyMarketingExcel = async (
{ header: 'Tanggal Jual', key: 'soDate', width: 15 }, { header: 'Tanggal Jual', key: 'soDate', width: 15 },
{ header: 'Tanggal Realisasi', key: 'realizationDate', width: 18 }, { header: 'Tanggal Realisasi', key: 'realizationDate', width: 18 },
{ header: 'Aging', key: 'aging', width: 10 }, { 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: 'Pelanggan', key: 'customer', width: 25 },
{ header: 'No. DO', key: 'doNumber', width: 15 }, { header: 'No. DO', key: 'doNumber', width: 15 },
{ header: 'Sales/Marketing', key: 'sales', width: 20 }, { 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) { if (column.width && column.width < 10) {
column.width = 10; column.width = 10;
} }
@@ -508,7 +508,7 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
}, },
{ {
id: 'warehouse', id: 'warehouse',
header: 'Gudang', header: 'Gudang Fisik',
accessorKey: 'warehouse', accessorKey: 'warehouse',
cell: ({ row }) => row.original.warehouse.name, cell: ({ row }) => row.original.warehouse.name,
footer: () => <div className='font-semibold text-gray-900'>-</div>, footer: () => <div className='font-semibold text-gray-900'>-</div>,
@@ -858,8 +858,8 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
{/* Warehouse Filter */} {/* Warehouse Filter */}
<SelectInput <SelectInput
label='Gudang' label='Gudang Fisik'
placeholder='Pilih Gudang' placeholder='Pilih Gudang Fisik'
options={warehouseOptions} options={warehouseOptions}
isLoading={isLoadingWarehouses} isLoading={isLoadingWarehouses}
value={warehouseValue} value={warehouseValue}
+57
View File
@@ -0,0 +1,57 @@
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;
}
return false;
};
+4 -3
View File
@@ -5,7 +5,6 @@ import {
CreatedUser, CreatedUser,
} from '@/types/api/api-general'; } from '@/types/api/api-general';
import { ProductWarehouse } from '@/types/api/inventory/product-warehouse'; import { ProductWarehouse } from '@/types/api/inventory/product-warehouse';
import { Kandang } from '@/types/api/master-data/kandang';
import { Warehouse } from '@/types/api/master-data/warehouse'; import { Warehouse } from '@/types/api/master-data/warehouse';
/** /**
@@ -110,7 +109,8 @@ export type BaseCreateMarketingPayload = {
export type BaseCreateMarketingProductPayload = { export type BaseCreateMarketingProductPayload = {
vehicle_number: string; vehicle_number: string;
kandang_id: string | number | undefined; warehouse_id?: string | number | undefined;
kandang_id?: string | number | undefined;
product_warehouse_id: string | number | undefined; product_warehouse_id: string | number | undefined;
unit_price: string | number | undefined; unit_price: string | number | undefined;
total_weight: string | number | undefined; total_weight: string | number | undefined;
@@ -136,7 +136,8 @@ export type CreateSalesOrderPayload = BaseCreateMarketingPayload & {
export type CreateSalesOrderProductPayload = export type CreateSalesOrderProductPayload =
BaseCreateMarketingProductPayload & { BaseCreateMarketingProductPayload & {
id?: number; id?: number;
kandang?: Kandang | undefined; warehouse?: Warehouse | undefined;
kandang?: Warehouse | undefined;
product_warehouse?: ProductWarehouse | undefined; product_warehouse?: ProductWarehouse | undefined;
}; };
+2
View File
@@ -55,6 +55,7 @@ export type BaseRecording = {
export type RecordingDepletion = { export type RecordingDepletion = {
product_warehouse_id: number; product_warehouse_id: number;
source_product_warehouse_id?: number;
qty: number; qty: number;
product_warehouse: ProductWarehouse; product_warehouse: ProductWarehouse;
}; };
@@ -114,6 +115,7 @@ export type CreateGrowingRecordingPayload = {
}[]; }[];
depletions?: { depletions?: {
product_warehouse_id?: number; product_warehouse_id?: number;
source_product_warehouse_id?: number;
qty?: number; qty?: number;
}[]; }[];
}; };