diff --git a/src/app/marketing/add/delivery-orders/layout.tsx b/src/app/marketing/add/delivery-orders/layout.tsx deleted file mode 100644 index 7220dfa1..00000000 --- a/src/app/marketing/add/delivery-orders/layout.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import SuspenseHelper from '@/components/helper/SuspenseHelper'; - -const Layout = ({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) => { - return {children}; -}; - -export default Layout; diff --git a/src/app/marketing/add/delivery-orders/page.tsx b/src/app/marketing/add/delivery-orders/page.tsx deleted file mode 100644 index 4d92acda..00000000 --- a/src/app/marketing/add/delivery-orders/page.tsx +++ /dev/null @@ -1,54 +0,0 @@ -'use client'; - -import MarketingForm from '@/components/pages/marketing/form/MarketingForm'; -import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; -import { MarketingApi } from '@/services/api/marketing/marketing'; -import { useRouter, useSearchParams } from 'next/navigation'; -import toast from 'react-hot-toast'; -import useSWR from 'swr'; - -const EditMarketingDelivery = () => { - const router = useRouter(); - const searchParams = useSearchParams(); - - const soId = searchParams.get('marketingId'); - - const { - data: marketing, - isLoading: isLoading, - mutate: refreshMarketing, - } = useSWR(`get-so-${soId}`, () => - MarketingApi.getSingle(soId ? parseInt(soId) : 0) - ); - - if (!soId) { - router.back(); - - return ( -
- -
- ); - } - - if (!isLoading && (!marketing || isResponseError(marketing))) { - router.replace('/404'); - return; - } - - return ( -
- {isLoading && } - {!isLoading && isResponseSuccess(marketing) && ( - { - refreshMarketing(); - }} - /> - )} -
- ); -}; -export default EditMarketingDelivery; diff --git a/src/app/marketing/add/sales-orders/page.tsx b/src/app/marketing/add/sales-orders/page.tsx deleted file mode 100644 index 9e33d304..00000000 --- a/src/app/marketing/add/sales-orders/page.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import MarketingForm from '@/components/pages/marketing/form/MarketingForm'; - -const AddSalesOrder = () => { - return ( -
- -
- ); -}; - -export default AddSalesOrder; diff --git a/src/app/marketing/detail/delivery-orders/edit/layout.tsx b/src/app/marketing/detail/delivery-orders/edit/layout.tsx deleted file mode 100644 index 7220dfa1..00000000 --- a/src/app/marketing/detail/delivery-orders/edit/layout.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import SuspenseHelper from '@/components/helper/SuspenseHelper'; - -const Layout = ({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) => { - return {children}; -}; - -export default Layout; diff --git a/src/app/marketing/detail/delivery-orders/edit/page.tsx b/src/app/marketing/detail/delivery-orders/edit/page.tsx deleted file mode 100644 index 32625026..00000000 --- a/src/app/marketing/detail/delivery-orders/edit/page.tsx +++ /dev/null @@ -1,62 +0,0 @@ -'use client'; - -import MarketingForm from '@/components/pages/marketing/form/MarketingForm'; -import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; -import { MarketingApi } from '@/services/api/marketing/marketing'; -import { useRouter, useSearchParams } from 'next/navigation'; -import toast from 'react-hot-toast'; -import useSWR from 'swr'; - -const EditMarketingDelivery = () => { - const router = useRouter(); - const searchParams = useSearchParams(); - - const soId = searchParams.get('marketingId'); - - const { - data: marketing, - isLoading: isLoading, - mutate: refreshMarketing, - } = useSWR(`get-so-${soId}`, () => - MarketingApi.getSingle(soId ? parseInt(soId) : 0) - ); - - if (!soId) { - router.back(); - - return ( -
- -
- ); - } - - if (!isLoading && (!marketing || isResponseError(marketing))) { - router.replace('/404'); - return; - } - - if ( - isResponseSuccess(marketing) && - marketing.data.latest_approval.step_number != 3 - ) { - toast.error('Data Marketing perlu dilakukan approval terlebih dahulu!'); - router.back(); - } - - return ( -
- {isLoading && } - {!isLoading && isResponseSuccess(marketing) && ( - { - refreshMarketing(); - }} - /> - )} -
- ); -}; -export default EditMarketingDelivery; diff --git a/src/app/marketing/detail/layout.tsx b/src/app/marketing/detail/layout.tsx deleted file mode 100644 index 7220dfa1..00000000 --- a/src/app/marketing/detail/layout.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import SuspenseHelper from '@/components/helper/SuspenseHelper'; - -const Layout = ({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) => { - return {children}; -}; - -export default Layout; diff --git a/src/app/marketing/detail/page.tsx b/src/app/marketing/detail/page.tsx deleted file mode 100644 index 902251e8..00000000 --- a/src/app/marketing/detail/page.tsx +++ /dev/null @@ -1,49 +0,0 @@ -'use client'; - -import MarketingDetail from '@/components/pages/marketing/detail/MarketingDetail'; -import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; -import { MarketingApi } from '@/services/api/marketing/marketing'; -import { useRouter, useSearchParams } from 'next/navigation'; -import useSWR from 'swr'; - -const DetailMarketing = () => { - const router = useRouter(); - const searchParams = useSearchParams(); - - const soId = searchParams.get('marketingId'); - - const { - data: marketing, - isLoading: isLoading, - mutate: refreshMarketing, - } = useSWR(soId, (id: number) => MarketingApi.getSingle(id)); - - if (!soId) { - router.back(); - - return ( -
- -
- ); - } - - if (!isLoading && (!marketing || isResponseError(marketing))) { - router.replace('/404'); - return; - } - - return ( -
- {isLoading && } - {!isLoading && isResponseSuccess(marketing) && ( - - )} -
- ); -}; - -export default DetailMarketing; diff --git a/src/app/marketing/detail/sales-orders/edit/layout.tsx b/src/app/marketing/detail/sales-orders/edit/layout.tsx deleted file mode 100644 index 7220dfa1..00000000 --- a/src/app/marketing/detail/sales-orders/edit/layout.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import SuspenseHelper from '@/components/helper/SuspenseHelper'; - -const Layout = ({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) => { - return {children}; -}; - -export default Layout; diff --git a/src/app/marketing/detail/sales-orders/edit/page.tsx b/src/app/marketing/detail/sales-orders/edit/page.tsx deleted file mode 100644 index 19a098c5..00000000 --- a/src/app/marketing/detail/sales-orders/edit/page.tsx +++ /dev/null @@ -1,52 +0,0 @@ -'use client'; - -import MarketingForm from '@/components/pages/marketing/form/MarketingForm'; -import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; -import { MarketingApi } from '@/services/api/marketing/marketing'; -import { useRouter, useSearchParams } from 'next/navigation'; -import useSWR from 'swr'; - -const EditSalesOrder = () => { - const router = useRouter(); - const searchParams = useSearchParams(); - - const soId = searchParams.get('marketingId'); - - const { - data: marketing, - isLoading: isLoading, - mutate: refreshMarketing, - } = useSWR(`get-so-${soId}`, () => - MarketingApi.getSingle(soId ? parseInt(soId) : 0) - ); - - if (!soId) { - router.back(); - - return ( -
- -
- ); - } - - if (!isLoading && (!marketing || isResponseError(marketing))) { - router.replace('/404'); - return; - } - return ( -
- {isLoading && } - {!isLoading && isResponseSuccess(marketing) && ( - { - refreshMarketing(); - }} - /> - )} -
- ); -}; -export default EditSalesOrder; diff --git a/src/components/pages/marketing/DeliveryOrderFormModal.tsx b/src/components/pages/marketing/DeliveryOrderFormModal.tsx index 2cf4ef5c..7c953fe8 100644 --- a/src/components/pages/marketing/DeliveryOrderFormModal.tsx +++ b/src/components/pages/marketing/DeliveryOrderFormModal.tsx @@ -1,16 +1,10 @@ 'use client'; import AlertErrorList from '@/components/helper/form/FormErrors'; -import { useSelect, OptionType } from '@/components/input/SelectInput'; +import { OptionType } from '@/components/input/SelectInput'; import Modal, { useModal } from '@/components/Modal'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; -import { - mergeSOwithDO, - SalesProductToFieldValues, - DeliveryProductToFieldValues, -} from '@/components/pages/marketing/form/MarketingForm'; import { DeliveryOrderProductFormValues } from '@/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.schema'; -import { SalesOrderProductFormValues } from '@/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema'; import { isResponseSuccess, isResponseError } from '@/lib/api-helper'; import { formatCurrency, formatDate, formatTitleCase } from '@/lib/helper'; import { @@ -18,16 +12,13 @@ import { MarketingApi, SalesOrderApi, } from '@/services/api/marketing/marketing'; -import { CustomerApi } from '@/services/api/master-data'; -import { UserApi } from '@/services/api/user'; import { useFormikErrorList } from '@/services/hooks/useFormikErrorList'; -import { BaseApproval, CreatedUser } from '@/types/api/api-general'; +import { BaseApproval } from '@/types/api/api-general'; import { CreateDeliveryOrderPayload, Marketing, UpdateDeliveryOrderPayload, } from '@/types/api/marketing/marketing'; -import { Customer } from '@/types/api/master-data/customer'; import { useFormik } from 'formik'; import { Icon } from '@iconify/react'; import { useRouter, useSearchParams } from 'next/navigation'; @@ -47,6 +38,9 @@ import { DeliveryOrderSchema, getFilledMarketingFormInitialValues, SalesOrderFormValues, + mergeSOwithDO, + SalesProductToFieldValues, + DeliveryProductToFieldValues, } from '@/components/pages/marketing/form/MarketingForm.schema'; import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes'; import RequirePermission from '@/components/helper/RequirePermission'; @@ -116,13 +110,6 @@ const DeliveryOrderFormModal = ({ const formRef = useRef(null); const textareaRef = useRef(null); - const [grandTotal, setGrandTotal] = useState( - isResponseSuccess(marketing) && - marketing?.data.sales_order - ?.map((item) => item.total_price) - .reduce((a, b) => a + b, 0) - ); - const [formErrorMessage, setFormErrorMessage] = useState(null); const [isLoading, setIsLoading] = useState(false); const [selectedDeliveryProduct, setSelectedDeliveryProduct] = @@ -505,6 +492,14 @@ const DeliveryOrderFormModal = ({ formik.setFieldValue('delivery_order', deliveryOrderValues); }, [deliveryOrderValues]); + const grandTotal = useMemo(() => { + return deliveryOrderValues.reduce( + (total, product) => + total + parseFloat((product.total_price as string) || '0'), + 0 + ); + }, [deliveryOrderValues]); + return ( <> )} -
-

+
+

{step == 2 && 'Ubah '} Informasi{' '} {step == 2 ? 'Delivery' : 'Produk'}

{step === 1 && ( - +
+ +
)} {step === 2 && ( void; -}) => { - const router = useRouter(); - const [approvalAction, setApprovalAction] = useState<'APPROVED' | 'REJECTED'>( - 'APPROVED' - ); - const [grandTotal, setGrandTotal] = useState( - initialValues?.sales_order - ?.map((item) => item.total_price) - .reduce((a, b) => a + b, 0) - ); - const [isLoading, setIsLoading] = useState(false); - - const deleteModal = useModal(); - const confirmationModal = useModal(); - const deliveryModal = useModal(); - const { - approvals, - isLoading: isLoadingApproval, - refresh: refreshApproval, - } = useApprovalSteps({ - latestApproval: initialValues?.latest_approval, - approvalLines: MARKETING_APPROVAL_LINE, - moduleName: 'MARKETINGS', - moduleId: initialValues?.id as number as unknown as string, - }); - - const approveClickHandler = () => { - setApprovalAction('APPROVED'); - confirmationModal.openModal(); - }; - - const rejectClickHandler = () => { - setApprovalAction('REJECTED'); - confirmationModal.openModal(); - }; - - const deleteClickHandler = () => { - deleteModal.openModal(); - }; - - const confirmationModalDeleteClickHandler = async () => { - setIsLoading(true); - const res = await MarketingApi.delete(initialValues?.id as number); - deleteModal.closeModal(); - router.push('/marketing'); - toast.success(res?.message as string); - setIsLoading(false); - }; - - const confirmationModalApproveClickHandler = async (notes: string) => { - setIsLoading(true); - const res = await SalesOrderApi.singleApproval( - initialValues?.id as number, - approvalAction, - notes - ); - setIsLoading(false); - confirmationModal.closeModal(); - toast.success(res?.message as string); - refresh?.(); - refreshApproval?.(); - }; - - const confirmationModalDeliveryClickHandler = async (notes: string) => { - setIsLoading(true); - const res = await SalesOrderApi.delivery( - initialValues?.id as number, - notes - ); - setIsLoading(false); - deliveryModal.closeModal(); - toast.success(res?.message as string); - refresh?.(); - refreshApproval?.(); - router.push( - `/marketing/detail/delivery-orders/edit?marketingId=${initialValues?.id}` - ); - }; - - const approval = initialValues?.latest_approval; - const isRejected = approval?.action == 'REJECTED'; - const isApproved = approval?.action == 'APPROVED'; - - return ( - <> -
- 2 ? 'Delivery Order' : 'Sales Order'}`} - backUrl='/marketing' - /> - {!isLoadingApproval && approvals && ( - - )} -
- {initialValues?.latest_approval?.step_number == 1 && ( - <> - - - - - - - - - )} - {initialValues?.latest_approval?.step_number != 1 && ( - <> - - - - - )} -
- - -
- - - - - - - - {Number(initialValues?.latest_approval?.step_number) > 2 && ( - - - - - - )} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {Number(initialValues?.latest_approval?.step_number) > 2 && ( - - - - - - )} - -
- No. Sales Order - : - {initialValues?.so_number} -
- No. Delivery Order - : - {initialValues?.delivery_order - ?.map((item) => item.do_number) - .join(', ')} -
Nama Pelanggan:{initialValues?.customer?.name}
Status: - - - {isRejected - ? 'Ditolak' - : formatTitleCase(approval?.step_name || '')} - -
Tanggal Penjualan:{formatDate(initialValues?.so_date, 'DD MMM yyyy')}
Total Penjualan:{formatCurrency(grandTotal as number)}
Catatan:{initialValues?.notes ?? '-'}
Dokumen Penjualan: - -
Dokumen Pengiriman: - {initialValues?.delivery_order?.map((item, index) => ( - - ))} -
-
-
- {initialValues?.sales_order && ( - - - data={initialValues?.sales_order} - columns={[ - { - header: 'Kandang', - accessorFn(row) { - return row.product_warehouse.warehouse.name; - }, - }, - { - header: 'Produk', - accessorFn(row) { - return row.product_warehouse.product.name; - }, - }, - { - header: 'Harga Satuan (Rp)', - accessorFn(row) { - return formatCurrency(row.unit_price); - }, - }, - { - header: 'Total Bobot (Kg)', - accessorFn(row) { - return formatNumber(row.total_weight); - }, - }, - { - header: 'Kuantitas', - accessorFn(row) { - return formatNumber(row.qty); - }, - }, - { - header: 'Avg. Bobot (Kg)', - accessorFn(row) { - return formatNumber(row.avg_weight); - }, - }, - { - header: 'Total Penjualan (Rp)', - accessorFn(row) { - return formatCurrency(row.total_price); - }, - }, - ]} - className={{ - containerClassName: cn({ - 'mb-20': - initialValues?.sales_order && - initialValues?.sales_order?.length === 0, - }), - tableWrapperClassName: 'overflow-x-auto min-h-full!', - tableClassName: 'font-inter w-full table-auto min-h-full!', - headerRowClassName: 'border-b border-b-gray-200', - headerColumnClassName: - 'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end', - bodyRowClassName: 'border-b border-b-gray-200', - bodyColumnClassName: - 'px-6 py-3 last:flex last:flex-row last:justify-end', - paginationClassName: 'hidden', - }} - /> - - )} - {initialValues?.delivery_order && ( - - {initialValues?.delivery_order.map((delivery, index) => { - return ( -
- -
-
- Nomor DO : {delivery.do_number} -
-
- - data={delivery.deliveries} - columns={[ - { - header: 'Tanggal Pengiriman', - accessorFn() { - return formatDate( - delivery.delivery_date, - 'DD MMM yyyy' - ); - }, - }, - { - header: 'No. Polisi', - accessorFn(row) { - return formatVechicleNumber(row.vehicle_number); - }, - }, - { - header: 'Kandang', - accessorFn(row) { - return row.product_warehouse.warehouse.name; - }, - }, - { - header: 'Produk', - accessorFn(row) { - return row.product_warehouse.product.name; - }, - }, - { - header: 'Harga Satuan (Rp)', - accessorFn(row) { - return formatCurrency(row.unit_price); - }, - }, - { - header: 'Total Bobot (Kg)', - accessorFn(row) { - return formatNumber(row.total_weight); - }, - }, - { - header: 'Kuantitas', - accessorFn(row) { - return formatNumber(row.qty); - }, - }, - { - header: 'Avg. Bobot (Kg)', - accessorFn(row) { - return formatNumber(row.avg_weight); - }, - }, - { - header: 'Total Penjualan (Rp)', - accessorFn(row) { - return formatCurrency(row.total_price); - }, - }, - ]} - className={{ - containerClassName: cn({ - 'mb-20': - initialValues?.sales_order && - initialValues?.sales_order?.length === 0, - }), - tableWrapperClassName: 'overflow-x-auto min-h-full!', - tableClassName: - 'font-inter w-full table-auto min-h-full!', - headerRowClassName: 'border-b border-b-gray-200', - headerColumnClassName: - 'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end', - bodyRowClassName: 'border-b border-b-gray-200', - bodyColumnClassName: - 'px-6 py-3 last:flex last:flex-row last:justify-end', - paginationClassName: 'hidden', - }} - /> -
-
- -
-
- ); - })} -
- )} -
- {initialValues?.latest_approval?.step_number != 3 && ( - <> - - - - - )} - - - -
-
- - - - - ); -}; - -export default MarketingDetail; diff --git a/src/components/pages/marketing/SalesOrderFormModal.tsx b/src/components/pages/marketing/SalesOrderFormModal.tsx index 57f1e18c..66acc440 100644 --- a/src/components/pages/marketing/SalesOrderFormModal.tsx +++ b/src/components/pages/marketing/SalesOrderFormModal.tsx @@ -14,8 +14,6 @@ import { DeliveryProductToFieldValues, mergeSOwithDO, SalesProductToFieldValues, -} from '@/components/pages/marketing/form/MarketingForm'; -import { DeliveryOrderFormValues, DeliveryOrderSchema, getFilledMarketingFormInitialValues, @@ -186,15 +184,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.week?.value ?? undefined, } as CreateSalesOrderProductPayload; }), } as CreateSalesOrderPayload) @@ -282,6 +296,7 @@ const SalesOrderFormModal = ({ // ================== HANDLER ================== const nextButtonHandler = () => { + setSelectedMarketingProduct(null); setStep(step + 1); }; const prevButtonHandler = () => { @@ -375,6 +390,7 @@ const SalesOrderFormModal = ({ } formik.setFieldValue('sales_order', updatedProducts); + console.log(formik.values); nextButtonHandler(); }, [memoSalesOrder, nextButtonHandler] @@ -650,8 +666,9 @@ const SalesOrderFormModal = ({
-
+
void; -}) => { - const router = useRouter(); - const [approvalAction, setApprovalAction] = useState<'APPROVED' | 'REJECTED'>( - 'APPROVED' - ); - const [grandTotal, setGrandTotal] = useState( - initialValues?.sales_order - ?.map((item) => item.total_price) - .reduce((a, b) => a + b, 0) - ); - const [isLoading, setIsLoading] = useState(false); - - const deleteModal = useModal(); - const confirmationModal = useModal(); - const deliveryModal = useModal(); - const { - approvals, - isLoading: isLoadingApproval, - refresh: refreshApproval, - } = useApprovalSteps({ - latestApproval: initialValues?.latest_approval, - approvalLines: MARKETING_APPROVAL_LINE, - moduleName: 'MARKETINGS', - moduleId: initialValues?.id as number as unknown as string, - }); - - const approveClickHandler = () => { - setApprovalAction('APPROVED'); - confirmationModal.openModal(); - }; - - const rejectClickHandler = () => { - setApprovalAction('REJECTED'); - confirmationModal.openModal(); - }; - - const deleteClickHandler = () => { - deleteModal.openModal(); - }; - - const confirmationModalDeleteClickHandler = async () => { - setIsLoading(true); - const res = await MarketingApi.delete(initialValues?.id as number); - deleteModal.closeModal(); - router.push('/marketing'); - toast.success(res?.message as string); - setIsLoading(false); - }; - - const confirmationModalApproveClickHandler = async (notes: string) => { - setIsLoading(true); - const res = await SalesOrderApi.singleApproval( - initialValues?.id as number, - approvalAction, - notes - ); - setIsLoading(false); - confirmationModal.closeModal(); - toast.success(res?.message as string); - refresh?.(); - refreshApproval?.(); - }; - - const confirmationModalDeliveryClickHandler = async (notes: string) => { - setIsLoading(true); - const res = await SalesOrderApi.delivery( - initialValues?.id as number, - notes - ); - setIsLoading(false); - deliveryModal.closeModal(); - toast.success(res?.message as string); - refresh?.(); - refreshApproval?.(); - router.push( - `/marketing/detail/delivery-orders/edit?marketingId=${initialValues?.id}` - ); - }; - - const approval = initialValues?.latest_approval; - const isRejected = approval?.action == 'REJECTED'; - const isApproved = approval?.action == 'APPROVED'; - - return ( - <> -
- 2 ? 'Delivery Order' : 'Sales Order'}`} - backUrl='/marketing' - /> - {!isLoadingApproval && approvals && ( - - )} -
- {initialValues?.latest_approval?.step_number == 1 && ( - <> - - - - - - - - - )} - {initialValues?.latest_approval?.step_number != 1 && ( - <> - - - - - )} -
- - -
- - - - - - - - {Number(initialValues?.latest_approval?.step_number) > 2 && ( - - - - - - )} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {Number(initialValues?.latest_approval?.step_number) > 2 && ( - - - - - - )} - -
- No. Sales Order - : - {initialValues?.so_number} -
- No. Delivery Order - : - {initialValues?.delivery_order - ?.map((item) => item.do_number) - .join(', ')} -
Nama Pelanggan:{initialValues?.customer?.name}
Status: - - - {isRejected - ? 'Ditolak' - : formatTitleCase(approval?.step_name || '')} - -
Tanggal Penjualan:{formatDate(initialValues?.so_date, 'DD MMM yyyy')}
Total Penjualan:{formatCurrency(grandTotal as number)}
Catatan:{initialValues?.notes ?? '-'}
Dokumen Penjualan: - -
Dokumen Pengiriman: - {initialValues?.delivery_order?.map((item, index) => ( - - ))} -
-
-
- {initialValues?.sales_order && ( - - - data={initialValues?.sales_order} - columns={[ - { - header: 'Kandang', - accessorFn(row) { - return row.product_warehouse.warehouse.name; - }, - }, - { - header: 'Produk', - accessorFn(row) { - return row.product_warehouse.product.name; - }, - }, - { - header: 'Harga Satuan (Rp)', - accessorFn(row) { - return formatCurrency(row.unit_price); - }, - }, - { - header: 'Total Bobot (Kg)', - accessorFn(row) { - return formatNumber(row.total_weight); - }, - }, - { - header: 'Kuantitas', - accessorFn(row) { - return formatNumber(row.qty); - }, - }, - { - header: 'Avg. Bobot (Kg)', - accessorFn(row) { - return formatNumber(row.avg_weight); - }, - }, - { - header: 'Total Penjualan (Rp)', - accessorFn(row) { - return formatCurrency(row.total_price); - }, - }, - ]} - className={{ - containerClassName: cn({ - 'mb-20': - initialValues?.sales_order && - initialValues?.sales_order?.length === 0, - }), - tableWrapperClassName: 'overflow-x-auto min-h-full!', - tableClassName: 'font-inter w-full table-auto min-h-full!', - headerRowClassName: 'border-b border-b-gray-200', - headerColumnClassName: - 'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end', - bodyRowClassName: 'border-b border-b-gray-200', - bodyColumnClassName: - 'px-6 py-3 last:flex last:flex-row last:justify-end', - paginationClassName: 'hidden', - }} - /> - - )} - {initialValues?.delivery_order && ( - - {initialValues?.delivery_order.map((delivery, index) => { - return ( -
- -
-
- Nomor DO : {delivery.do_number} -
-
- - data={delivery.deliveries} - columns={[ - { - header: 'Tanggal Pengiriman', - accessorFn() { - return formatDate( - delivery.delivery_date, - 'DD MMM yyyy' - ); - }, - }, - { - header: 'No. Polisi', - accessorFn(row) { - return formatVechicleNumber(row.vehicle_number); - }, - }, - { - header: 'Kandang', - accessorFn(row) { - return row.product_warehouse.warehouse.name; - }, - }, - { - header: 'Produk', - accessorFn(row) { - return row.product_warehouse.product.name; - }, - }, - { - header: 'Harga Satuan (Rp)', - accessorFn(row) { - return formatCurrency(row.unit_price); - }, - }, - { - header: 'Total Bobot (Kg)', - accessorFn(row) { - return formatNumber(row.total_weight); - }, - }, - { - header: 'Kuantitas', - accessorFn(row) { - return formatNumber(row.qty); - }, - }, - { - header: 'Avg. Bobot (Kg)', - accessorFn(row) { - return formatNumber(row.avg_weight); - }, - }, - { - header: 'Total Penjualan (Rp)', - accessorFn(row) { - return formatCurrency(row.total_price); - }, - }, - ]} - className={{ - containerClassName: cn({ - 'mb-20': - initialValues?.sales_order && - initialValues?.sales_order?.length === 0, - }), - tableWrapperClassName: 'overflow-x-auto min-h-full!', - tableClassName: - 'font-inter w-full table-auto min-h-full!', - headerRowClassName: 'border-b border-b-gray-200', - headerColumnClassName: - 'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end', - bodyRowClassName: 'border-b border-b-gray-200', - bodyColumnClassName: - 'px-6 py-3 last:flex last:flex-row last:justify-end', - paginationClassName: 'hidden', - }} - /> -
-
- -
-
- ); - })} -
- )} -
- {initialValues?.latest_approval?.step_number != 3 && ( - <> - - - - - )} - - - -
-
- - - - - ); -}; - -export default MarketingDetail; diff --git a/src/components/pages/marketing/form/MarketingForm.schema.ts b/src/components/pages/marketing/form/MarketingForm.schema.ts index 4a77ebd5..0215217f 100644 --- a/src/components/pages/marketing/form/MarketingForm.schema.ts +++ b/src/components/pages/marketing/form/MarketingForm.schema.ts @@ -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; @@ -94,7 +94,7 @@ export type SalesOrderFormValues = Yup.InferType; export type DeliveryOrderFormValues = Yup.InferType; // ================ Helper Function ================ -const SalesProductToFieldValues = ( +export const SalesProductToFieldValues = ( product: BaseSalesOrder ): SalesOrderProductFormValues => { return { @@ -109,15 +109,37 @@ const SalesProductToFieldValues = ( value: product.product_warehouse.id, label: product.product_warehouse.product.name, }, + product_warehouse_data: product.product_warehouse, product_warehouse_id: product.product_warehouse.id, unit_price: product.unit_price, total_weight: product.total_weight, qty: product.qty, avg_weight: product.avg_weight, total_price: product.total_price, + marketing_type: product.marketing_type + ? { + value: product.marketing_type, + label: formatTitleCase(product.marketing_type), + } + : null, + 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, + weight_per_convertion: product.weight_per_convertion, + uom: product.product_warehouse.product.uom.name, }; }; -const DeliveryProductToFieldValues = ( +export const DeliveryProductToFieldValues = ( salesOrders: BaseSalesOrder[], delivery: BaseDeliveryOrder ): DeliveryOrderProductFormValues[] => { @@ -181,6 +203,24 @@ export const mergeSOwithDO = ( avg_weight: autofill ? so.avg_weight : delivery?.avg_weight, total_price: autofill ? so.total_price : delivery?.total_price, marketing_product: so, // jika ada, override + uom: autofill ? so.uom : delivery?.uom, + weight_per_convertion: autofill + ? so.weight_per_convertion + : delivery?.weight_per_convertion, + price_per_convertion: autofill + ? so.price_per_convertion + : delivery?.price_per_convertion, + convertion_unit: autofill + ? so.convertion_unit + : delivery?.convertion_unit, + marketing_type: autofill ? so.marketing_type : delivery?.marketing_type, + total_peti: autofill ? so.total_peti : delivery?.total_peti, + price_per_qty: autofill ? so.price_per_qty : delivery?.price_per_qty, + sisa_berat: autofill ? so.sisa_berat : delivery?.sisa_berat, + price_sisa_berat: autofill + ? so.price_sisa_berat + : delivery?.price_sisa_berat, + week: autofill ? so.week : delivery?.week, } as DeliveryOrderProductFormValues; }); }; @@ -213,3 +253,11 @@ export const getFilledMarketingFormInitialValues = ( ), }; }; + +export const getPricePerConvertion = ( + totalPrice: number, + weightPerConvertion: number, + totalPeti: number +) => { + return totalPrice / (weightPerConvertion * totalPeti); +}; diff --git a/src/components/pages/marketing/form/MarketingForm.tsx b/src/components/pages/marketing/form/MarketingForm.tsx deleted file mode 100644 index 1f866350..00000000 --- a/src/components/pages/marketing/form/MarketingForm.tsx +++ /dev/null @@ -1,872 +0,0 @@ -'use client'; - -import Button from '@/components/Button'; -import Card from '@/components/Card'; -import { FormHeader } from '@/components/helper/form/FormHeader'; -import DateInput from '@/components/input/DateInput'; -import SelectInput, { - OptionType, - useSelect, -} from '@/components/input/SelectInput'; -import Modal, { useModal } from '@/components/Modal'; -import { formatCurrency, formatDate } from '@/lib/helper'; -import { - BaseDeliveryOrder, - BaseSalesOrder, - CreateDeliveryOrderPayload, - CreateSalesOrderPayload, - CreateSalesOrderProductPayload, - Marketing, - UpdateDeliveryOrderPayload, - UpdateSalesOrderPayload, -} from '@/types/api/marketing/marketing'; -import { Icon } from '@iconify/react'; -import { memo, useCallback, useEffect, useMemo, useState } from 'react'; -import { Customer } from '@/types/api/master-data/customer'; -import { CustomerApi } from '@/services/api/master-data'; -import { useFormik } from 'formik'; -import { - DeliveryOrderFormValues, - DeliveryOrderSchema, - SalesOrderFormValues, - SalesOrderSchema, -} from '@/components/pages/marketing/form/MarketingForm.schema'; -import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; -import { - DeliveryOrderApi, - MarketingApi, - SalesOrderApi, -} from '@/services/api/marketing/marketing'; -import ConfirmationModal from '@/components/modal/ConfirmationModal'; -import toast from 'react-hot-toast'; -import { useRouter } from 'next/navigation'; -import DebouncedTextArea from '@/components/input/DebouncedTextArea'; -import SalesOrderProductForm from '@/components/pages/marketing/form/repeater/sales-order/SalesOrderProductForm'; -import DeliveryOrderProductTable from '@/components/pages/marketing/form/table-view/DeliveryOrderProductTable'; -import DeliveryOrderProductForm from '@/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct'; -import { SalesOrderProductFormValues } from '@/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema'; -import { DeliveryOrderProductFormValues } from '@/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.schema'; -import RequirePermission from '@/components/helper/RequirePermission'; -import AlertErrorList from '@/components/helper/form/FormErrors'; -import { useFormikErrorList } from '@/services/hooks/useFormikErrorList'; -import { CreatedUser } from '@/types/api/api-general'; -import { UserApi } from '@/services/api/user'; - -const MemoizedSalesOrderProductForm = memo(SalesOrderProductForm); -const MemoizedDeliveryOrderProductTable = memo(DeliveryOrderProductTable); -const MemoizedDeliveryOrderProductForm = memo(DeliveryOrderProductForm); - -// ================== EXTERNAL HELPER FUNCTION ================== -export interface ProductCalculationFields { - qty: string | number | undefined; - unit_price: string | number | undefined; - total_price: string | number | undefined; - avg_weight: string | number | undefined; - total_weight: string | number | undefined; -} - -export const SalesProductToFieldValues = ( - product: BaseSalesOrder -): SalesOrderProductFormValues => { - return { - id: product.id, - vehicle_number: product.vehicle_number, - kandang_id: product.product_warehouse.warehouse.id, - kandang: { - value: product.product_warehouse.warehouse.id, - label: product.product_warehouse.warehouse.name, - }, - product_warehouse: { - value: product.product_warehouse.id, - label: product.product_warehouse.product.name, - }, - product_warehouse_id: product.product_warehouse.id, - unit_price: product.unit_price, - total_weight: product.total_weight, - qty: product.qty, - avg_weight: product.avg_weight, - total_price: product.total_price, - }; -}; -export const DeliveryProductToFieldValues = ( - salesOrders: BaseSalesOrder[], - delivery: BaseDeliveryOrder -): DeliveryOrderProductFormValues[] => { - const data = delivery.deliveries.map((item) => { - const soId = salesOrders.find( - (so) => so.product_warehouse.id === item.product_warehouse.id - )?.id; - return { - id: soId, - unit_price: item.unit_price, - total_weight: item.total_weight, - qty: item.qty, - avg_weight: item.avg_weight, - total_price: item.total_price, - vehicle_number: item.vehicle_number, - delivery_date: formatDate(delivery.delivery_date, 'yyyy-MM-DD'), - do_number: delivery.do_number, - marketing_product_id: soId, - marketing_product: { - id: soId, - vehicle_number: item.vehicle_number, - kandang_id: item.product_warehouse.warehouse.id, - kandang: { - value: item.product_warehouse.warehouse.id, - label: item.product_warehouse.warehouse.name, - }, - product_warehouse: { - value: item.product_warehouse.id, - label: item.product_warehouse.product.name, - }, - product_warehouse_id: item.product_warehouse.id, - unit_price: item.unit_price, - total_weight: item.total_weight, - qty: item.qty, - avg_weight: item.avg_weight, - total_price: item.total_price, - }, - } as DeliveryOrderProductFormValues; - }); - return data; -}; -export const mergeSOwithDO = ( - salesOrders: SalesOrderProductFormValues[], - deliveryOrders: DeliveryOrderProductFormValues[], - autofill?: boolean -): DeliveryOrderProductFormValues[] => { - return salesOrders.map((so) => { - const delivery = deliveryOrders.find( - (d) => d?.marketing_product_id === so.id - ); - - return { - ...so, // nilai dasar dari sales order - marketing_product_id: so.id, - delivery_date: delivery?.delivery_date || undefined, - do_number: delivery?.do_number || undefined, - vehicle_number: delivery?.vehicle_number || so.vehicle_number, - unit_price: autofill ? so.unit_price : delivery?.unit_price, - total_weight: autofill ? so.total_weight : delivery?.total_weight, - qty: autofill ? so.qty : delivery?.qty, - avg_weight: autofill ? so.avg_weight : delivery?.avg_weight, - total_price: autofill ? so.total_price : delivery?.total_price, - marketing_product: so, // jika ada, override - } as DeliveryOrderProductFormValues; - }); -}; -export const recalculate = ( - field: string, - values: ProductCalculationFields -) => { - const { qty, unit_price, total_price, avg_weight, total_weight } = values; - const result: Partial = {}; - if (field == 'unit_price' || field == 'total_price' || field == 'qty') { - if (qty && unit_price && (field == 'unit_price' || field == 'qty')) { - result.total_price = Number(qty) * Number(unit_price); - } else if (qty && total_price && field == 'total_price') { - result.unit_price = Number(total_price) / Number(qty); - } - } - if (field == 'avg_weight' || field == 'total_weight' || field == 'qty') { - if (qty && avg_weight && (field == 'avg_weight' || field == 'qty')) { - result.total_weight = Number(qty) * Number(avg_weight); - } else if (qty && total_weight && field == 'total_weight') { - result.avg_weight = Number(total_weight) / Number(qty); - } - } - return result; -}; -export const getSubmitField = (values: ProductCalculationFields) => { - const { qty, unit_price, total_price, avg_weight, total_weight } = values; - - // Harga logic - if (qty && unit_price && !total_price) { - return 'unit_price'; - } - if (qty && total_price && !unit_price) { - return 'total_price'; - } - - // Bobot logic - if (qty && avg_weight && !total_weight) { - return 'avg_weight'; - } - if (qty && total_weight && !avg_weight) { - return 'total_weight'; - } - - // Tidak ada yang perlu dihitung - return ''; -}; - -const MarketingForm = ({ - formType = 'add', - initialValues, - afterSubmit, -}: { - formType?: 'add' | 'edit' | 'add_deliver' | 'edit_deliver'; - initialValues?: Marketing; - afterSubmit?: () => void; -}) => { - const router = useRouter(); - const deleteModal = useModal(); - - const [isLoading, setIsLoading] = useState(false); - const [selectedMarketingProduct, setSelectedMarketingProduct] = - useState(null); - const [selectedDeliveryProduct, setSelectedDeliveryProduct] = - useState(null); - const [deliveryFormState, setDeliveryFormState] = useState<'add' | 'edit'>( - 'add' - ); - const [deliveryOrderValues, setDeliveryOrderValues] = useState< - DeliveryOrderProductFormValues[] - >( - mergeSOwithDO( - initialValues?.sales_order?.map(SalesProductToFieldValues) ?? [], - initialValues?.delivery_order?.flatMap((delivery) => - DeliveryProductToFieldValues(initialValues.sales_order, delivery) - ) ?? [] - ) - ); - - // ================== REPEATER ================== - const addSOModal = useModal(); - const addDOModal = useModal(); - const [rowSOSelection, setRowSOSelection] = useState>( - {} - ); - const selectedRowSOIds = Object.keys(rowSOSelection).map((item) => - parseInt(item) - ); - - // ================== FETCH OPTIONS ================== - const { - options: customerOptions, - isLoadingOptions: isLoadingCustomerOptions, - setInputValue: setInputCustomerValue, - loadMore: loadMoreCustomer, - } = useSelect(CustomerApi.basePath, 'id', 'name'); - const { - options: salesOptions, - isLoadingOptions: isLoadingSalesOptions, - setInputValue: setInputSalesValue, - loadMore: loadMoreSales, - } = useSelect(UserApi.basePath, 'id', 'name'); - - // ================== SETUP FORMIK ================== - const formikInitialValues = useMemo< - SalesOrderFormValues & DeliveryOrderFormValues - >(() => { - return { - so_date: initialValues?.so_date || undefined, - notes: initialValues?.notes || undefined, - customer_id: initialValues?.customer?.id || undefined, - sales_person_id: initialValues?.sales_person?.id || 1, - sales_person: initialValues?.sales_person - ? { - value: initialValues.sales_person.id, - label: initialValues.sales_person.name, - } - : null, - customer: initialValues?.customer - ? { - value: initialValues.customer.id, - label: initialValues.customer.name, - } - : null, - sales_order: - initialValues?.sales_order?.map((product) => - SalesProductToFieldValues(product) - ) ?? [], - delivery_order: mergeSOwithDO( - initialValues?.sales_order?.map(SalesProductToFieldValues) ?? [], - initialValues?.delivery_order?.flatMap((delivery) => - DeliveryProductToFieldValues(initialValues.sales_order, delivery) - ) ?? [] - ), - }; - }, [initialValues]); - const formik = useFormik({ - enableReinitialize: true, - initialValues: formikInitialValues, - validationSchema: - formType == 'add_deliver' || formType == 'edit_deliver' - ? DeliveryOrderSchema - : SalesOrderSchema, - validateOnMount: true, - onSubmit: async (values) => { - const payload = - formType != 'add_deliver' && formType != 'edit_deliver' - ? ({ - customer_id: values.customer_id as number, - sales_person_id: values.sales_person_id as number, - date: formatDate(values.so_date as string, 'yyyy-MM-DD'), - notes: values.notes as string, - marketing_products: values.sales_order.map((product) => { - 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), - } as CreateSalesOrderProductPayload; - }), - } as CreateSalesOrderPayload) - : ({ - marketing_id: initialValues?.id as number, - delivery_products: values.delivery_order - .map((product) => { - if (Boolean(product.delivery_date)) { - return { - marketing_product_id: - product.marketing_product_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), - delivery_date: formatDate( - product.delivery_date as string, - 'yyyy-MM-DD' - ), - vehicle_number: product.vehicle_number, - }; - } - }) - .filter((item) => Boolean(item)), - } as UpdateDeliveryOrderPayload); - switch (formType) { - case 'add': - await createMarketingHandler(payload as CreateSalesOrderPayload); - break; - case 'edit': - await updateMarketingHandler(payload as UpdateSalesOrderPayload); - break; - case 'add_deliver': - await createDeliveryHandler(payload as CreateDeliveryOrderPayload); - break; - case 'edit_deliver': - await updateDeliveryHandler(payload as UpdateDeliveryOrderPayload); - break; - default: - break; - } - afterSubmit?.(); - }, - }); - - const memoSalesOrder = formik.values.sales_order; - - // ================== FORM REPEATER HANDLER ================== - const createMarketingHandler = async (values: CreateSalesOrderPayload) => { - setIsLoading(true); - const createMarketingRes = await SalesOrderApi.create(values); - if (isResponseSuccess(createMarketingRes)) { - toast.success(createMarketingRes?.message as string); - router.push('/marketing'); - } - if (isResponseError(createMarketingRes)) { - toast.error(createMarketingRes?.message as string); - } - setIsLoading(false); - }; - const updateMarketingHandler = async (values: UpdateSalesOrderPayload) => { - setIsLoading(true); - const updateMarketingRes = await SalesOrderApi.update( - initialValues?.id as number, - values - ); - if (isResponseSuccess(updateMarketingRes)) { - toast.success(updateMarketingRes?.message as string); - router.push(`/marketing/detail?marketingId=${initialValues?.id}`); - } - if (isResponseError(updateMarketingRes)) { - toast.error(updateMarketingRes?.message as string); - } - setIsLoading(false); - }; - const createDeliveryHandler = async (values: CreateDeliveryOrderPayload) => { - setIsLoading(true); - const createDeliveryRes = await DeliveryOrderApi.create(values); - if (isResponseSuccess(createDeliveryRes)) { - toast.success(createDeliveryRes?.message as string); - setDeliveryOrderValues( - createDeliveryRes.data?.delivery_order?.flatMap((delivery) => - DeliveryProductToFieldValues( - createDeliveryRes.data?.sales_order, - delivery - ) - ) ?? [] - ); - router.push(`/marketing/detail?marketingId=${initialValues?.id}`); - } - if (isResponseError(createDeliveryRes)) { - toast.error(createDeliveryRes?.message as string); - } - setIsLoading(false); - }; - const updateDeliveryHandler = async (values: UpdateDeliveryOrderPayload) => { - setIsLoading(true); - const updateDeliveryRes = await DeliveryOrderApi.update( - initialValues?.id as number, - values - ); - if (isResponseSuccess(updateDeliveryRes)) { - toast.success(updateDeliveryRes?.message as string); - setDeliveryOrderValues( - mergeSOwithDO( - formik.values.sales_order, - updateDeliveryRes.data?.delivery_order?.flatMap((delivery) => - DeliveryProductToFieldValues( - updateDeliveryRes.data?.sales_order, - delivery - ) - ) ?? [] - ) - ); - router.push(`/marketing/detail?marketingId=${initialValues?.id}`); - } - if (isResponseError(updateDeliveryRes)) { - toast.error(updateDeliveryRes?.message as string); - } - setIsLoading(false); - }; - - // ================== MARKETING HANDLER ================== - const deleteMarketingHandler = async () => { - setIsLoading(true); - const deleteMarketingRes = await MarketingApi.delete( - initialValues?.id as number - ); - if (isResponseSuccess(deleteMarketingRes)) { - toast.success(deleteMarketingRes?.message as string); - } - if (isResponseError(deleteMarketingRes)) { - toast.error(deleteMarketingRes?.message as string); - } - setIsLoading(false); - deleteModal.closeModal(); - router.push('/marketing'); - }; - const handleChangeCustomer = useCallback( - (val: OptionType | OptionType[] | null) => { - formik.setFieldValue('customer_id', (val as OptionType)?.value); - formik.setFieldValue('customer', val as OptionType); - }, - [] - ); - const handleChangeSalesPerson = useCallback( - (val: OptionType | OptionType[] | null) => { - formik.setFieldValue('sales_person_id', (val as OptionType)?.value); - formik.setFieldValue('sales_person', val as OptionType); - }, - [] - ); - const handleDelete = useCallback(() => { - deleteModal.openModal(); - }, [deleteModal]); - - // ================== SALES ORDER HANDLER ================== - const handleDeleteSO = useCallback( - (id: number) => { - const currentProducts = formik.values.sales_order; - formik.setFieldValue( - 'sales_order', - currentProducts.filter((p) => p.id != id) - ); - }, - [memoSalesOrder] - ); - const handleEditSO = useCallback( - (id: number) => { - const currentProducts = formik.values.sales_order; - const selectedProduct = currentProducts.find((p) => p.id == id); - setSelectedMarketingProduct(selectedProduct ?? null); - addSOModal.openModal(); - }, - [memoSalesOrder] - ); - const handleBulkDeleteSO = useCallback(() => { - const currentProducts = formik.values.sales_order; - formik.setFieldValue( - 'sales_order', - currentProducts.filter( - (product) => !selectedRowSOIds.includes(product.id ?? -1) - ) - ); - setRowSOSelection({}); - }, [selectedRowSOIds, memoSalesOrder]); - const handleAddSOClick = useCallback(() => { - setSelectedMarketingProduct(null); - addSOModal.openModal(); - }, [addSOModal]); - const handleAddSubmitSO = useCallback( - async (values: SalesOrderProductFormValues, id?: number) => { - const currentProducts = formik.values.sales_order; - - const newValues = { - ...values, - id: values.id ?? Date.now(), - }; - - let updatedProducts = []; - - if (id) { - // Overwrite - updatedProducts = currentProducts.map((item) => - item.id === id ? newValues : item - ); - } else { - // Add new item - updatedProducts = [...currentProducts, newValues]; - } - - formik.setFieldValue('sales_order', updatedProducts); - - addSOModal.closeModal(); - }, - [addSOModal, memoSalesOrder] - ); - - // ================== DELIVERY ORDER HANDLER ================== - const handleEditDO = useCallback( - (id: number, values?: DeliveryOrderProductFormValues) => { - setDeliveryFormState('edit'); - const currentProducts = formik.values.delivery_order.find( - (product) => product.id == id - ); - setSelectedDeliveryProduct(values ?? currentProducts ?? null); - addDOModal.openModal(); - }, - [addDOModal] - ); - const handleAddDOClick = useCallback(() => { - setDeliveryFormState('add'); - setSelectedDeliveryProduct(null); - addDOModal.openModal(); - }, [addDOModal]); - const handleAddSubmitDO = useCallback( - async (values: DeliveryOrderProductFormValues) => { - const newValues = { - ...values, - id: values.id ?? Date.now(), - }; - - setDeliveryOrderValues((prev) => [...prev, newValues]); - addDOModal.closeModal(); - setSelectedDeliveryProduct(null); - }, - [addDOModal] - ); - const handleUpdateDO = useCallback( - async (id: number, values: DeliveryOrderProductFormValues) => { - setDeliveryOrderValues((prev) => - prev.map((product) => - product.id === id ? { ...product, ...values } : product - ) - ); - addDOModal.closeModal(); - setSelectedDeliveryProduct(null); - }, - [addDOModal] - ); - const handleDeleteDO = useCallback( - async (id: number) => { - setDeliveryOrderValues((prev) => - prev.map((product) => - product.id === id - ? { - ...product, - ...{ - unit_price: '', - total_weight: '', - qty: '', - avg_weight: '', - total_price: '', - delivery_date: '', - }, - } - : product - ) - ); - addDOModal.closeModal(); - setSelectedDeliveryProduct(null); - }, - [addDOModal] - ); - - useEffect(() => { - formik.setFieldValue('delivery_order', deliveryOrderValues); - }, [deliveryOrderValues, initialValues]); - - const grandTotal = useMemo(() => { - return memoSalesOrder.reduce( - (total, product) => - total + parseFloat((product.total_price as string) || '0'), - 0 - ); - }, [memoSalesOrder]); - - // ===== Formik Error List ===== - const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik); - - return ( - <> -
- - {/* Input Cutomer And Date */} - -
- - -
-
- - {/* Input Table Repeater Sales Order */} - - {/* */} - - - {/* Input Table Repeater Delivery Order */} - {(formType == 'add_deliver' || formType == 'edit_deliver') && - initialValues?.sales_order && - initialValues?.sales_order.length > 0 && ( - - - - )} - - {/* Input Notes */} -
-
- - -
-
- Total Penjualan - - {formatCurrency(grandTotal)}{' '} - -
-
- - - - {/* Form Actions */} -
- - -
- - - {/* Actions button */} - {formType == 'edit' && ( -
- - - -
- )} - - {/* Modals */} - -
-
-

