Merge branch 'development' into 'production'

Development

See merge request mbugroup/lti-web-client!388
This commit is contained in:
Adnan Zahir
2026-04-11 14:13:02 +07:00
11 changed files with 166 additions and 76 deletions
@@ -199,6 +199,9 @@ const DeliveryOrderFormModal = ({}: { initialValues?: Marketing }) => {
'yyyy-MM-DD' 'yyyy-MM-DD'
), ),
vehicle_number: product.vehicle_number, vehicle_number: product.vehicle_number,
weight_per_convertion: parseFloat(
String(product.weight_per_convertion ?? 0)
),
}; };
} }
}) })
@@ -432,6 +435,9 @@ const DeliveryOrderFormModal = ({}: { initialValues?: Marketing }) => {
'yyyy-MM-DD' 'yyyy-MM-DD'
), ),
vehicle_number: product.vehicle_number, vehicle_number: product.vehicle_number,
weight_per_convertion: parseFloat(
String(product.weight_per_convertion ?? 0)
),
}; };
} }
}) })
@@ -151,6 +151,25 @@ export const DeliveryProductToFieldValues = (
value: item.product_warehouse.warehouse.id, value: item.product_warehouse.warehouse.id,
label: item.product_warehouse.warehouse.name, label: item.product_warehouse.warehouse.name,
}; };
const initialSisaBerat =
item?.total_weight &&
salesOrder?.weight_per_convertion &&
salesOrder?.total_peti
? Number(item.total_weight) -
Number(salesOrder.weight_per_convertion) *
Number(salesOrder.total_peti)
: 0;
const initialPricePerConvertion =
item?.total_price &&
salesOrder?.total_peti &&
Number(salesOrder.total_peti) !== 0
? (Number(item.total_price) -
initialSisaBerat * Number(item.unit_price || 0)) /
Number(salesOrder.total_peti)
: Number(item?.unit_price || 0);
return { return {
id: salesOrder?.id, id: salesOrder?.id,
unit_price: item.unit_price, unit_price: item.unit_price,
@@ -193,6 +212,10 @@ export const DeliveryProductToFieldValues = (
avg_weight: item.avg_weight, avg_weight: item.avg_weight,
total_price: item.total_price, total_price: item.total_price,
}, },
total_peti: salesOrder?.total_peti,
weight_per_convertion:
item?.weight_per_convertion ?? salesOrder?.weight_per_convertion ?? 0,
price_per_convertion: initialPricePerConvertion,
} as DeliveryOrderProductFormValues; } as DeliveryOrderProductFormValues;
}); });
@@ -203,6 +226,8 @@ export const mergeSOwithDO = (
deliveryOrders: DeliveryOrderProductFormValues[], deliveryOrders: DeliveryOrderProductFormValues[],
autofill?: boolean autofill?: boolean
): DeliveryOrderProductFormValues[] => { ): DeliveryOrderProductFormValues[] => {
const hasDeliveryOrders = deliveryOrders.length > 0;
return salesOrders.map((so) => { return salesOrders.map((so) => {
const delivery = deliveryOrders.find( const delivery = deliveryOrders.find(
(d) => d?.marketing_product_id === so.id (d) => d?.marketing_product_id === so.id
@@ -227,30 +252,50 @@ export const mergeSOwithDO = (
delivery_date: delivery?.delivery_date || undefined, delivery_date: delivery?.delivery_date || undefined,
do_number: delivery?.do_number || undefined, do_number: delivery?.do_number || undefined,
vehicle_number: delivery?.vehicle_number || so.vehicle_number, vehicle_number: delivery?.vehicle_number || so.vehicle_number,
unit_price: autofill ? delivery?.unit_price : salesOrderUnitPrice, unit_price:
total_weight: autofill ? delivery?.total_weight : so.total_weight, autofill && hasDeliveryOrders
qty: autofill ? delivery?.qty : so.qty, ? delivery?.unit_price
avg_weight: autofill ? delivery?.avg_weight : so.avg_weight, : salesOrderUnitPrice,
total_price: autofill ? delivery?.total_price : so.total_price, total_weight:
autofill && hasDeliveryOrders
? delivery?.total_weight
: so.total_weight,
qty: autofill && hasDeliveryOrders ? delivery?.qty : so.qty,
avg_weight:
autofill && hasDeliveryOrders ? delivery?.avg_weight : so.avg_weight,
total_price:
autofill && hasDeliveryOrders ? delivery?.total_price : so.total_price,
marketing_product: so, // jika ada, override marketing_product: so, // jika ada, override
uom: autofill ? delivery?.uom : so.uom, uom: autofill && hasDeliveryOrders ? delivery?.uom : so.uom,
weight_per_convertion: autofill weight_per_convertion:
? delivery?.weight_per_convertion autofill && hasDeliveryOrders
: so.weight_per_convertion, ? delivery?.weight_per_convertion
price_per_convertion: autofill : so.weight_per_convertion,
? delivery?.price_per_convertion price_per_convertion:
: so.price_per_convertion, autofill && hasDeliveryOrders
convertion_unit: autofill ? delivery?.price_per_convertion
? delivery?.convertion_unit : so.price_per_convertion,
: so.convertion_unit, convertion_unit:
marketing_type: autofill ? delivery?.marketing_type : so.marketing_type, autofill && hasDeliveryOrders
total_peti: autofill ? delivery?.total_peti : so.total_peti, ? delivery?.convertion_unit
price_per_qty: autofill ? delivery?.price_per_qty : salesOrderPricePerQty, : so.convertion_unit,
sisa_berat: autofill ? delivery?.sisa_berat : so.sisa_berat, marketing_type:
price_sisa_berat: autofill autofill && hasDeliveryOrders
? delivery?.price_sisa_berat ? delivery?.marketing_type
: so.price_sisa_berat, : so.marketing_type,
week: autofill ? delivery?.week : so.week, total_peti:
autofill && hasDeliveryOrders ? delivery?.total_peti : so.total_peti,
price_per_qty:
autofill && hasDeliveryOrders
? delivery?.price_per_qty
: salesOrderPricePerQty,
sisa_berat:
autofill && hasDeliveryOrders ? delivery?.sisa_berat : so.sisa_berat,
price_sisa_berat:
autofill && hasDeliveryOrders
? delivery?.price_sisa_berat
: so.price_sisa_berat,
week: autofill && hasDeliveryOrders ? delivery?.week : so.week,
} as DeliveryOrderProductFormValues; } as DeliveryOrderProductFormValues;
}); });
}; };
@@ -126,18 +126,14 @@ const DeliveryOrderProductForm = ({
Number(initialValues.total_peti) Number(initialValues.total_peti)
: 0; : 0;
// const initialPricePerConvertion = const initialPricePerConvertion =
// initialValues?.total_price && initialValues?.total_price &&
// initialValues?.total_peti && initialValues?.total_peti &&
// Number(initialValues.total_peti) !== 0 Number(initialValues.total_peti) !== 0
// ? (Number(initialValues.total_price) - ? (Number(initialValues.total_price) -
// initialSisaBerat * Number(initialValues.unit_price || 0)) / initialSisaBerat * Number(initialValues.unit_price || 0)) /
// Number(initialValues.total_peti) Number(initialValues.total_peti)
// : 0; : Number(initialValues?.unit_price || 0);
const initialPricePerConvertion = initialValues?.unit_price
? Number(initialValues?.unit_price)
: 0;
const initialPriceSisaBerat = const initialPriceSisaBerat =
initialValues?.total_price && initialValues?.total_peti initialValues?.total_price && initialValues?.total_peti
@@ -728,7 +724,7 @@ const DeliveryOrderProductForm = ({
placeholder='Masukan Total Peti' placeholder='Masukan Total Peti'
endAdornment={ endAdornment={
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>
<span className='text-sm text-base-content/50'>Kg</span> <span className='text-sm text-base-content/50'>Peti</span>
</div> </div>
} }
bottomLabel={`1 ${formik.values.convertion_unit?.value.toLowerCase()} = ${formik.values.weight_per_convertion ?? 0} Kg`} bottomLabel={`1 ${formik.values.convertion_unit?.value.toLowerCase()} = ${formik.values.weight_per_convertion ?? 0} Kg`}
@@ -778,6 +774,9 @@ const DeliveryOrderProductForm = ({
} }
errorMessage={formik.errors.total_weight} errorMessage={formik.errors.total_weight}
placeholder='Masukan Total Bobot' placeholder='Masukan Total Bobot'
disabled={
formik.values.convertion_unit?.value.toLowerCase() === 'peti'
}
/> />
)} )}
@@ -61,18 +61,14 @@ const SalesOrderProductForm = ({
Number(initialValues.total_peti) Number(initialValues.total_peti)
: 0; : 0;
// const initialPricePerConvertion = const initialPricePerConvertion =
// initialValues?.total_price && initialValues?.total_price &&
// initialValues?.total_peti && initialValues?.total_peti &&
// Number(initialValues.total_peti) !== 0 Number(initialValues.total_peti) !== 0
// ? (Number(initialValues.total_price) - ? (Number(initialValues.total_price) -
// initialSisaBerat * Number(initialValues.unit_price || 0)) / initialSisaBerat * Number(initialValues.unit_price || 0)) /
// Number(initialValues.total_peti) Number(initialValues.total_peti)
// : 0; : Number(initialValues?.unit_price || 0);
const initialPricePerConvertion = initialValues?.unit_price
? Number(initialValues?.unit_price)
: 0;
const isInitialTelurQty = const isInitialTelurQty =
initialValues?.marketing_type?.value?.toLowerCase() === 'telur' && initialValues?.marketing_type?.value?.toLowerCase() === 'telur' &&
@@ -624,7 +620,7 @@ const SalesOrderProductForm = ({
placeholder='Masukan Total Peti' placeholder='Masukan Total Peti'
endAdornment={ endAdornment={
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>
<span className='text-sm text-base-content/50'>Kg</span> <span className='text-sm text-base-content/50'>Peti</span>
</div> </div>
} }
bottomLabel={`1 ${formik.values.convertion_unit?.value.toLowerCase()} = ${formik.values.weight_per_convertion ?? 0} Kg`} bottomLabel={`1 ${formik.values.convertion_unit?.value.toLowerCase()} = ${formik.values.weight_per_convertion ?? 0} Kg`}
@@ -674,6 +670,9 @@ const SalesOrderProductForm = ({
} }
errorMessage={formik.errors.total_weight} errorMessage={formik.errors.total_weight}
placeholder='Masukan Total Bobot' placeholder='Masukan Total Bobot'
disabled={
formik.values.convertion_unit?.value.toLowerCase() === 'peti'
}
/> />
)} )}
@@ -85,7 +85,7 @@ const DeliveryOrderProductTable = ({
<th className='text-start font-medium text-base-content/50 text-sm px-4 py-3'> <th className='text-start font-medium text-base-content/50 text-sm px-4 py-3'>
<div className='flex w-full flex-row gap-1 items-center justify-between h-full'> <div className='flex w-full flex-row gap-1 items-center justify-between h-full'>
<div>Value</div> <div>Value</div>
{formType !== 'success' && {/* {formType !== 'success' &&
(formType === 'add_delivery' || (formType === 'add_delivery' ||
formType === 'edit_delivery' || formType === 'edit_delivery' ||
formType === 'detail') && ( formType === 'detail') && (
@@ -102,7 +102,7 @@ const DeliveryOrderProductTable = ({
<Icon icon='heroicons:pencil' width={20} height={20} /> <Icon icon='heroicons:pencil' width={20} height={20} />
</Button> </Button>
</div> </div>
)} )} */}
</div> </div>
</th> </th>
</tr> </tr>
@@ -141,12 +141,15 @@ const DeliveryOrderProductTable = ({
<tr> <tr>
<td className='text-sm px-4 py-3'>Total Bobot</td> <td className='text-sm px-4 py-3'>Total Bobot</td>
<td className='text-sm px-4 py-3'> <td className='text-sm px-4 py-3'>
{formatNumber(Number(item.total_weight))} {formatNumber(Number(item.total_weight))} Kg
</td> </td>
</tr> </tr>
)} )}
<tr> <tr>
<td className='text-sm px-4 py-3'>Total Harga Satuan</td> <td className='text-sm px-4 py-3'>
Total Harga Satuan
{item.convertion_unit?.label.toLowerCase() === 'peti' && ' (Kg)'}
</td>
<td className='text-sm px-4 py-3'> <td className='text-sm px-4 py-3'>
{formatCurrency(parseFloat(item.unit_price as string))} {formatCurrency(parseFloat(item.unit_price as string))}
</td> </td>
@@ -137,8 +137,22 @@ const SalesOrderProductTable = ({
{`${formatNumber(parseFloat(item.qty as string))} ${item.uom || ''}`} {`${formatNumber(parseFloat(item.qty as string))} ${item.uom || ''}`}
</td> </td>
</tr> </tr>
{item.convertion_unit?.value.toLowerCase() === 'peti' && (
<tr>
<td className='text-sm px-4 py-3'>Harga Satuan Per Peti</td>
<td className='text-sm px-4 py-3'>
{formatCurrency(
parseFloat(item.unit_price as string) *
parseFloat(String(item.weight_per_convertion))
)}
</td>
</tr>
)}
<tr> <tr>
<td className='text-sm px-4 py-3'>Harga Satuan</td> <td className='text-sm px-4 py-3'>
Harga Satuan
{item.convertion_unit?.value.toLowerCase() === 'peti' && ' (Kg)'}
</td>
<td className='text-sm px-4 py-3'> <td className='text-sm px-4 py-3'>
{formatCurrency(parseFloat(item.unit_price as string))} {formatCurrency(parseFloat(item.unit_price as string))}
</td> </td>
@@ -597,6 +597,7 @@ const UniformityForm = ({
onBlur={formik.handleBlur} onBlur={formik.handleBlur}
isError={formik.touched.date && Boolean(formik.errors.date)} isError={formik.touched.date && Boolean(formik.errors.date)}
errorMessage={formik.errors.date as string} errorMessage={formik.errors.date as string}
disabled={isNextStep}
/> />
<SelectInput <SelectInput
@@ -615,6 +616,7 @@ const UniformityForm = ({
errorMessage={formik.errors.location_id as string} errorMessage={formik.errors.location_id as string}
isClearable isClearable
className={{ wrapper: 'w-full' }} className={{ wrapper: 'w-full' }}
isDisabled={isNextStep}
/> />
<SelectInput <SelectInput
@@ -627,7 +629,7 @@ const UniformityForm = ({
onInputChange={setProjectFlockSearchValue} onInputChange={setProjectFlockSearchValue}
isLoading={isLoadingProjectFlocks} isLoading={isLoadingProjectFlocks}
onMenuScrollToBottom={loadMoreProjectFlocks} onMenuScrollToBottom={loadMoreProjectFlocks}
isDisabled={!formik.values.location_id} isDisabled={!formik.values.location_id || isNextStep}
isError={ isError={
formik.touched.project_flock_id && formik.touched.project_flock_id &&
Boolean(formik.errors.project_flock_id) Boolean(formik.errors.project_flock_id)
@@ -644,7 +646,7 @@ const UniformityForm = ({
value={formik.values.kandang} value={formik.values.kandang}
onChange={handleKandangChange} onChange={handleKandangChange}
options={kandangOptions} options={kandangOptions}
isDisabled={!formik.values.project_flock_id} isDisabled={!formik.values.project_flock_id || isNextStep}
isError={ isError={
formik.touched.kandang_id && Boolean(formik.errors.kandang_id) formik.touched.kandang_id && Boolean(formik.errors.kandang_id)
} }
@@ -71,24 +71,32 @@ const HppPerKandangTab = ({ tabId }: HppPerKandangTabProps) => {
const filterModal = useModal(); const filterModal = useModal();
// ===== OPTIONS ===== // ===== OPTIONS =====
const { options: areaOptions, isLoadingOptions: isLoadingAreas } = useSelect( const {
AreaApi.basePath, options: areaOptions,
isLoadingOptions: isLoadingAreas,
setInputValue: setAreaInputValue,
loadMore: loadMoreArea,
} = useSelect(AreaApi.basePath, 'id', 'name', 'search');
const {
options: locationOptions,
isLoadingOptions: isLoadingLocations,
setInputValue: setLocationInputValue,
loadMore: loadMoreLocation,
} = useSelect(LocationApi.basePath, 'id', 'name', 'search');
const {
options: kandangOptions,
isLoadingOptions: isLoadingKandangs,
setInputValue: setKandangInputValue,
loadMore: loadMoreKandang,
} = useSelect(
ProjectFlockKandangApi.basePath,
'id', 'id',
'name', 'name_with_period',
'search' 'search'
); );
const { options: locationOptions, isLoadingOptions: isLoadingLocations } =
useSelect(LocationApi.basePath, 'id', 'name', 'search');
const { options: kandangOptions, isLoadingOptions: isLoadingKandangs } =
useSelect(
ProjectFlockKandangApi.basePath,
'id',
'name_with_period',
'search'
);
const showUnrecordedOptions = useMemo( const showUnrecordedOptions = useMemo(
() => [ () => [
{ value: 'false', label: 'Sembunyikan' }, { value: 'false', label: 'Sembunyikan' },
@@ -918,6 +926,8 @@ const HppPerKandangTab = ({ tabId }: HppPerKandangTabProps) => {
isLoading={isLoadingAreas} isLoading={isLoadingAreas}
isClearable isClearable
className={{ wrapper: 'w-full' }} className={{ wrapper: 'w-full' }}
onInputChange={setAreaInputValue}
onMenuScrollToBottom={loadMoreArea}
/> />
{/* Location Filter */} {/* Location Filter */}
@@ -937,6 +947,8 @@ const HppPerKandangTab = ({ tabId }: HppPerKandangTabProps) => {
isLoading={isLoadingLocations} isLoading={isLoadingLocations}
isClearable isClearable
className={{ wrapper: 'w-full' }} className={{ wrapper: 'w-full' }}
onInputChange={setLocationInputValue}
onMenuScrollToBottom={loadMoreLocation}
/> />
{/* Kandang Filter */} {/* Kandang Filter */}
@@ -956,6 +968,8 @@ const HppPerKandangTab = ({ tabId }: HppPerKandangTabProps) => {
isLoading={isLoadingKandangs} isLoading={isLoadingKandangs}
isClearable isClearable
className={{ wrapper: 'w-full' }} className={{ wrapper: 'w-full' }}
onInputChange={setKandangInputValue}
onMenuScrollToBottom={loadMoreKandang}
/> />
{/* Weight Range Filter */} {/* Weight Range Filter */}
+12 -5
View File
@@ -235,10 +235,8 @@ export const calculateTelurPeti = (
const totalPrice = pricePerConvertion * totalPeti + priceSisaBerat; const totalPrice = pricePerConvertion * totalPeti + priceSisaBerat;
setFieldValue('total_price', totalPrice); setFieldValue('total_price', totalPrice);
// Recalculate unit_price = total_price / total_weight // Recalculate unit_price = total_price / total_weight
// TODO: consider sisa berat later
const totalWeight = weightPerConvertion * totalPeti + sisaBerat; const totalWeight = weightPerConvertion * totalPeti + sisaBerat;
updateUnitPrice(totalPrice, totalPeti); updateUnitPrice(totalPrice, totalWeight);
} }
break; break;
} }
@@ -257,8 +255,8 @@ export const calculateTelurPeti = (
if (pricePerConvertion > 0 && totalPeti > 0) { if (pricePerConvertion > 0 && totalPeti > 0) {
const totalPrice = pricePerConvertion * totalPeti + priceSisaBerat; const totalPrice = pricePerConvertion * totalPeti + priceSisaBerat;
setFieldValue('total_price', totalPrice); setFieldValue('total_price', totalPrice);
// Recalculate unit_price = total_price / total_peti // Recalculate unit_price = total_price / totalWeight
updateUnitPrice(totalPrice, totalPeti); updateUnitPrice(totalPrice, totalWeight);
} }
break; break;
} }
@@ -317,6 +315,15 @@ export const calculateTelurPeti = (
updateUnitPrice(totalPrice, totalWeight); updateUnitPrice(totalPrice, totalWeight);
break; break;
} }
case 'qty':
// Recalculate avg_weight = total_weight / qty
if (qty > 0 && values.total_weight) {
setFieldValue(
'avg_weight',
preciseWeight(Number(values.total_weight) / qty)
);
}
break;
} }
}; };
+1
View File
@@ -61,6 +61,7 @@ export type BaseDelivery = {
avg_weight: number; avg_weight: number;
total_price: number; total_price: number;
vehicle_number: string; vehicle_number: string;
weight_per_convertion: number;
}; };
export type MarketingProduct = { export type MarketingProduct = {