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!382
This commit is contained in:
@@ -368,7 +368,9 @@ const DeliveryOrderFormModal = ({}: { initialValues?: Marketing }) => {
|
|||||||
const currentProducts = deliveryOrderValues?.find(
|
const currentProducts = deliveryOrderValues?.find(
|
||||||
(product) => product.id == id
|
(product) => product.id == id
|
||||||
);
|
);
|
||||||
setSelectedDeliveryProduct(values ?? currentProducts ?? null);
|
|
||||||
|
setSelectedDeliveryProduct(currentProducts ?? values ?? null);
|
||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
setStep(2);
|
setStep(2);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,15 +144,15 @@ export const DeliveryProductToFieldValues = (
|
|||||||
delivery: BaseDeliveryOrder
|
delivery: BaseDeliveryOrder
|
||||||
): DeliveryOrderProductFormValues[] => {
|
): DeliveryOrderProductFormValues[] => {
|
||||||
const data = delivery.deliveries.map((item) => {
|
const data = delivery.deliveries.map((item) => {
|
||||||
const soId = salesOrders.find(
|
const salesOrder = salesOrders.find(
|
||||||
(so) => so.product_warehouse.id === item.product_warehouse.id
|
(so) => so.product_warehouse.id === item.product_warehouse.id
|
||||||
)?.id;
|
);
|
||||||
const warehouseOption = {
|
const warehouseOption = {
|
||||||
value: item.product_warehouse.warehouse.id,
|
value: item.product_warehouse.warehouse.id,
|
||||||
label: item.product_warehouse.warehouse.name,
|
label: item.product_warehouse.warehouse.name,
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
id: soId,
|
id: salesOrder?.id,
|
||||||
unit_price: item.unit_price,
|
unit_price: item.unit_price,
|
||||||
total_weight: item.total_weight,
|
total_weight: item.total_weight,
|
||||||
qty: item.qty,
|
qty: item.qty,
|
||||||
@@ -161,9 +161,21 @@ export const DeliveryProductToFieldValues = (
|
|||||||
vehicle_number: item.vehicle_number,
|
vehicle_number: item.vehicle_number,
|
||||||
delivery_date: formatDate(delivery.delivery_date, 'yyyy-MM-DD'),
|
delivery_date: formatDate(delivery.delivery_date, 'yyyy-MM-DD'),
|
||||||
do_number: delivery.do_number,
|
do_number: delivery.do_number,
|
||||||
marketing_product_id: soId,
|
marketing_product_id: salesOrder?.id,
|
||||||
|
marketing_type: salesOrder?.marketing_type
|
||||||
|
? {
|
||||||
|
value: salesOrder?.marketing_type,
|
||||||
|
label: formatTitleCase(salesOrder?.marketing_type),
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
convertion_unit: salesOrder?.convertion_unit
|
||||||
|
? {
|
||||||
|
value: salesOrder?.convertion_unit.toLowerCase(),
|
||||||
|
label: formatTitleCase(salesOrder?.convertion_unit),
|
||||||
|
}
|
||||||
|
: null,
|
||||||
marketing_product: {
|
marketing_product: {
|
||||||
id: soId,
|
id: salesOrder?.id,
|
||||||
vehicle_number: item.vehicle_number,
|
vehicle_number: item.vehicle_number,
|
||||||
warehouse_id: item.product_warehouse.warehouse.id,
|
warehouse_id: item.product_warehouse.warehouse.id,
|
||||||
warehouse: warehouseOption,
|
warehouse: warehouseOption,
|
||||||
@@ -183,6 +195,7 @@ export const DeliveryProductToFieldValues = (
|
|||||||
},
|
},
|
||||||
} as DeliveryOrderProductFormValues;
|
} as DeliveryOrderProductFormValues;
|
||||||
});
|
});
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
export const mergeSOwithDO = (
|
export const mergeSOwithDO = (
|
||||||
@@ -194,6 +207,19 @@ export const mergeSOwithDO = (
|
|||||||
const delivery = deliveryOrders.find(
|
const delivery = deliveryOrders.find(
|
||||||
(d) => d?.marketing_product_id === so.id
|
(d) => d?.marketing_product_id === so.id
|
||||||
);
|
);
|
||||||
|
const isTelurQty =
|
||||||
|
so.marketing_type?.value?.toLowerCase() === 'telur' &&
|
||||||
|
so.convertion_unit?.value?.toLowerCase() === 'qty';
|
||||||
|
const salesOrderUnitPrice =
|
||||||
|
isTelurQty && Number(so.total_price || 0) > 0 && Number(so.qty || 0) > 0
|
||||||
|
? Number(so.total_price) / Number(so.qty)
|
||||||
|
: so.unit_price;
|
||||||
|
const salesOrderPricePerQty =
|
||||||
|
isTelurQty &&
|
||||||
|
Number(so.total_price || 0) > 0 &&
|
||||||
|
Number(so.total_weight || 0) > 0
|
||||||
|
? Number(so.total_price) / Number(so.total_weight)
|
||||||
|
: so.price_per_qty;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...so, // nilai dasar dari sales order
|
...so, // nilai dasar dari sales order
|
||||||
@@ -201,30 +227,30 @@ 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 ? so.unit_price : delivery?.unit_price,
|
unit_price: autofill ? delivery?.unit_price : salesOrderUnitPrice,
|
||||||
total_weight: autofill ? so.total_weight : delivery?.total_weight,
|
total_weight: autofill ? delivery?.total_weight : so.total_weight,
|
||||||
qty: autofill ? so.qty : delivery?.qty,
|
qty: autofill ? delivery?.qty : so.qty,
|
||||||
avg_weight: autofill ? so.avg_weight : delivery?.avg_weight,
|
avg_weight: autofill ? delivery?.avg_weight : so.avg_weight,
|
||||||
total_price: autofill ? so.total_price : delivery?.total_price,
|
total_price: autofill ? delivery?.total_price : so.total_price,
|
||||||
marketing_product: so, // jika ada, override
|
marketing_product: so, // jika ada, override
|
||||||
uom: autofill ? so.uom : delivery?.uom,
|
uom: autofill ? delivery?.uom : so.uom,
|
||||||
weight_per_convertion: autofill
|
weight_per_convertion: autofill
|
||||||
? so.weight_per_convertion
|
? delivery?.weight_per_convertion
|
||||||
: delivery?.weight_per_convertion,
|
: so.weight_per_convertion,
|
||||||
price_per_convertion: autofill
|
price_per_convertion: autofill
|
||||||
? so.price_per_convertion
|
? delivery?.price_per_convertion
|
||||||
: delivery?.price_per_convertion,
|
: so.price_per_convertion,
|
||||||
convertion_unit: autofill
|
convertion_unit: autofill
|
||||||
? so.convertion_unit
|
? delivery?.convertion_unit
|
||||||
: delivery?.convertion_unit,
|
: so.convertion_unit,
|
||||||
marketing_type: autofill ? so.marketing_type : delivery?.marketing_type,
|
marketing_type: autofill ? delivery?.marketing_type : so.marketing_type,
|
||||||
total_peti: autofill ? so.total_peti : delivery?.total_peti,
|
total_peti: autofill ? delivery?.total_peti : so.total_peti,
|
||||||
price_per_qty: autofill ? so.price_per_qty : delivery?.price_per_qty,
|
price_per_qty: autofill ? delivery?.price_per_qty : salesOrderPricePerQty,
|
||||||
sisa_berat: autofill ? so.sisa_berat : delivery?.sisa_berat,
|
sisa_berat: autofill ? delivery?.sisa_berat : so.sisa_berat,
|
||||||
price_sisa_berat: autofill
|
price_sisa_berat: autofill
|
||||||
? so.price_sisa_berat
|
? delivery?.price_sisa_berat
|
||||||
: delivery?.price_sisa_berat,
|
: so.price_sisa_berat,
|
||||||
week: autofill ? so.week : delivery?.week,
|
week: autofill ? delivery?.week : so.week,
|
||||||
} as DeliveryOrderProductFormValues;
|
} as DeliveryOrderProductFormValues;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
+122
-36
@@ -32,6 +32,63 @@ import Dropdown from '@/components/Dropdown';
|
|||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import { handleMarketingCalculation } from '@/lib/marketing-calculation';
|
import { handleMarketingCalculation } from '@/lib/marketing-calculation';
|
||||||
|
|
||||||
|
type PricingOption =
|
||||||
|
| string
|
||||||
|
| {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
| null
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
type PricingSource =
|
||||||
|
| {
|
||||||
|
marketing_type?: PricingOption;
|
||||||
|
convertion_unit?: PricingOption;
|
||||||
|
total_price?: string | number | null;
|
||||||
|
qty?: string | number | null;
|
||||||
|
total_weight?: string | number | null;
|
||||||
|
unit_price?: string | number | null;
|
||||||
|
price_per_qty?: number | null;
|
||||||
|
}
|
||||||
|
| null
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
const getOptionValue = (value?: PricingOption) => {
|
||||||
|
if (!value) return undefined;
|
||||||
|
if (typeof value === 'string') return value.toLowerCase();
|
||||||
|
|
||||||
|
return value.value?.toLowerCase();
|
||||||
|
};
|
||||||
|
|
||||||
|
const isTelurQtyProduct = (value?: PricingSource) =>
|
||||||
|
getOptionValue(value?.marketing_type) === 'telur' &&
|
||||||
|
getOptionValue(value?.convertion_unit) === 'qty';
|
||||||
|
|
||||||
|
const getDisplayedUnitPrice = (value?: PricingSource) => {
|
||||||
|
if (
|
||||||
|
isTelurQtyProduct(value) &&
|
||||||
|
Number(value?.total_price || 0) > 0 &&
|
||||||
|
Number(value?.qty || 0) > 0
|
||||||
|
) {
|
||||||
|
return Number(value?.total_price) / Number(value?.qty);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value?.unit_price ?? undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDisplayedPricePerQty = (value?: PricingSource) => {
|
||||||
|
if (
|
||||||
|
isTelurQtyProduct(value) &&
|
||||||
|
Number(value?.total_price || 0) > 0 &&
|
||||||
|
Number(value?.total_weight || 0) > 0
|
||||||
|
) {
|
||||||
|
return Number(value?.total_price) / Number(value?.total_weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value?.price_per_qty ?? null;
|
||||||
|
};
|
||||||
|
|
||||||
const DeliveryOrderProductForm = ({
|
const DeliveryOrderProductForm = ({
|
||||||
formState,
|
formState,
|
||||||
salesOrders,
|
salesOrders,
|
||||||
@@ -69,14 +126,18 @@ 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;
|
// : 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
|
||||||
@@ -154,6 +215,27 @@ const DeliveryOrderProductForm = ({
|
|||||||
(item) => item.id === initialValues?.marketing_product_id
|
(item) => item.id === initialValues?.marketing_product_id
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const defaultPricingSource: PricingSource = {
|
||||||
|
marketing_type:
|
||||||
|
initialValues?.marketing_type ?? salesOrder?.marketing_type ?? null,
|
||||||
|
convertion_unit:
|
||||||
|
initialValues?.convertion_unit ?? salesOrder?.convertion_unit ?? null,
|
||||||
|
total_price:
|
||||||
|
deliveryOrder?.total_price ??
|
||||||
|
initialValues?.total_price ??
|
||||||
|
salesOrder?.total_price,
|
||||||
|
qty: deliveryOrder?.qty ?? initialValues?.qty ?? salesOrder?.qty,
|
||||||
|
total_weight:
|
||||||
|
deliveryOrder?.total_weight ??
|
||||||
|
initialValues?.total_weight ??
|
||||||
|
salesOrder?.total_weight,
|
||||||
|
unit_price:
|
||||||
|
deliveryOrder?.unit_price ??
|
||||||
|
initialValues?.unit_price ??
|
||||||
|
salesOrder?.unit_price,
|
||||||
|
price_per_qty: initialValues?.price_per_qty ?? null,
|
||||||
|
};
|
||||||
|
|
||||||
const formik = useFormik<DeliveryOrderProductFormValues>({
|
const formik = useFormik<DeliveryOrderProductFormValues>({
|
||||||
enableReinitialize: true,
|
enableReinitialize: true,
|
||||||
initialValues: {
|
initialValues: {
|
||||||
@@ -167,8 +249,7 @@ const DeliveryOrderProductForm = ({
|
|||||||
undefined,
|
undefined,
|
||||||
marketing_product_id:
|
marketing_product_id:
|
||||||
salesOrder?.id || initialValues?.marketing_product_id || undefined,
|
salesOrder?.id || initialValues?.marketing_product_id || undefined,
|
||||||
unit_price:
|
unit_price: getDisplayedUnitPrice(defaultPricingSource),
|
||||||
deliveryOrder?.unit_price ?? initialValues?.unit_price ?? undefined,
|
|
||||||
total_weight:
|
total_weight:
|
||||||
deliveryOrder?.total_weight ?? initialValues?.total_weight ?? undefined,
|
deliveryOrder?.total_weight ?? initialValues?.total_weight ?? undefined,
|
||||||
qty: deliveryOrder?.qty ?? initialValues?.qty ?? undefined,
|
qty: deliveryOrder?.qty ?? initialValues?.qty ?? undefined,
|
||||||
@@ -186,7 +267,7 @@ const DeliveryOrderProductForm = ({
|
|||||||
convertion_unit: initialValues?.convertion_unit || null,
|
convertion_unit: initialValues?.convertion_unit || null,
|
||||||
marketing_type: initialValues?.marketing_type || null,
|
marketing_type: initialValues?.marketing_type || null,
|
||||||
total_peti: initialValues?.total_peti ?? null,
|
total_peti: initialValues?.total_peti ?? null,
|
||||||
price_per_qty: initialValues?.price_per_qty ?? null,
|
price_per_qty: getDisplayedPricePerQty(defaultPricingSource),
|
||||||
sisa_berat: initialSisaBerat,
|
sisa_berat: initialSisaBerat,
|
||||||
price_sisa_berat: initialPriceSisaBerat,
|
price_sisa_berat: initialPriceSisaBerat,
|
||||||
week: initialValues?.week ?? null,
|
week: initialValues?.week ?? null,
|
||||||
@@ -329,7 +410,11 @@ const DeliveryOrderProductForm = ({
|
|||||||
if (!Boolean(initialValues.qty)) {
|
if (!Boolean(initialValues.qty)) {
|
||||||
handleResetForm();
|
handleResetForm();
|
||||||
} else {
|
} else {
|
||||||
setFormikValues(initialValues);
|
setFormikValues({
|
||||||
|
...initialValues,
|
||||||
|
unit_price: getDisplayedUnitPrice(initialValues),
|
||||||
|
price_per_qty: getDisplayedPricePerQty(initialValues),
|
||||||
|
});
|
||||||
if (initialValues?.marketing_product_id) {
|
if (initialValues?.marketing_product_id) {
|
||||||
setSelectedProduct({
|
setSelectedProduct({
|
||||||
value: initialValues?.id,
|
value: initialValues?.id,
|
||||||
@@ -458,10 +543,11 @@ const DeliveryOrderProductForm = ({
|
|||||||
marketing_product_id: selected.value as number,
|
marketing_product_id: selected.value as number,
|
||||||
marketing_product: soFieldValues,
|
marketing_product: soFieldValues,
|
||||||
qty: so.qty,
|
qty: so.qty,
|
||||||
unit_price: so.unit_price,
|
unit_price: getDisplayedUnitPrice(so),
|
||||||
total_price: so.total_price,
|
total_price: so.total_price,
|
||||||
avg_weight: so.avg_weight,
|
avg_weight: so.avg_weight,
|
||||||
total_weight: so.total_weight,
|
total_weight: so.total_weight,
|
||||||
|
price_per_qty: getDisplayedPricePerQty(so),
|
||||||
vehicle_number: so.vehicle_number,
|
vehicle_number: so.vehicle_number,
|
||||||
week: soFieldValues.week ?? null,
|
week: soFieldValues.week ?? null,
|
||||||
});
|
});
|
||||||
@@ -761,12 +847,32 @@ const DeliveryOrderProductForm = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Harga per butir untuk TELUR + QTY */}
|
{/* Harga Satuan */}
|
||||||
|
{formik.values.convertion_unit?.value.toLowerCase() !== 'peti' &&
|
||||||
|
formik.values.convertion_unit?.value.toLowerCase() !== 'kg' && (
|
||||||
|
<NumberInput
|
||||||
|
required
|
||||||
|
label={`Harga / ${formik.values.convertion_unit?.label.toLowerCase() !== 'qty' ? 'Kg' : 'Butir'} (Rp)`}
|
||||||
|
name='unit_price'
|
||||||
|
value={formik.values.unit_price}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = Number(e.target.value);
|
||||||
|
handleFieldChange('unit_price', value, () =>
|
||||||
|
setCurrentInput(e.target.name)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
isError={Boolean(formik.errors.unit_price)}
|
||||||
|
errorMessage={formik.errors.unit_price}
|
||||||
|
placeholder='Masukan Harga Satuan'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Harga per kg untuk TELUR + QTY */}
|
||||||
{formik.values.marketing_type?.value.toLowerCase() === 'telur' &&
|
{formik.values.marketing_type?.value.toLowerCase() === 'telur' &&
|
||||||
formik.values.convertion_unit?.value.toLowerCase() === 'qty' && (
|
formik.values.convertion_unit?.value.toLowerCase() === 'qty' && (
|
||||||
<NumberInput
|
<NumberInput
|
||||||
required
|
required
|
||||||
label='Harga / Butir (Rp)'
|
label='Harga / Kg (Rp)'
|
||||||
name='price_per_qty'
|
name='price_per_qty'
|
||||||
value={formik.values.price_per_qty ?? undefined}
|
value={formik.values.price_per_qty ?? undefined}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@@ -780,27 +886,7 @@ const DeliveryOrderProductForm = ({
|
|||||||
Boolean(formik.errors.price_per_qty)
|
Boolean(formik.errors.price_per_qty)
|
||||||
}
|
}
|
||||||
errorMessage={formik.errors.price_per_qty}
|
errorMessage={formik.errors.price_per_qty}
|
||||||
placeholder='Masukan Harga per Butir'
|
placeholder='Masukan Harga per Kg'
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Harga Satuan */}
|
|
||||||
{formik.values.convertion_unit?.value.toLowerCase() !== 'peti' &&
|
|
||||||
formik.values.convertion_unit?.value.toLowerCase() !== 'kg' && (
|
|
||||||
<NumberInput
|
|
||||||
required
|
|
||||||
label={`Harga / ${isResponseSuccess(productData) ? productData?.data?.uom?.name : 'Produk'} (Rp)`}
|
|
||||||
name='unit_price'
|
|
||||||
value={formik.values.unit_price}
|
|
||||||
onChange={(e) => {
|
|
||||||
const value = Number(e.target.value);
|
|
||||||
handleFieldChange('unit_price', value, () =>
|
|
||||||
setCurrentInput(e.target.name)
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
isError={Boolean(formik.errors.unit_price)}
|
|
||||||
errorMessage={formik.errors.unit_price}
|
|
||||||
placeholder='Masukan Harga Satuan'
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -157,11 +157,6 @@ const SalesOrderProductForm = ({
|
|||||||
);
|
);
|
||||||
}, [selectedProductWarehouse, formik.values.marketing_type]);
|
}, [selectedProductWarehouse, formik.values.marketing_type]);
|
||||||
|
|
||||||
console.log({
|
|
||||||
initialValues,
|
|
||||||
values: formik.values,
|
|
||||||
});
|
|
||||||
|
|
||||||
// ===== Options =====
|
// ===== Options =====
|
||||||
const {
|
const {
|
||||||
options: warehouseOptions,
|
options: warehouseOptions,
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ import { Icon } from '@iconify/react';
|
|||||||
import { useRef, useMemo } from 'react';
|
import { useRef, useMemo } from 'react';
|
||||||
import { formatCurrency, formatDate, formatNumber } from '@/lib/helper';
|
import { formatCurrency, formatDate, formatNumber } from '@/lib/helper';
|
||||||
import DeliveryOrderExport from '@/components/pages/marketing/pdf/DeliveryOrderExport';
|
import DeliveryOrderExport from '@/components/pages/marketing/pdf/DeliveryOrderExport';
|
||||||
import { Marketing, BaseDelivery } from '@/types/api/marketing/marketing';
|
import { Marketing } from '@/types/api/marketing/marketing';
|
||||||
import { Warehouse } from '@/types/api/master-data/warehouse';
|
import { Warehouse } from '@/types/api/master-data/warehouse';
|
||||||
|
import { DeliveryProductToFieldValues } from '@/components/pages/marketing/form/MarketingForm.schema';
|
||||||
|
|
||||||
type DeliveryOrderProductTableProps = {
|
type DeliveryOrderProductTableProps = {
|
||||||
data: DeliveryOrderProductFormValues[];
|
data: DeliveryOrderProductFormValues[];
|
||||||
@@ -55,14 +56,17 @@ const DeliveryOrderProductTable = ({
|
|||||||
|
|
||||||
const deliveryItems = useMemo(() => {
|
const deliveryItems = useMemo(() => {
|
||||||
if (!hasDeliveryOrder) return [];
|
if (!hasDeliveryOrder) return [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
marketing?.delivery_order?.flatMap((doItem) =>
|
marketing?.delivery_order?.flatMap((doItem) =>
|
||||||
doItem.deliveries.map((delivery) => ({
|
DeliveryProductToFieldValues(marketing?.sales_order, doItem).map(
|
||||||
...delivery,
|
(delivery) => ({
|
||||||
do_number: doItem.do_number,
|
...delivery,
|
||||||
delivery_date: doItem.delivery_date,
|
do_number: doItem.do_number,
|
||||||
warehouse: doItem.warehouse,
|
delivery_date: doItem.delivery_date,
|
||||||
}))
|
warehouse: doItem.warehouse,
|
||||||
|
})
|
||||||
|
)
|
||||||
) ?? []
|
) ?? []
|
||||||
);
|
);
|
||||||
}, [marketing?.delivery_order, hasDeliveryOrder]);
|
}, [marketing?.delivery_order, hasDeliveryOrder]);
|
||||||
@@ -212,7 +216,7 @@ const DeliveryOrderProductTable = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderDeliveryOrderContent = (
|
const renderDeliveryOrderContent = (
|
||||||
item: BaseDelivery & {
|
item: DeliveryOrderProductFormValues & {
|
||||||
do_number: string;
|
do_number: string;
|
||||||
delivery_date: string;
|
delivery_date: string;
|
||||||
warehouse: Warehouse;
|
warehouse: Warehouse;
|
||||||
@@ -231,6 +235,24 @@ 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 === 'add_delivery' ||
|
||||||
|
formType === 'edit_delivery' ||
|
||||||
|
formType === 'detail') && (
|
||||||
|
<div className='flex flex-row gap-1.5 items-center'>
|
||||||
|
<Button
|
||||||
|
type='button'
|
||||||
|
variant='ghost'
|
||||||
|
color='none'
|
||||||
|
onClick={() => {
|
||||||
|
onEditRef.current(item.id as number, item);
|
||||||
|
}}
|
||||||
|
className='p-0 hover:text-base-content'
|
||||||
|
>
|
||||||
|
<Icon icon='heroicons:pencil' width={20} height={20} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -242,14 +264,14 @@ const DeliveryOrderProductTable = ({
|
|||||||
<tr>
|
<tr>
|
||||||
<td className='text-sm px-4 py-3'>Produk</td>
|
<td className='text-sm px-4 py-3'>Produk</td>
|
||||||
<td className='text-sm px-4 py-3'>
|
<td className='text-sm px-4 py-3'>
|
||||||
{item.product_warehouse?.product?.name}
|
{item.marketing_product?.product_warehouse_data?.product.name}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td className='text-sm px-4 py-3'>Qty</td>
|
<td className='text-sm px-4 py-3'>Qty</td>
|
||||||
<td className='text-sm px-4 py-3'>
|
<td className='text-sm px-4 py-3'>
|
||||||
{item.qty
|
{item.qty
|
||||||
? `${formatNumber(item.qty)} ${item.product_warehouse?.product?.uom?.name ?? ''}`
|
? `${formatNumber(Number(item.qty))} ${item.marketing_product?.product_warehouse_data?.product.uom.name ?? ''}`
|
||||||
: '-'}
|
: '-'}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -272,13 +294,13 @@ const DeliveryOrderProductTable = ({
|
|||||||
<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</td>
|
||||||
<td className='text-sm px-4 py-3'>
|
<td className='text-sm px-4 py-3'>
|
||||||
{formatCurrency(item.unit_price)}
|
{formatCurrency(Number(item.unit_price))}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td className='text-sm px-4 py-3'>Total Penjualan</td>
|
<td className='text-sm px-4 py-3'>Total Penjualan</td>
|
||||||
<td className='text-sm px-4 py-3'>
|
<td className='text-sm px-4 py-3'>
|
||||||
{formatCurrency(item.total_price)}
|
{formatCurrency(Number(item.total_price))}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</>
|
</>
|
||||||
@@ -334,7 +356,9 @@ const DeliveryOrderProductTable = ({
|
|||||||
<div className='size-full flex flex-col relative overflow-x-hidden gap-3'>
|
<div className='size-full flex flex-col relative overflow-x-hidden gap-3'>
|
||||||
{hasDeliveryOrder
|
{hasDeliveryOrder
|
||||||
? deliveryItems.map((item, index) => (
|
? deliveryItems.map((item, index) => (
|
||||||
<div key={`do-table-${item.product_warehouse?.id}-${index}`}>
|
<div
|
||||||
|
key={`do-table-${item.marketing_product?.product_warehouse?.value}-${index}`}
|
||||||
|
>
|
||||||
{formType === 'success' ? (
|
{formType === 'success' ? (
|
||||||
<div className='rounded-lg border border-tools-table-outline border-base-content/5'>
|
<div className='rounded-lg border border-tools-table-outline border-base-content/5'>
|
||||||
<table
|
<table
|
||||||
@@ -350,8 +374,11 @@ const DeliveryOrderProductTable = ({
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Card
|
<Card
|
||||||
key={`do-table-${item.product_warehouse?.id}-${index}`}
|
key={`do-table-${item.marketing_product?.product_warehouse?.value}-${index}`}
|
||||||
title={item.product_warehouse?.product?.name || 'Produk'}
|
title={
|
||||||
|
item.marketing_product?.product_warehouse_data?.product
|
||||||
|
.name || 'Produk'
|
||||||
|
}
|
||||||
collapsible={true}
|
collapsible={true}
|
||||||
defaultCollapsed={false}
|
defaultCollapsed={false}
|
||||||
variant='bordered'
|
variant='bordered'
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import { useUiStore } from '@/stores/ui/ui.store';
|
|||||||
import { usePathname } from 'next/navigation';
|
import { usePathname } from 'next/navigation';
|
||||||
import { Color } from '@/types/theme';
|
import { Color } from '@/types/theme';
|
||||||
import ButtonFilter from '@/components/helper/ButtonFilter';
|
import ButtonFilter from '@/components/helper/ButtonFilter';
|
||||||
|
import Dropdown from '@/components/Dropdown';
|
||||||
|
|
||||||
// ===== STATUS BADGE UTILITIES =====
|
// ===== STATUS BADGE UTILITIES =====
|
||||||
const statusTextMap: Record<string, string> = {
|
const statusTextMap: Record<string, string> = {
|
||||||
@@ -352,6 +353,9 @@ const RecordingTable = () => {
|
|||||||
const [isRejectLoading, setIsRejectLoading] = useState(false);
|
const [isRejectLoading, setIsRejectLoading] = useState(false);
|
||||||
const [, setApprovalNotes] = useState('');
|
const [, setApprovalNotes] = useState('');
|
||||||
|
|
||||||
|
const [isLoadingExportingToExcel, setIsLoadingExportingToExcel] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
const singleDeleteModal = useModal();
|
const singleDeleteModal = useModal();
|
||||||
const approveModal = useModal();
|
const approveModal = useModal();
|
||||||
const rejectModal = useModal();
|
const rejectModal = useModal();
|
||||||
@@ -686,6 +690,14 @@ const RecordingTable = () => {
|
|||||||
});
|
});
|
||||||
}, [selectedRowIds, recordings, isRecordingApproved]);
|
}, [selectedRowIds, recordings, isRecordingApproved]);
|
||||||
|
|
||||||
|
const exportToExcelHandler = async () => {
|
||||||
|
setIsLoadingExportingToExcel(true);
|
||||||
|
|
||||||
|
await RecordingApi.exportToExcel(getTableFilterQueryString());
|
||||||
|
|
||||||
|
setIsLoadingExportingToExcel(false);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isResponseSuccess(recordings) && recordings.data) {
|
if (isResponseSuccess(recordings) && recordings.data) {
|
||||||
const newSelection: Record<string, boolean> = {};
|
const newSelection: Record<string, boolean> = {};
|
||||||
@@ -1313,6 +1325,50 @@ const RecordingTable = () => {
|
|||||||
onClick={handleFilterModalOpen}
|
onClick={handleFilterModalOpen}
|
||||||
className='px-3 py-2.5'
|
className='px-3 py-2.5'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Dropdown
|
||||||
|
align='end'
|
||||||
|
direction='bottom'
|
||||||
|
trigger={
|
||||||
|
<Button
|
||||||
|
variant='outline'
|
||||||
|
color='none'
|
||||||
|
className={cn(
|
||||||
|
'px-3 py-2.5 rounded-lg font-semibold text-sm gap-1.5',
|
||||||
|
'text-sm text-base-content/50 border border-base-content/10 shadow-button-soft'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
icon='heroicons:cloud-arrow-down'
|
||||||
|
/>
|
||||||
|
Export
|
||||||
|
<div className='w-6.5 h-5 flex items-center justify-center border-l border-base-content/10'>
|
||||||
|
<Icon
|
||||||
|
width={14}
|
||||||
|
height={14}
|
||||||
|
icon='heroicons:chevron-down'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
className={{
|
||||||
|
content:
|
||||||
|
'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
color='none'
|
||||||
|
onClick={exportToExcelHandler}
|
||||||
|
isLoading={isLoadingExportingToExcel}
|
||||||
|
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||||
|
>
|
||||||
|
<Icon icon='heroicons:table-cells' width={20} height={20} />
|
||||||
|
Export to Excel
|
||||||
|
</Button>
|
||||||
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -367,6 +367,9 @@ const PurchaseOrderAcceptApprovalForm = ({
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
formik.setFieldValue(`items.${idx}.expedition_vendor_id`, null);
|
formik.setFieldValue(`items.${idx}.expedition_vendor_id`, null);
|
||||||
|
|
||||||
|
formik.setFieldValue(`items.${idx}.transport_per_item`, null);
|
||||||
|
formik.setFieldValue(`items.${idx}.vehicle_number`, null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -553,6 +556,7 @@ const PurchaseOrderAcceptApprovalForm = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
|
disabled={!Boolean(formItem?.expedition_vendor)}
|
||||||
isError={
|
isError={
|
||||||
isRepeaterInputError(idx, 'vehicle_number').isError
|
isRepeaterInputError(idx, 'vehicle_number').isError
|
||||||
}
|
}
|
||||||
@@ -657,6 +661,7 @@ const PurchaseOrderAcceptApprovalForm = ({
|
|||||||
thousandSeparator=','
|
thousandSeparator=','
|
||||||
decimalSeparator='.'
|
decimalSeparator='.'
|
||||||
inputPrefix={'Rp'}
|
inputPrefix={'Rp'}
|
||||||
|
disabled={!Boolean(formItem?.expedition_vendor)}
|
||||||
isError={
|
isError={
|
||||||
isRepeaterInputError(idx, 'transport_per_item')
|
isRepeaterInputError(idx, 'transport_per_item')
|
||||||
.isError
|
.isError
|
||||||
|
|||||||
@@ -185,7 +185,12 @@ const PurchaseAcceptApprovalItemObjectSchema: Yup.ObjectSchema<PurchaseAcceptApp
|
|||||||
.typeError('No. Surat jalan wajib diisi!'),
|
.typeError('No. Surat jalan wajib diisi!'),
|
||||||
vehicle_number: Yup.string()
|
vehicle_number: Yup.string()
|
||||||
.nullable()
|
.nullable()
|
||||||
.optional()
|
.when('expedition_vendor', {
|
||||||
|
is: (expeditionVendor?: { value?: number; label?: string } | null) =>
|
||||||
|
Boolean(expeditionVendor?.value),
|
||||||
|
then: (schema) => schema.required('Nomor kendaraan wajib diisi!'),
|
||||||
|
otherwise: (schema) => schema.optional(),
|
||||||
|
})
|
||||||
.typeError('Nomor kendaraan harus berupa plat nomor!'),
|
.typeError('Nomor kendaraan harus berupa plat nomor!'),
|
||||||
expedition_vendor: Yup.object({
|
expedition_vendor: Yup.object({
|
||||||
value: Yup.number().min(1).required(),
|
value: Yup.number().min(1).required(),
|
||||||
@@ -213,7 +218,13 @@ const PurchaseAcceptApprovalItemObjectSchema: Yup.ObjectSchema<PurchaseAcceptApp
|
|||||||
.typeError('Jumlah diterima harus berupa angka!'),
|
.typeError('Jumlah diterima harus berupa angka!'),
|
||||||
transport_per_item: Yup.mixed<string | number>()
|
transport_per_item: Yup.mixed<string | number>()
|
||||||
.nullable()
|
.nullable()
|
||||||
.optional()
|
.when('expedition_vendor', {
|
||||||
|
is: (expeditionVendor?: { value?: number; label?: string } | null) =>
|
||||||
|
Boolean(expeditionVendor?.value),
|
||||||
|
then: (schema) =>
|
||||||
|
schema.required('Biaya transport per item wajib diisi!'),
|
||||||
|
otherwise: (schema) => schema.optional(),
|
||||||
|
})
|
||||||
.test(
|
.test(
|
||||||
'is-valid-transport-per-item',
|
'is-valid-transport-per-item',
|
||||||
'Biaya transport per item harus berupa angka lebih dari atau sama dengan 0!',
|
'Biaya transport per item harus berupa angka lebih dari atau sama dengan 0!',
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import {
|
|||||||
NextDayRecording,
|
NextDayRecording,
|
||||||
} from '@/types/api/production/recording';
|
} from '@/types/api/production/recording';
|
||||||
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
|
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
|
||||||
|
import { httpClient } from '@/services/http/client';
|
||||||
|
import { formatDate } from '@/lib/helper';
|
||||||
|
|
||||||
export const ProjectFlockKandangApi = new BaseApiService<
|
export const ProjectFlockKandangApi = new BaseApiService<
|
||||||
ProjectFlockKandang,
|
ProjectFlockKandang,
|
||||||
@@ -88,6 +90,30 @@ export class RecordingService extends BaseApiService<
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async exportToExcel(initialQueryString: string) {
|
||||||
|
const params = new URLSearchParams(initialQueryString);
|
||||||
|
|
||||||
|
params.set('export', 'excel');
|
||||||
|
|
||||||
|
const queryString = `?${params.toString()}`;
|
||||||
|
|
||||||
|
const res = await httpClient<Blob>(`${this.basePath}${queryString}`, {
|
||||||
|
method: 'GET',
|
||||||
|
responseType: 'blob',
|
||||||
|
});
|
||||||
|
|
||||||
|
const url = window.URL.createObjectURL(new Blob([res]));
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
|
||||||
|
const fileName = `recording-${formatDate(Date.now(), 'DD-MM-YYYY')}.xlsx`;
|
||||||
|
link.setAttribute('download', fileName);
|
||||||
|
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RecordingApi = new RecordingService('/production/recordings');
|
export const RecordingApi = new RecordingService('/production/recordings');
|
||||||
|
|||||||
@@ -9,6 +9,13 @@ export type RequestOptions<B = unknown> = {
|
|||||||
auth?: AuthMode; // 'cookie' | 'bearer' | 'none'
|
auth?: AuthMode; // 'cookie' | 'bearer' | 'none'
|
||||||
token?: string; // required if auth === 'bearer'
|
token?: string; // required if auth === 'bearer'
|
||||||
timeoutMs?: number;
|
timeoutMs?: number;
|
||||||
|
responseType?:
|
||||||
|
| 'arraybuffer'
|
||||||
|
| 'blob'
|
||||||
|
| 'document'
|
||||||
|
| 'json'
|
||||||
|
| 'text'
|
||||||
|
| 'stream';
|
||||||
};
|
};
|
||||||
|
|
||||||
export class HttpError extends Error {
|
export class HttpError extends Error {
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export async function httpClient<T, B = unknown>(
|
|||||||
data: opts.body,
|
data: opts.body,
|
||||||
timeout: opts.timeoutMs ?? 10_000,
|
timeout: opts.timeoutMs ?? 10_000,
|
||||||
withCredentials: isCookieAuth && !isBearerAuth,
|
withCredentials: isCookieAuth && !isBearerAuth,
|
||||||
|
responseType: opts.responseType,
|
||||||
headers: {
|
headers: {
|
||||||
...(isFormData ? {} : { 'Content-Type': 'application/json' }),
|
...(isFormData ? {} : { 'Content-Type': 'application/json' }),
|
||||||
...(opts.headers ?? {}),
|
...(opts.headers ?? {}),
|
||||||
|
|||||||
Reference in New Issue
Block a user