Tambah Produk

- -
-
- -
-
-
- -
-
-

- {selectedDeliveryProduct ? 'Edit' : 'Tambah'} Pengiriman -

- -
-
- -
-
-
- - - ); -}; - -export default MarketingForm; diff --git a/src/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.schema.ts b/src/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.schema.ts index 1fc4c7c0..4c20f05b 100644 --- a/src/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.schema.ts +++ b/src/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.schema.ts @@ -13,6 +13,30 @@ type DeliveryOrderProductSchemaType = { vehicle_number: string | undefined; delivery_date: string | undefined | null; do_number?: string | undefined | null; // Uncertain + uom?: string | null | undefined; + convertion_unit?: { + value: string; + label: string; + } | null; + weight_per_convertion?: number | null | undefined; + price_per_convertion?: number | null | undefined; + marketing_type?: { + value: string; + label: string; + } | null; + 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; + /** Week untuk ayam pullet */ + week?: + | { + value?: number; + label?: string; + } + | null + | undefined; }; export const DeliveryOrderProductSchema: Yup.ObjectSchema = @@ -40,6 +64,43 @@ export const DeliveryOrderProductSchema: Yup.ObjectSchema + 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 DeliveryOrderProductFormValues = Yup.InferType< diff --git a/src/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.tsx b/src/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.tsx index 9e735a95..850d88d2 100644 --- a/src/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.tsx +++ b/src/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { DeliveryOrderProductFormValues, DeliveryOrderProductSchema, @@ -8,10 +8,10 @@ import Alert from '@/components/Alert'; import Button from '@/components/Button'; import NumberInput from '@/components/input/NumberInput'; import PatternInput from '@/components/input/PatternInput'; -import { formatVechicleNumber } from '@/lib/helper'; +import { formatTitleCase, formatVechicleNumber } from '@/lib/helper'; import DateInput from '@/components/input/DateInput'; import { BaseSalesOrder } from '@/types/api/marketing/marketing'; -import { SalesProductToFieldValues } from '@/components/pages/marketing/form/MarketingForm'; +import { SalesProductToFieldValues } from '@/components/pages/marketing/form/MarketingForm.schema'; import * as Yup from 'yup'; import { isResponseSuccess } from '@/lib/api-helper'; import AlertErrorList from '@/components/helper/form/FormErrors'; @@ -21,9 +21,13 @@ import { ProductApi } from '@/services/api/master-data'; import StatusBadge from '@/components/helper/StatusBadge'; import SelectInputRadio from '@/components/input/SelectInputRadio'; import { OptionType } from '@/components/input/SelectInput'; - -const roundWeight = (value: number) => Number(value.toFixed(2)); -const roundPrice = (value: number) => Math.round(value); +import { + MARKETING_CONVERTION_UNIT_OPTIONS, + MARKETING_TYPE_OPTIONS, +} from '@/config/constant'; +import Dropdown from '@/components/Dropdown'; +import { Icon } from '@iconify/react'; +import { handleMarketingCalculation } from '@/lib/marketing-calculation'; const DeliveryOrderProductForm = ({ formState, @@ -49,6 +53,35 @@ const DeliveryOrderProductForm = ({ ); const [currentInput, setCurrentInput] = useState(''); + // Check jika ada sisa berat = total_weight - (weight_per_convertion * total_peti) + const initialSisaBerat = + initialValues?.total_weight && + initialValues?.weight_per_convertion && + initialValues?.total_peti + ? Number(initialValues.total_weight) - + Number(initialValues.weight_per_convertion) * + Number(initialValues.total_peti) + : 0; + + const initialPricePerConvertion = + initialValues?.total_price && + initialValues?.total_peti && + Number(initialValues.total_peti) !== 0 + ? (Number(initialValues.total_price) - + initialSisaBerat * Number(initialValues.unit_price || 0)) / + Number(initialValues.total_peti) + : 0; + + const initialPriceSisaBerat = + initialValues?.total_price && initialValues?.total_peti + ? Number(initialValues.total_price) - + initialPricePerConvertion * Number(initialValues.total_peti) + : 0; + + const [hasSisaBerat, setHasSisaBerat] = useState( + initialSisaBerat > 0 + ); + // ============ Fetch Data ============ const { data: productData } = useSWR( selectedProduct?.value @@ -60,6 +93,27 @@ const DeliveryOrderProductForm = ({ : undefined ); + // Options Week dari minggu 1 - 22 + const optionsWeek = useMemo(() => { + return Array.from({ length: 22 }, (_, i) => ({ + value: i + 1, + label: `Week ${i + 1}`, + })); + }, []); + + const options = exisitingValues + ?.map((item) => { + if (!Boolean(item.qty)) { + return { + value: item.id, + label: `${item.marketing_product?.product_warehouse?.label} - ${item.marketing_product?.kandang?.label}`, + } as OptionType; + } else { + return null; + } + }) + ?.filter((item) => item != null) as OptionType[]; + const salesOrder = salesOrders.find( (item) => item.id === initialValues?.marketing_product_id ); @@ -77,6 +131,19 @@ const DeliveryOrderProductForm = ({ avg_weight: initialValues?.avg_weight || undefined, total_price: initialValues?.total_price || undefined, marketing_product: initialValues?.marketing_product || undefined, + uom: initialValues?.uom || '', + weight_per_convertion: + initialValues?.weight_per_convertion != null + ? Number(initialValues.weight_per_convertion) + : null, + price_per_convertion: initialPricePerConvertion, + convertion_unit: initialValues?.convertion_unit || null, + marketing_type: initialValues?.marketing_type || null, + total_peti: initialValues?.total_peti ?? null, + price_per_qty: initialValues?.price_per_qty ?? null, + sisa_berat: initialSisaBerat, + price_sisa_berat: initialPriceSisaBerat, + week: initialValues?.week ?? null, }, isInitialValid: false, validationSchema: Yup.object().shape({ @@ -124,6 +191,16 @@ const DeliveryOrderProductForm = ({ avg_weight: '', total_price: '', marketing_product: undefined, + total_peti: null, + price_per_qty: null, + price_sisa_berat: null, + sisa_berat: null, + convertion_unit: null, + marketing_type: null, + weight_per_convertion: null, + price_per_convertion: null, + uom: '', + week: null, }, }); // setSelectedProduct(null); @@ -132,94 +209,34 @@ const DeliveryOrderProductForm = ({ 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; - - switch (field) { - // ===== SOURCE FIELDS ===== - case 'qty': { - if (avgWeight > 0) { - const tw = roundWeight(qty * avgWeight); - formik.setFieldValue('total_weight', tw); - - // Hitung total_price berdasarkan unit_price × total_weight - if (unitPrice > 0) { - 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 unit_price × total_weight - if (unitPrice > 0) { - formik.setFieldValue('total_price', roundPrice(unitPrice * tw)); - } - } - break; - } - - case 'unit_price': { - if (unitPrice > 0 && totalWeight > 0) { - // Hitung total_price berdasarkan 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 unit_price × total_weight - if (unitPrice > 0) { - formik.setFieldValue( - 'total_price', - roundPrice(unitPrice * totalWeight) - ); - } - } - break; - } - - case 'total_price': { - if (totalPrice > 0 && totalWeight > 0) { - // Hitung unit_price berdasarkan total_price / total_weight - formik.setFieldValue( - 'unit_price', - roundPrice(totalPrice / totalWeight) - ); - } - break; - } - } + handleMarketingCalculation(field, { + values: formik.values, + setFieldValue: formik.setFieldValue, + hasSisaBerat, + }); }; - const options = exisitingValues - ?.map((item) => { - if (!Boolean(item.qty)) { - return { - value: item.id, - label: `${item.marketing_product?.product_warehouse?.label} - ${item.marketing_product?.kandang?.label}`, - } as OptionType; - } else { - return null; - } - }) - ?.filter((item) => item != null) as OptionType[]; + // Handler khusus untuk toggle sisa berat - langsung pakai nilai baru + const handleSisaBeratToggle = (newHasSisaBerat: boolean) => { + setHasSisaBerat(newHasSisaBerat); + + if (!newHasSisaBerat) { + // Ketika OFF - set nilai ke 0 dan recalculate tanpa sisa + formik.setFieldValue('sisa_berat', 0); + formik.setFieldValue('price_sisa_berat', 0); + } + + // Langsung trigger recalculation dengan hasSisaBerat yang baru + handleMarketingCalculation('total_peti', { + values: { + ...formik.values, + sisa_berat: newHasSisaBerat ? formik.values.sisa_berat : 0, + price_sisa_berat: newHasSisaBerat ? formik.values.price_sisa_berat : 0, + }, + setFieldValue: formik.setFieldValue, + hasSisaBerat: newHasSisaBerat, + }); + }; const { setValues: setFormikValues } = formik; @@ -229,9 +246,6 @@ const DeliveryOrderProductForm = ({ handleResetForm(); } else { setFormikValues(initialValues); - // const value = exisitingValues?.find( - // (item) => item.id === initialValues?.id - // ); if (initialValues?.marketing_product_id) { setSelectedProduct({ value: initialValues?.id, @@ -243,7 +257,23 @@ const DeliveryOrderProductForm = ({ }, [initialValues]); // ===== Formik Error List ===== - const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik); + const { formErrorList, close, handleFormSubmit } = useFormikErrorList( + formik, + { + onBeforeSubmit(e) { + e.preventDefault(); + handleBlurField(currentInput); + formik.setFieldValue( + 'uom', + isResponseSuccess(productData) ? productData?.data?.uom?.name : '' + ); + }, + } + ); + + useEffect(() => { + handleBlurField('week'); + }, [formik.values.week]); return ( <> @@ -252,214 +282,514 @@ const DeliveryOrderProductForm = ({ onSubmit={handleFormSubmit} onReset={handleResetForm} > - {formikErrorMessage && ( -
setFormErrorMessage('')} className='my-3 w-full'> - {formikErrorMessage} -
- )} - - item.id === selectedProduct?.value - )?.marketing_product?.product_warehouse?.label, - } as OptionType) - : null - } - onChange={(value) => { - const selected = value as OptionType; - setSelectedProduct(selected); +
+ {formikErrorMessage && ( +
setFormErrorMessage('')} + className='my-3 w-full' + > + {formikErrorMessage} +
+ )} + + {/* Tanggal Pengiriman */} + + + {/* No. Polisi */} + + + {/* Produk */} + item.id === selectedProduct?.value + )?.marketing_product?.product_warehouse?.label, + } as OptionType) + : null + } + onChange={(value) => { + const selected = value as OptionType; + setSelectedProduct(selected); + + const so = salesOrders?.find( + (item) => item.id === selected?.value + ); + if (!so) { + formik.setValues({ + ...formik.values, + marketing_product_id: undefined, + marketing_product: null, + qty: '', + unit_price: '', + total_price: '', + avg_weight: '', + total_weight: '', + vehicle_number: '', + }); + return; + } - const so = salesOrders?.find((item) => item.id === selected?.value); - if (!so) { formik.setValues({ ...formik.values, - marketing_product_id: undefined, - marketing_product: null, - qty: '', - unit_price: '', - total_price: '', - avg_weight: '', - total_weight: '', - vehicle_number: '', + marketing_product_id: selected.value as number, + marketing_product: SalesProductToFieldValues(so), + qty: so.qty, + unit_price: so.unit_price, + total_price: so.total_price, + avg_weight: so.avg_weight, + total_weight: so.total_weight, + vehicle_number: so.vehicle_number, }); - return; + }} + startAdornment={ + selectedProduct && ( + item.id === selectedProduct?.value + )?.marketing_product?.kandang?.label ?? '' + } + color='success' + className={{ + badge: 'whitespace-nowrap w-fit font-semibold', + }} + /> + ) } + isClearable + isError={Boolean(formik.errors.marketing_product_id)} + errorMessage={formik.errors.marketing_product_id} + required + /> - formik.setValues({ - ...formik.values, - marketing_product_id: selected.value as number, - marketing_product: SalesProductToFieldValues(so), - qty: so.qty, - unit_price: so.unit_price, - total_price: so.total_price, - avg_weight: so.avg_weight, - total_weight: so.total_weight, - vehicle_number: so.vehicle_number, - }); - }} - startAdornment={ - selectedProduct && ( - item.id === selectedProduct?.value - )?.marketing_product?.kandang?.label ?? '' - } - color='success' - className={{ - badge: 'whitespace-nowrap w-fit font-semibold', - }} + {/* Kategori */} + { + formik.setFieldValue('marketing_type', val); + }} + isClearable + placeholder='Pilih Kategori' + isDisabled + /> + + {/* Konversi Satuan Telur */} + {formik.values.marketing_type && + formik.values.marketing_type.value.toLowerCase() === 'telur' && + (!formik.values.convertion_unit || + formik.values.convertion_unit.value.toLowerCase() !== 'peti') && ( + formik.setFieldValue('convertion_unit', val)} + isClearable + placeholder='Pilih Konversi Satuan' /> - ) - } - isClearable - isError={Boolean(formik.errors.marketing_product_id)} - errorMessage={formik.errors.marketing_product_id} - required - /> + )} + {formik.values.convertion_unit && + formik.values.convertion_unit.value.toLowerCase() === 'peti' && ( +
+ +
+
+ +
+ {formatTitleCase( + formik.values.convertion_unit.value + )} + +
+
+
+ } + className={{ + wrapper: 'relative', + content: + 'rounded-xl mt-1 border border-base-content/5 shadow-sm overflow-hidden min-w-68.5 sm:min-w-103.25 w-full', + }} + > +
    + {MARKETING_CONVERTION_UNIT_OPTIONS.map((option) => ( +
  • + +
  • + ))} +
+ +
+ { + formik.setFieldValue( + 'weight_per_convertion', + Number(e.target.value) + ); + setCurrentInput(e.target.name); + }} + onBlur={() => handleBlurField('weight_per_convertion')} + /> +
+
+ )} - - { - formik.handleChange(e); - setCurrentInput(e.target.name); - }} - onBlur={() => handleBlurField('qty')} - isError={Boolean(formik.errors.qty)} - errorMessage={formik.errors.qty} - placeholder='Masukan Kuantitas' - endAdornment={ -
- - {isResponseSuccess(productData) - ? productData?.data?.uom.name - : ''} + {/* Konversi Satuan Week Pullet */} + {formik.values.marketing_type?.value.toLowerCase() === + 'ayam_pullet' && ( + { + formik.setFieldValue('week', val); + }} + placeholder='Pilih Week' + /> + )} + + {/* Total Peti */} + {formik.values.convertion_unit?.value.toLowerCase() === 'peti' && ( + { + formik.handleChange(e); + setCurrentInput(e.target.name); + }} + onBlur={() => handleBlurField('total_peti')} + isError={ + formik.touched.total_peti && Boolean(formik.errors.total_peti) + } + errorMessage={formik.errors.total_peti} + placeholder='Masukan Total Peti' + endAdornment={ +
+ Kg +
+ } + bottomLabel={`1 ${formik.values.convertion_unit?.value.toLowerCase()} = ${formik.values.weight_per_convertion ?? 0} Kg`} + /> + )} + + {/* Avg. Bobot */} + {formik.values.marketing_type?.value.toLowerCase() === 'trading' || + (formik.values.convertion_unit?.value.toLowerCase() !== 'peti' && + formik.values.convertion_unit?.value.toLowerCase() !== 'kg' && ( + { + formik.handleChange(e); + setCurrentInput(e.target.name); + }} + onBlur={() => handleBlurField('avg_weight')} + isError={ + formik.touched.avg_weight && + Boolean(formik.errors.avg_weight) + } + errorMessage={formik.errors.avg_weight} + placeholder='Masukan Bobot Rata-rata' + /> + ))} + + {/* Total Bobot */} + {formik.values.marketing_type?.value.toLowerCase() !== 'trading' && ( + { + formik.handleChange(e); + setCurrentInput(e.target.name); + }} + onBlur={() => handleBlurField('total_weight')} + isError={ + formik.touched.total_weight && + Boolean(formik.errors.total_weight) + } + errorMessage={formik.errors.total_weight} + placeholder='Masukan Total Bobot' + /> + )} + + {/* Kuantitas */} + { + formik.handleChange(e); + setCurrentInput(e.target.name); + }} + onBlur={() => handleBlurField('qty')} + isError={Boolean(formik.errors.qty)} + errorMessage={formik.errors.qty} + placeholder='Masukan Kuantitas' + endAdornment={ +
+ + {isResponseSuccess(productData) + ? productData?.data?.uom.name + : ''} + +
+ } + bottomLabel={ + formik.values.marketing_product_id + ? 'Stok dijual: ' + + salesOrders?.find( + (item) => item.id === formik.values.marketing_product_id + )?.qty + + ' ' + + (isResponseSuccess(productData) + ? productData?.data?.uom.name + : '') + : '' + } + /> + + {/* Harga per convertion unit (PETI / KG) */} + {(formik.values.convertion_unit?.value.toLowerCase() === 'peti' || + formik.values.convertion_unit?.value.toLowerCase() === 'kg') && ( + { + formik.handleChange(e); + setCurrentInput(e.target.name); + }} + onBlur={() => handleBlurField('price_per_convertion')} + isError={ + formik.touched.price_per_convertion && + Boolean(formik.errors.price_per_convertion) + } + errorMessage={formik.errors.price_per_convertion} + placeholder='Masukan Harga Satuan' + /> + )} + + {/* Harga per butir untuk TELUR + QTY */} + {formik.values.marketing_type?.value.toLowerCase() === 'telur' && + formik.values.convertion_unit?.value.toLowerCase() === 'qty' && ( + { + 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 */} + {formik.values.convertion_unit?.value.toLowerCase() !== 'peti' && + formik.values.convertion_unit?.value.toLowerCase() !== 'kg' && ( + { + formik.handleChange(e); + setCurrentInput(e.target.name); + }} + onBlur={() => handleBlurField('unit_price')} + isError={Boolean(formik.errors.unit_price)} + errorMessage={formik.errors.unit_price} + placeholder='Masukan Harga Satuan' + /> + )} + + {/* Sisa kg diluar peti */} + {formik.values.convertion_unit?.value.toLowerCase() === 'peti' && ( +
+
+ handleSisaBeratToggle(!hasSisaBerat)} + className='toggle toggle-primary rounded-full before:rounded-full before:bg-base-content/50 border-base-content/50 checked:border-primary checked:bg-primary checked:before:bg-base-100' + /> + +
+ + Jika ada, masukan berat di luar peti
- } - bottomLabel={ - formik.values.marketing_product_id - ? 'Stok dijual: ' + - salesOrders?.find( - (item) => item.id === formik.values.marketing_product_id - )?.qty + - ' ' + - (isResponseSuccess(productData) - ? productData?.data?.uom.name - : '') - : '' - } - /> - { - formik.handleChange(e); - setCurrentInput(e.target.name); - }} - onBlur={() => handleBlurField('unit_price')} - isError={Boolean(formik.errors.unit_price)} - errorMessage={formik.errors.unit_price} - placeholder='Masukan Harga Satuan' - /> - { - formik.handleChange(e); - setCurrentInput(e.target.name); - }} - onBlur={() => handleBlurField('avg_weight')} - isError={Boolean(formik.errors.avg_weight)} - errorMessage={formik.errors.avg_weight} - placeholder='Masukan Bobot Rata-rata' - /> - { - formik.handleChange(e); - setCurrentInput(e.target.name); - }} - onBlur={() => handleBlurField('total_weight')} - isError={Boolean(formik.errors.total_weight)} - errorMessage={formik.errors.total_weight} - placeholder='Masukan Total Bobot' - /> + )} - { - formik.handleChange(e); - setCurrentInput(e.target.name); - }} - onBlur={() => handleBlurField('total_price')} - isError={Boolean(formik.errors.total_price)} - errorMessage={formik.errors.total_price} - placeholder='Masukan Total Penjualan' - /> + {hasSisaBerat && ( + <> + { + formik.handleChange(e); + setCurrentInput(e.target.name); + }} + onBlur={() => handleBlurField('sisa_berat')} + isError={ + formik.touched.sisa_berat && Boolean(formik.errors.sisa_berat) + } + errorMessage={formik.errors.sisa_berat} + placeholder='Masukan Sisa Berat' + /> + { + formik.handleChange(e); + setCurrentInput(e.target.name); + }} + onBlur={() => handleBlurField('price_sisa_berat')} + isError={ + formik.touched.price_sisa_berat && + Boolean(formik.errors.price_sisa_berat) + } + errorMessage={formik.errors.price_sisa_berat} + placeholder='Masukan Harga Sisa Berat' + /> + + )} - + {/* Total Penjualan */} + { + formik.handleChange(e); + setCurrentInput(e.target.name); + }} + onBlur={() => handleBlurField('total_price')} + isError={ + formik.touched.total_price && Boolean(formik.errors.total_price) + } + errorMessage={formik.errors.total_price} + placeholder='Masukan Total Penjualan' + /> + +
-
+
+ + ))} + + +
+ { + formik.setFieldValue( + 'weight_per_convertion', + Number(e.target.value) + ); + setCurrentInput(e.target.name); + }} + onBlur={() => handleBlurField('weight_per_convertion')} + /> +
+
+ )} + + {/* Konversi Satuan Week Pullet */} + {formik.values.marketing_type?.value.toLowerCase() === + 'ayam_pullet' && ( + { + formik.setFieldValue('week', val); + }} + placeholder='Pilih Week' + /> + )} + + {/* Total Peti */} + {formik.values.convertion_unit?.value.toLowerCase() === 'peti' && ( + { + formik.handleChange(e); + setCurrentInput(e.target.name); + }} + onBlur={() => handleBlurField('total_peti')} + isError={ + formik.touched.total_peti && Boolean(formik.errors.total_peti) + } + errorMessage={formik.errors.total_peti} + placeholder='Masukan Total Peti' + endAdornment={ +
+ Kg +
+ } + bottomLabel={`1 ${formik.values.convertion_unit?.value.toLowerCase()} = ${formik.values.weight_per_convertion ?? 0} Kg`} + /> + )} + + {/* Avg. Bobot */} + {formik.values.marketing_type?.value.toLowerCase() === 'trading' || + (formik.values.convertion_unit?.value.toLowerCase() !== 'peti' && + formik.values.convertion_unit?.value.toLowerCase() !== 'kg' && ( + { + formik.handleChange(e); + setCurrentInput(e.target.name); + }} + onBlur={() => handleBlurField('avg_weight')} + isError={ + formik.touched.avg_weight && + Boolean(formik.errors.avg_weight) + } + errorMessage={formik.errors.avg_weight} + placeholder='Masukan Bobot Rata-rata' + /> + ))} + + {/* Total Bobot */} + {formik.values.marketing_type?.value.toLowerCase() !== 'trading' && ( + { + formik.handleChange(e); + setCurrentInput(e.target.name); + }} + onBlur={() => handleBlurField('total_weight')} + isError={ + formik.touched.total_weight && + Boolean(formik.errors.total_weight) + } + errorMessage={formik.errors.total_weight} + placeholder='Masukan Total Bobot' + /> + )} + + {/* Kuantitas */} + { + formik.handleChange(e); + setCurrentInput(e.target.name); + }} + onBlur={() => handleBlurField('qty')} + isError={formik.touched.qty && Boolean(formik.errors.qty)} + errorMessage={formik.errors.qty} + placeholder='Masukan Kuantitas' + endAdornment={ + formik.values.uom ? ( +
+ + {formik.values.uom} + +
+ ) : undefined + } + bottomLabel={ + isResponseSuccess(warehouseSourceRawData) && + formik.values.product_warehouse_id + ? `Stok tersedia: ${formatNumber( + warehouseSourceRawData?.data?.find( + (item) => item.id === formik.values.product_warehouse_id + )?.quantity ?? 0 + )} ${formik.values.uom}` + : '' + } + /> + + {/* Harga per convertion unit (PETI / KG) */} + {(formik.values.convertion_unit?.value.toLowerCase() === 'peti' || + formik.values.convertion_unit?.value.toLowerCase() === 'kg') && ( + { + formik.handleChange(e); + setCurrentInput(e.target.name); + }} + onBlur={() => handleBlurField('price_per_convertion')} + isError={ + formik.touched.price_per_convertion && + Boolean(formik.errors.price_per_convertion) + } + errorMessage={formik.errors.price_per_convertion} + placeholder='Masukan Harga Satuan' + /> + )} + + {/* Harga per butir untuk TELUR + QTY */} + {formik.values.marketing_type?.value.toLowerCase() === 'telur' && + formik.values.convertion_unit?.value.toLowerCase() === 'qty' && ( + { + 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' && ( + { + formik.handleChange(e); + setCurrentInput(e.target.name); + }} + onBlur={() => handleBlurField('unit_price')} + isError={ + formik.touched.unit_price && Boolean(formik.errors.unit_price) + } + errorMessage={formik.errors.unit_price} + placeholder='Masukan Harga Satuan' + /> + )} + + {/* Sisa kg diluar peti */} + {formik.values.convertion_unit?.value.toLowerCase() === 'peti' && ( +
+
+ handleSisaBeratToggle(!hasSisaBerat)} + className='toggle toggle-primary rounded-full before:rounded-full before:bg-base-content/50 border-base-content/50 checked:border-primary checked:bg-primary checked:before:bg-base-100' + /> + +
+ + Jika ada, masukan berat di luar peti
- } - bottomLabel={ - isResponseSuccess(warehouseSourceRawData) && - formik.values.product_warehouse_id - ? `Stok tersedia: ${formatNumber( - warehouseSourceRawData?.data?.find( - (item) => item.id === formik.values.product_warehouse_id - )?.quantity ?? 0 - )} ${selectedProductWarehouse?.product?.uom?.name}` - : '' - } - /> - { - formik.handleChange(e); - setCurrentInput(e.target.name); - }} - onBlur={() => handleBlurField('unit_price')} - isError={ - formik.touched.unit_price && Boolean(formik.errors.unit_price) - } - errorMessage={formik.errors.unit_price} - placeholder='Masukan Harga Satuan' - /> - { - formik.handleChange(e); - setCurrentInput(e.target.name); - }} - onBlur={() => handleBlurField('avg_weight')} - isError={ - formik.touched.avg_weight && Boolean(formik.errors.avg_weight) - } - errorMessage={formik.errors.avg_weight} - placeholder='Masukan Bobot Rata-rata' - /> - { - formik.handleChange(e); - setCurrentInput(e.target.name); - }} - onBlur={() => handleBlurField('total_weight')} - isError={ - formik.touched.total_weight && Boolean(formik.errors.total_weight) - } - errorMessage={formik.errors.total_weight} - placeholder='Masukan Total Bobot' - /> - { - formik.handleChange(e); - setCurrentInput(e.target.name); - }} - onBlur={() => handleBlurField('total_price')} - isError={ - formik.touched.total_price && Boolean(formik.errors.total_price) - } - errorMessage={formik.errors.total_price} - placeholder='Masukan Total Penjualan' - /> + )} -
- + {hasSisaBerat && ( + <> + { + formik.handleChange(e); + setCurrentInput(e.target.name); + }} + onBlur={() => handleBlurField('sisa_berat')} + isError={ + formik.touched.sisa_berat && Boolean(formik.errors.sisa_berat) + } + errorMessage={formik.errors.sisa_berat} + placeholder='Masukan Sisa Berat' + /> + { + formik.handleChange(e); + setCurrentInput(e.target.name); + }} + onBlur={() => handleBlurField('price_sisa_berat')} + isError={ + formik.touched.price_sisa_berat && + Boolean(formik.errors.price_sisa_berat) + } + errorMessage={formik.errors.price_sisa_berat} + placeholder='Masukan Harga Sisa Berat' + /> + + )} + + {/* Total Penjualan */} + { + formik.handleChange(e); + setCurrentInput(e.target.name); + }} + onBlur={() => handleBlurField('total_price')} + isError={ + formik.touched.total_price && Boolean(formik.errors.total_price) + } + errorMessage={formik.errors.total_price} + placeholder='Masukan Total Penjualan' + /> + + {formErrorList.length > 0 && ( +
+ +
+ )}
-
+