feat(FE): adding 4 input scenario marketing type

This commit is contained in:
randy-ar
2026-02-05 05:38:02 +07:00
parent cb22fd1037
commit 43dcbf73ee
7 changed files with 123 additions and 65 deletions
@@ -208,6 +208,7 @@ const SalesOrderFormModal = ({
convertion_unit: normalizedConvertionUnit, convertion_unit: normalizedConvertionUnit,
weight_per_convertion: weight_per_convertion:
product.weight_per_convertion ?? undefined, product.weight_per_convertion ?? undefined,
week: product.week?.value ?? undefined,
} as CreateSalesOrderProductPayload; } as CreateSalesOrderProductPayload;
}), }),
} as CreateSalesOrderPayload) } as CreateSalesOrderPayload)
@@ -115,14 +115,24 @@ export const SalesProductToFieldValues = (
qty: product.qty, qty: product.qty,
avg_weight: product.avg_weight, avg_weight: product.avg_weight,
total_price: product.total_price, total_price: product.total_price,
marketing_type: { marketing_type: product.marketing_type
value: product.marketing_type, ? {
label: formatTitleCase(product.marketing_type), value: product.marketing_type,
}, label: formatTitleCase(product.marketing_type),
convertion_unit: { }
value: product.convertion_unit, : null,
label: formatTitleCase(product.convertion_unit), convertion_unit: product.convertion_unit
}, ? {
value: product.convertion_unit,
label: formatTitleCase(product.convertion_unit),
}
: null,
week: product.week
? {
value: product.week,
label: `Week ${product.week}`,
}
: null,
total_peti: product.total_peti, total_peti: product.total_peti,
weight_per_convertion: product.weight_per_convertion, weight_per_convertion: product.weight_per_convertion,
}; };
@@ -34,6 +34,14 @@ type SalesOrderProductSchemaType = {
price_sisa_berat?: number | null | undefined; price_sisa_berat?: number | null | undefined;
/** Harga per butir telur untuk TELUR + QTY */ /** Harga per butir telur untuk TELUR + QTY */
price_per_qty?: number | null | undefined; price_per_qty?: number | null | undefined;
/** Week untuk ayam pullet */
week?:
| {
value?: number;
label?: string;
}
| null
| undefined;
}; };
export const SalesOrderProductSchema: Yup.ObjectSchema<SalesOrderProductSchemaType> = export const SalesOrderProductSchema: Yup.ObjectSchema<SalesOrderProductSchemaType> =
@@ -88,6 +96,28 @@ export const SalesOrderProductSchema: Yup.ObjectSchema<SalesOrderProductSchemaTy
sisa_berat: Yup.number().nullable().optional().notRequired(), sisa_berat: Yup.number().nullable().optional().notRequired(),
price_sisa_berat: Yup.number().nullable().optional().notRequired(), price_sisa_berat: Yup.number().nullable().optional().notRequired(),
price_per_qty: Yup.number().nullable().optional().notRequired(), price_per_qty: Yup.number().nullable().optional().notRequired(),
week: Yup.object({
value: Yup.number(),
label: Yup.string(),
})
.nullable()
.default(null)
.when('marketing_type', {
is: (marketingType: { value: string } | null | undefined) =>
marketingType?.value?.toLowerCase() === 'ayam_pullet',
then: (schema) =>
schema
.shape({
value: Yup.number().required(
'Week wajib diisi untuk Ayam Pullet!'
),
label: Yup.string().required(
'Week wajib diisi untuk Ayam Pullet!'
),
})
.required('Week wajib diisi untuk Ayam Pullet!'),
otherwise: (schema) => schema.optional().notRequired(),
}),
}); });
export type SalesOrderProductFormValues = Yup.InferType< export type SalesOrderProductFormValues = Yup.InferType<
@@ -70,8 +70,10 @@ const SalesOrderProductForm = ({
: 0; : 0;
const initialPriceSisaBerat = const initialPriceSisaBerat =
Number(initialValues?.total_price) - initialValues?.total_price && initialValues?.total_peti
initialPricePerConvertion * Number(initialValues?.total_peti); ? Number(initialValues.total_price) -
initialPricePerConvertion * Number(initialValues.total_peti)
: 0;
const [hasSisaBerat, setHasSisaBerat] = useState<boolean>( const [hasSisaBerat, setHasSisaBerat] = useState<boolean>(
initialSisaBerat > 0 initialSisaBerat > 0
@@ -103,6 +105,7 @@ const SalesOrderProductForm = ({
price_per_qty: initialValues?.price_per_qty ?? null, price_per_qty: initialValues?.price_per_qty ?? null,
sisa_berat: initialSisaBerat, sisa_berat: initialSisaBerat,
price_sisa_berat: initialPriceSisaBerat, price_sisa_berat: initialPriceSisaBerat,
week: initialValues?.week ?? null,
}, },
validationSchema: SalesOrderProductSchema, validationSchema: SalesOrderProductSchema,
onSubmit: async (values) => { onSubmit: async (values) => {
@@ -122,11 +125,11 @@ const SalesOrderProductForm = ({
loadMore: loadMoreKandang, loadMore: loadMoreKandang,
} = useSelect<Kandang>(WarehouseApi.basePath, 'id', 'name'); } = useSelect<Kandang>(WarehouseApi.basePath, 'id', 'name');
// Options Weeks dari minggu 1 - 22 // Options Week dari minggu 1 - 22
const optionsWeeks = useMemo(() => { const optionsWeek = useMemo(() => {
return Array.from({ length: 22 }, (_, i) => ({ return Array.from({ length: 22 }, (_, i) => ({
value: i + 1, value: i + 1,
label: `Weeks ${i + 1}`, label: `Week ${i + 1}`,
})); }));
}, []); }, []);
@@ -205,6 +208,7 @@ const SalesOrderProductForm = ({
weight_per_convertion: null, weight_per_convertion: null,
price_per_convertion: null, price_per_convertion: null,
uom: '', uom: '',
week: null,
}, },
}); });
}; };
@@ -256,6 +260,10 @@ const SalesOrderProductForm = ({
} }
); );
useEffect(() => {
handleBlurField('week');
}, [formik.values.week]);
return ( return (
<> <>
<form <form
@@ -452,20 +460,24 @@ const SalesOrderProductForm = ({
</div> </div>
)} )}
{/* Konversi Satuan Weeks Pullet */} {/* Konversi Satuan Week Pullet */}
{/* {formik.values.marketing_type?.value.toLowerCase() === {formik.values.marketing_type?.value.toLowerCase() ===
'ayam_pullet' && ( 'ayam_pullet' && (
<SelectInputRadio <SelectInputRadio
required required
label='Minggu' label='Minggu'
options={optionsWeeks} options={optionsWeek}
value={formik.values.weeks || undefined} value={
formik.values.week?.value
? (formik.values.week as { value: number; label: string })
: null
}
onChange={(val) => { onChange={(val) => {
formik.setFieldValue('weeks', val); formik.setFieldValue('week', val);
}} }}
placeholder='Pilih Weeks' placeholder='Pilih Week'
/> />
)} */} )}
{/* Total Peti */} {/* Total Peti */}
{formik.values.convertion_unit?.value.toLowerCase() === 'peti' && ( {formik.values.convertion_unit?.value.toLowerCase() === 'peti' && (
@@ -222,12 +222,21 @@ const SalesOrderProductTable = ({
{item.product_warehouse?.label} {item.product_warehouse?.label}
</td> </td>
</tr> </tr>
<tr> {item.marketing_type?.value.toLowerCase() === 'telur' && (
<td className='text-sm px-4 py-3'>Tipe Konversi</td> <tr>
<td className='text-sm px-4 py-3'> <td className='text-sm px-4 py-3'>Tipe Konversi</td>
{item.convertion_unit?.label} <td className='text-sm px-4 py-3'>
</td> {item.convertion_unit?.label}
</tr> </td>
</tr>
)}
{item.marketing_type?.value.toLowerCase() ===
'ayam_pullet' && (
<tr>
<td className='text-sm px-4 py-3'>Tipe Konversi</td>
<td className='text-sm px-4 py-3'>{item.week?.label}</td>
</tr>
)}
{item.convertion_unit?.value.toLowerCase() === 'peti' && ( {item.convertion_unit?.value.toLowerCase() === 'peti' && (
<tr> <tr>
<td className='text-sm px-4 py-3'>Total Peti</td> <td className='text-sm px-4 py-3'>Total Peti</td>
@@ -236,25 +245,30 @@ const SalesOrderProductTable = ({
</td> </td>
</tr> </tr>
)} )}
<tr> {item.marketing_type?.value.toLowerCase() !== 'trading' && (
<td className='text-sm px-4 py-3'>Total Bobot</td> <>
<td className='text-sm px-4 py-3'> <tr>
{item.total_weight <td className='text-sm px-4 py-3'>Total Bobot</td>
? formatNumber( <td className='text-sm px-4 py-3'>
parseFloat(item.total_weight as string) {item.total_weight
) + ' Kg' ? formatNumber(
: '-'} parseFloat(item.total_weight as string)
</td> ) + ' Kg'
</tr> : '0 Kg'}
<tr> </td>
<td className='text-sm px-4 py-3'>Avg Bobot</td> </tr>
<td className='text-sm px-4 py-3'> <tr>
{item.avg_weight <td className='text-sm px-4 py-3'>Avg Bobot</td>
? formatNumber(parseFloat(item.avg_weight as string)) + <td className='text-sm px-4 py-3'>
' Kg' {item.avg_weight
: '-'} ? formatNumber(
</td> parseFloat(item.avg_weight as string)
</tr> ) + ' Kg'
: '0 Kg'}
</td>
</tr>
</>
)}
<tr> <tr>
<td className='text-sm px-4 py-3'> <td className='text-sm px-4 py-3'>
{item.marketing_type?.value === 'telur' {item.marketing_type?.value === 'telur'
+9 -9
View File
@@ -15,7 +15,7 @@ export type MarketingFormValues = {
total_price?: string | number; total_price?: string | number;
marketing_type?: { value: string; label: string } | null; marketing_type?: { value: string; label: string } | null;
convertion_unit?: { value: string; label: string } | null; convertion_unit?: { value: string; label: string } | null;
weeks?: { value: number; label: string } | null; week?: { value?: number; label?: string } | null;
weight_per_convertion?: number | null; weight_per_convertion?: number | null;
price_per_convertion?: number | null; price_per_convertion?: number | null;
total_peti?: number | null; total_peti?: number | null;
@@ -100,7 +100,7 @@ export const calculateAyamPullet = (
): void => { ): void => {
const { values, setFieldValue } = ctx; const { values, setFieldValue } = ctx;
const unitPrice = Number(values.unit_price || 0); const unitPrice = Number(values.unit_price || 0);
const weeks = Number(values.weeks?.value || 0); const week = Number(values.week?.value || 0);
const qty = Number(values.qty || 0); const qty = Number(values.qty || 0);
const avgWeight = Number(values.avg_weight || 0); const avgWeight = Number(values.avg_weight || 0);
const totalWeight = Number(values.total_weight || 0); const totalWeight = Number(values.total_weight || 0);
@@ -108,11 +108,11 @@ export const calculateAyamPullet = (
switch (field) { switch (field) {
case 'unit_price': case 'unit_price':
case 'weeks': case 'week':
case 'qty': { case 'qty': {
// total_price = unit_price × weeks × qty // total_price = unit_price × week × qty
if (unitPrice > 0 && weeks > 0 && qty > 0) { if (unitPrice > 0 && week > 0 && qty > 0) {
setFieldValue('total_price', roundPrice(unitPrice * weeks * qty)); setFieldValue('total_price', roundPrice(unitPrice * week * qty));
} }
// total_weight = avg_weight × qty // total_weight = avg_weight × qty
if (avgWeight > 0 && qty > 0) { if (avgWeight > 0 && qty > 0) {
@@ -133,9 +133,9 @@ export const calculateAyamPullet = (
break; break;
} }
case 'total_price': { case 'total_price': {
// Reverse: unit_price = total_price / (weeks × qty) // Reverse: unit_price = total_price / (week × qty)
if (totalPrice > 0 && weeks > 0 && qty > 0) { if (totalPrice > 0 && week > 0 && qty > 0) {
setFieldValue('unit_price', roundPrice(totalPrice / (weeks * qty))); setFieldValue('unit_price', roundPrice(totalPrice / (week * qty)));
} }
break; break;
} }
+2 -11
View File
@@ -40,6 +40,8 @@ export type BaseSalesOrder = {
convertion_unit: string; convertion_unit: string;
total_peti: number; total_peti: number;
weight_per_convertion: number; weight_per_convertion: number;
/** Umur minggu untuk AYAM_PULLET */
week?: number;
}; };
export type BaseDeliveryOrder = { export type BaseDeliveryOrder = {
@@ -115,17 +117,6 @@ export type BaseCreateMarketingProductPayload = {
avg_weight: string | number | undefined; avg_weight: string | number | undefined;
total_price: string | number | undefined; total_price: string | number | undefined;
marketing_type: string; marketing_type: string;
/**
* Tipe konversi untuk TELUR
* - "PETI": Penjualan telur dalam satuan peti
* - "KG": Penjualan telur dalam satuan kilogram
*
* Note: Untuk mode "QTY" di FE, tetap kirim "KG" ke BE dengan unit_price yang dinormalisasi
* karena BE tidak support convertion_unit "QTY". Workaround:
* - FE hitung: total_price = qty × price_per_qty
* - FE normalisasi: unit_price = total_price / total_weight
* - BE akan hitung: total_price = total_weight × unit_price (hasil sama)
*/
convertion_unit?: 'PETI' | 'KG'; convertion_unit?: 'PETI' | 'KG';
/** Berat per peti (kg), hanya untuk TELUR + PETI */ /** Berat per peti (kg), hanya untuk TELUR + PETI */
weight_per_convertion?: number; weight_per_convertion?: number;