mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-21 22:05:45 +00:00
feat(FE): scenario egg sale with type conversion qty
This commit is contained in:
@@ -186,15 +186,31 @@ const SalesOrderFormModal = ({
|
||||
date: formatDate(values.so_date as string, 'yyyy-MM-DD'),
|
||||
notes: values.notes as string,
|
||||
marketing_products: values.sales_order.map((product) => {
|
||||
// Workaround untuk TELUR + QTY: kirim "KG" karena BE tidak support "QTY"
|
||||
const convertionUnitValue =
|
||||
product.convertion_unit?.value?.toUpperCase();
|
||||
const normalizedConvertionUnit =
|
||||
product.marketing_type?.value?.toLowerCase() === 'telur'
|
||||
? convertionUnitValue === 'PETI'
|
||||
? 'PETI'
|
||||
: 'KG' // termasuk "QTY" dan "KG"
|
||||
: undefined;
|
||||
|
||||
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(product.unit_price as string),
|
||||
total_weight: parseFloat(product.total_weight as string),
|
||||
qty: parseFloat(product.qty as string),
|
||||
avg_weight: parseFloat(product.avg_weight as string),
|
||||
total_price: parseFloat(product.total_price as string),
|
||||
unit_price: parseFloat(String(product.unit_price || 0)),
|
||||
total_weight: parseFloat(String(product.total_weight || 0)),
|
||||
qty: parseFloat(String(product.qty || 0)),
|
||||
avg_weight: parseFloat(String(product.avg_weight || 0)),
|
||||
total_price: parseFloat(String(product.total_price || 0)),
|
||||
marketing_type:
|
||||
product.marketing_type?.value?.toUpperCase() || '',
|
||||
convertion_unit: normalizedConvertionUnit,
|
||||
weight_per_convertion:
|
||||
product.weight_per_convertion ?? undefined,
|
||||
week: product.weeks?.value ?? undefined,
|
||||
} as CreateSalesOrderProductPayload;
|
||||
}),
|
||||
} as CreateSalesOrderPayload)
|
||||
@@ -375,6 +391,7 @@ const SalesOrderFormModal = ({
|
||||
}
|
||||
|
||||
formik.setFieldValue('sales_order', updatedProducts);
|
||||
console.log(formik.values);
|
||||
nextButtonHandler();
|
||||
},
|
||||
[memoSalesOrder, nextButtonHandler]
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
BaseSalesOrder,
|
||||
Marketing,
|
||||
} from '@/types/api/marketing/marketing';
|
||||
import { formatDate } from '@/lib/helper';
|
||||
import { formatDate, formatTitleCase } from '@/lib/helper';
|
||||
|
||||
type MarketingSchemaType = {
|
||||
customer_id: number | undefined;
|
||||
@@ -115,6 +115,14 @@ const SalesProductToFieldValues = (
|
||||
qty: product.qty,
|
||||
avg_weight: product.avg_weight,
|
||||
total_price: product.total_price,
|
||||
marketing_type: {
|
||||
value: product.marketing_type,
|
||||
label: formatTitleCase(product.marketing_type),
|
||||
},
|
||||
convertion_unit: {
|
||||
value: product.convertion_unit,
|
||||
label: formatTitleCase(product.convertion_unit),
|
||||
},
|
||||
};
|
||||
};
|
||||
const DeliveryProductToFieldValues = (
|
||||
|
||||
@@ -9,7 +9,7 @@ import SelectInput, {
|
||||
useSelect,
|
||||
} from '@/components/input/SelectInput';
|
||||
import Modal, { useModal } from '@/components/Modal';
|
||||
import { formatCurrency, formatDate } from '@/lib/helper';
|
||||
import { formatCurrency, formatDate, formatTitleCase } from '@/lib/helper';
|
||||
import {
|
||||
BaseDeliveryOrder,
|
||||
BaseSalesOrder,
|
||||
|
||||
+7
-1
@@ -32,6 +32,8 @@ type SalesOrderProductSchemaType = {
|
||||
total_peti?: number | null | undefined;
|
||||
sisa_berat?: number | null | undefined;
|
||||
price_sisa_berat?: number | null | undefined;
|
||||
/** Harga per butir telur untuk TELUR + QTY */
|
||||
price_per_qty?: number | null | undefined;
|
||||
weeks?: {
|
||||
value: number;
|
||||
label: string;
|
||||
@@ -89,10 +91,14 @@ export const SalesOrderProductSchema: Yup.ObjectSchema<SalesOrderProductSchemaTy
|
||||
total_peti: Yup.number().nullable().optional().notRequired(),
|
||||
sisa_berat: Yup.number().nullable().optional().notRequired(),
|
||||
price_sisa_berat: Yup.number().nullable().optional().notRequired(),
|
||||
price_per_qty: Yup.number().nullable().optional().notRequired(),
|
||||
weeks: Yup.object({
|
||||
value: Yup.number().required('Minggu wajib diisi!'),
|
||||
label: Yup.string().required('Minggu wajib diisi!'),
|
||||
}).nullable(),
|
||||
})
|
||||
.nullable()
|
||||
.optional()
|
||||
.notRequired(),
|
||||
});
|
||||
|
||||
export type SalesOrderProductFormValues = Yup.InferType<
|
||||
|
||||
+32
-112
@@ -30,9 +30,7 @@ import {
|
||||
} from '@/config/constant';
|
||||
import { Icon } from '@iconify/react';
|
||||
import Dropdown from '@/components/Dropdown';
|
||||
|
||||
const roundWeight = (value: number) => Number(value.toFixed(2));
|
||||
const roundPrice = (value: number) => Math.round(value);
|
||||
import { handleMarketingCalculation } from '@/lib/marketing-calculation';
|
||||
|
||||
const SalesOrderProductForm = ({
|
||||
initialValues,
|
||||
@@ -75,6 +73,8 @@ const SalesOrderProductForm = ({
|
||||
convertion_unit: initialValues?.convertion_unit || null,
|
||||
marketing_type: initialValues?.marketing_type || null,
|
||||
total_peti: initialValues?.total_peti ?? null,
|
||||
weeks: initialValues?.weeks || null,
|
||||
price_per_qty: initialValues?.price_per_qty ?? null,
|
||||
},
|
||||
validationSchema: SalesOrderProductSchema,
|
||||
onSubmit: async (values) => {
|
||||
@@ -175,113 +175,11 @@ const SalesOrderProductForm = ({
|
||||
const handleBlurField = (field: string) => {
|
||||
setCurrentInput(field);
|
||||
|
||||
const qty = Number(formik.values.qty || 0);
|
||||
const avgWeight = Number(formik.values.avg_weight || 0);
|
||||
const totalWeight = Number(formik.values.total_weight || 0);
|
||||
const unitPrice = Number(formik.values.unit_price || 0);
|
||||
const totalPrice = Number(formik.values.total_price || 0);
|
||||
|
||||
if (qty <= 0) return;
|
||||
|
||||
// Cek apakah produk memiliki flag OVK atau PAKAN
|
||||
const productFlags = selectedProductWarehouse?.product?.flags || [];
|
||||
const isOvkOrPakan =
|
||||
productFlags.includes('OVK') || productFlags.includes('PAKAN');
|
||||
|
||||
switch (field) {
|
||||
// ===== SOURCE FIELDS =====
|
||||
case 'qty': {
|
||||
if (avgWeight > 0) {
|
||||
const tw = roundWeight(qty * avgWeight);
|
||||
formik.setFieldValue('total_weight', tw);
|
||||
|
||||
// Hitung total_price berdasarkan flag produk
|
||||
if (unitPrice > 0) {
|
||||
if (isOvkOrPakan) {
|
||||
// Untuk OVK/PAKAN: total_price = qty × unit_price
|
||||
formik.setFieldValue('total_price', roundPrice(qty * unitPrice));
|
||||
} else {
|
||||
// Untuk produk lain: total_price = unit_price × total_weight
|
||||
formik.setFieldValue('total_price', roundPrice(unitPrice * tw));
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'avg_weight': {
|
||||
if (avgWeight > 0) {
|
||||
const tw = roundWeight(qty * avgWeight);
|
||||
formik.setFieldValue('total_weight', tw);
|
||||
|
||||
// Hitung total_price berdasarkan flag produk
|
||||
if (unitPrice > 0) {
|
||||
if (isOvkOrPakan) {
|
||||
// Untuk OVK/PAKAN: total_price = qty × unit_price
|
||||
formik.setFieldValue('total_price', roundPrice(qty * unitPrice));
|
||||
} else {
|
||||
// Untuk produk lain: total_price = unit_price × total_weight
|
||||
formik.setFieldValue('total_price', roundPrice(unitPrice * tw));
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'unit_price': {
|
||||
if (unitPrice > 0) {
|
||||
if (isOvkOrPakan) {
|
||||
// Untuk OVK/PAKAN: total_price = qty × unit_price
|
||||
formik.setFieldValue('total_price', roundPrice(qty * unitPrice));
|
||||
} else if (totalWeight > 0) {
|
||||
// Untuk produk lain: total_price = unit_price × total_weight
|
||||
formik.setFieldValue(
|
||||
'total_price',
|
||||
roundPrice(unitPrice * totalWeight)
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// ===== TOTAL EDITABLE =====
|
||||
case 'total_weight': {
|
||||
if (totalWeight > 0) {
|
||||
formik.setFieldValue('avg_weight', roundWeight(totalWeight / qty));
|
||||
|
||||
// Hitung ulang total_price berdasarkan flag produk
|
||||
if (unitPrice > 0) {
|
||||
if (isOvkOrPakan) {
|
||||
// Untuk OVK/PAKAN: total_price = qty × unit_price
|
||||
formik.setFieldValue('total_price', roundPrice(qty * unitPrice));
|
||||
} else {
|
||||
// Untuk produk lain: total_price = unit_price × total_weight
|
||||
formik.setFieldValue(
|
||||
'total_price',
|
||||
roundPrice(unitPrice * totalWeight)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'total_price': {
|
||||
if (totalPrice > 0) {
|
||||
if (isOvkOrPakan && qty > 0) {
|
||||
// Untuk OVK/PAKAN: unit_price = total_price / qty
|
||||
formik.setFieldValue('unit_price', roundPrice(totalPrice / qty));
|
||||
} else if (totalWeight > 0) {
|
||||
// Untuk produk lain: unit_price = total_price / total_weight
|
||||
formik.setFieldValue(
|
||||
'unit_price',
|
||||
roundPrice(totalPrice / totalWeight)
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
handleMarketingCalculation(field, {
|
||||
values: formik.values,
|
||||
setFieldValue: formik.setFieldValue,
|
||||
hasSisaBerat,
|
||||
});
|
||||
};
|
||||
|
||||
// ===== Formik Error List =====
|
||||
@@ -617,7 +515,7 @@ const SalesOrderProductForm = ({
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Harga per convertion unit */}
|
||||
{/* Harga per convertion unit (PETI / KG) */}
|
||||
{(formik.values.convertion_unit?.value.toLowerCase() === 'peti' ||
|
||||
formik.values.convertion_unit?.value.toLowerCase() === 'kg') && (
|
||||
<NumberInput
|
||||
@@ -639,12 +537,34 @@ const SalesOrderProductForm = ({
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Harga per butir untuk TELUR + QTY */}
|
||||
{formik.values.marketing_type?.value.toLowerCase() === 'telur' &&
|
||||
formik.values.convertion_unit?.value.toLowerCase() === 'qty' && (
|
||||
<NumberInput
|
||||
required
|
||||
label='Harga / Butir (Rp)'
|
||||
name='price_per_qty'
|
||||
value={formik.values.price_per_qty ?? undefined}
|
||||
onChange={(e) => {
|
||||
formik.setFieldValue('price_per_qty', Number(e.target.value));
|
||||
setCurrentInput('price_per_qty');
|
||||
}}
|
||||
onBlur={() => handleBlurField('price_per_qty')}
|
||||
isError={
|
||||
formik.touched.price_per_qty &&
|
||||
Boolean(formik.errors.price_per_qty)
|
||||
}
|
||||
errorMessage={formik.errors.price_per_qty}
|
||||
placeholder='Masukan Harga per Butir'
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Harga Satuan per Uom Produk Warehouse */}
|
||||
{formik.values.convertion_unit?.value.toLowerCase() !== 'peti' &&
|
||||
formik.values.convertion_unit?.value.toLowerCase() !== 'kg' && (
|
||||
<NumberInput
|
||||
required
|
||||
label={`Harga / ${selectedProductWarehouse?.product?.uom?.name ?? 'Produk'} (Rp)`}
|
||||
label={`Harga / ${formik.values.convertion_unit?.label !== 'qty' ? 'Kg' : (selectedProductWarehouse?.product?.uom?.name ?? 'Produk')} (Rp)`}
|
||||
name='unit_price'
|
||||
value={formik.values.unit_price}
|
||||
onChange={(e) => {
|
||||
|
||||
@@ -208,8 +208,12 @@ const SalesOrderProductTable = ({
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Gudang</td>
|
||||
<td className='text-sm px-4 py-3'>{item.kandang?.label}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Kategori</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{item.product_warehouse?.label}
|
||||
{item.marketing_type?.label}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -218,6 +222,20 @@ const SalesOrderProductTable = ({
|
||||
{item.product_warehouse?.label}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Tipe Konversi</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{item.convertion_unit?.label}
|
||||
</td>
|
||||
</tr>
|
||||
{item.convertion_unit?.value.toLowerCase() === 'peti' && (
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Total Peti</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{item.convertion_unit?.label}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Total Bobot</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
@@ -238,13 +256,17 @@ const SalesOrderProductTable = ({
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Qty</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{item.marketing_type?.value === 'telur'
|
||||
? 'Total Butir Telur'
|
||||
: 'Qty'}
|
||||
</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{`${formatNumber(parseFloat(item.qty as string))} ${item.uom || ''}`}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Total Harga Satuan</td>
|
||||
<td className='text-sm px-4 py-3'>Harga Satuan</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{formatCurrency(parseFloat(item.unit_price as string))}
|
||||
</td>
|
||||
|
||||
Reference in New Issue
Block a user