From 7ceb25ea71dc421b68bcc24a7d006a16e23c52ee Mon Sep 17 00:00:00 2001 From: rstubryan Date: Wed, 8 Oct 2025 15:26:45 +0700 Subject: [PATCH] feat(FE-62,65): add inventory movement management with API and form validation --- .../movement/form/MovementForm.schema.ts | 67 +++++ src/config/constant.ts | 241 ++++++++++-------- src/services/api/inventory.ts | 12 + src/types/api/inventory/movement.d.ts | 51 ++++ 4 files changed, 262 insertions(+), 109 deletions(-) create mode 100644 src/components/pages/inventory/movement/form/MovementForm.schema.ts create mode 100644 src/services/api/inventory.ts create mode 100644 src/types/api/inventory/movement.d.ts diff --git a/src/components/pages/inventory/movement/form/MovementForm.schema.ts b/src/components/pages/inventory/movement/form/MovementForm.schema.ts new file mode 100644 index 00000000..cdabe355 --- /dev/null +++ b/src/components/pages/inventory/movement/form/MovementForm.schema.ts @@ -0,0 +1,67 @@ +import * as Yup from 'yup'; + +export const MovementFormSchema = Yup.object({ + alasan_transfer: Yup.string() + .required('Alasan Transfer wajib diisi!'), + tanggal_transfer: Yup.date() + .required('Tanggal Transfer wajib diisi!') + .typeError('Tanggal Transfer tidak valid!'), + warehouse_asal: Yup.object({ + value: Yup.number().min(1).required(), + label: Yup.string().required(), + }).nullable(), + warehouse_asal_id: Yup.number() + .required('Gudang Asal wajib diisi!'), + warehouse_tujuan: Yup.object({ + value: Yup.number().min(1).required(), + label: Yup.string().required(), + }).nullable(), + warehouse_tujuan_id: Yup.number() + .required('Gudang Tujuan wajib diisi!'), + alasan: Yup.string() + .required('Alasan wajib diisi!'), + product: Yup.object({ + value: Yup.number().min(1).required(), + label: Yup.string().required(), + }).nullable(), + product_id: Yup.array() + .of(Yup.number()).min(1, 'Pilih minimal 1 produk') + .required('Produk wajib diisi!'), + qty_product: Yup.array() + .of(Yup.number().min(1, 'Kuantitas minimal 1')) + .min(1, 'Pilih minimal 1 produk') + .required('Kuantitas wajib diisi!'), + ekspedisi: Yup.array().of( + Yup.object({ + product: Yup.object({ + value: Yup.number().min(1).required(), + label: Yup.string().required(), + }).nullable(), + product_id: Yup.number() + .required('Produk wajib diisi!'), + qty: Yup.number().min(1, 'Kuantitas minimal 1') + .required('Kuantitas wajib diisi!'), + supplier: Yup.object({ + value: Yup.number().min(1).required(), + label: Yup.string().required(), + }).nullable(), + supplier_id: Yup.number() + .required('Supplier wajib diisi!'), + plat_nomor: Yup.string() + .required('Plat Nomor wajib diisi!'), + no_surat_jalan: Yup.string() + .required('No Surat Jalan wajib diisi!'), + dokumen: Yup.mixed() + .required('Dokumen wajib diisi!'), + biaya_ekspedisi: Yup.number() + .min(0, 'Biaya Ekspedisi minimal 0') + .required('Biaya Ekspedisi wajib diisi!'), + nama_sopir: Yup.string() + .required('Nama Sopir wajib diisi!'), + }) + ).min(1, 'Pilih minimal 1 ekspedisi').required('Ekspedisi wajib diisi!'), +}); + +export const UpdateMovementFormSchema = MovementFormSchema; + +export type MovementFormValues = Yup.InferType; \ No newline at end of file diff --git a/src/config/constant.ts b/src/config/constant.ts index 1fbef81f..870002bf 100644 --- a/src/config/constant.ts +++ b/src/config/constant.ts @@ -1,122 +1,145 @@ export const MAIN_DRAWER_LINKS = [ - { - title: 'Dashboard', - link: '/dashboard', - icon: 'gg:chart', - }, + { + title: 'Dashboard', + link: '/dashboard', + icon: 'gg:chart', + }, - { - title: 'Master Data', - link: '/master-data', - icon: 'majesticons:data-line', - submenu: [ - { - title: 'Product', - link: '/master-data/product', - icon: 'fluent-mdl2:product-variant', - }, - { - title: 'Product Category', - link: '/master-data/product-category', - icon: 'carbon:categories', - }, - { - title: 'Bank', - link: '/master-data/bank', - icon: 'mdi:bank-outline', - }, - { - title: 'Area', - link: '/master-data/area', - icon: 'majesticons:map-marker-area-line', - }, - { - title: 'Location', - link: '/master-data/location', - icon: 'mingcute:location-line', - }, - { - title: 'Kandang', - link: '/master-data/kandang', - icon: 'mdi:farm-home-outline', - }, - { - title: 'Warehouse', - link: '/master-data/warehouse', - icon: 'hugeicons:warehouse', - }, - { - title: 'Customer', - link: '/master-data/customer', - icon: 'ix:customer', - }, - { - title: 'UOM', - link: '/master-data/uom', - icon: 'lsicon:measure-outline', - }, - { - title: 'Non-Stock', - link: '/master-data/nonstock', - icon: 'fluent:box-32-regular', - }, - { - title: 'FCR', - link: '/master-data/FCR', - icon: 'fluent:food-chicken-leg-16-regular', - }, - { - title: 'Supplier', - link: '/master-data/supplier', - icon: 'material-symbols:add-business-outline-rounded', - }, - ], - }, + { + title: 'Persediaan', + link: '/inventory', + icon: 'mdi:warehouse', + submenu: [ + { + title: 'Product', + link: '/inventory/product', + icon: 'mdi:package-variant-closed', + }, + { + title: 'Penyesuaian Stok', + link: '/inventory/adjustment', + icon: 'mdi:database-edit', + }, + { + title: 'Transfer Stok', + link: '/inventory/movement', + icon: 'mdi:swap-horizontal', + }, + ], + }, + + { + title: 'Master Data', + link: '/master-data', + icon: 'majesticons:data-line', + submenu: [ + { + title: 'Product', + link: '/master-data/product', + icon: 'fluent-mdl2:product-variant', + }, + { + title: 'Product Category', + link: '/master-data/product-category', + icon: 'carbon:categories', + }, + { + title: 'Bank', + link: '/master-data/bank', + icon: 'mdi:bank-outline', + }, + { + title: 'Area', + link: '/master-data/area', + icon: 'majesticons:map-marker-area-line', + }, + { + title: 'Location', + link: '/master-data/location', + icon: 'mingcute:location-line', + }, + { + title: 'Kandang', + link: '/master-data/kandang', + icon: 'mdi:farm-home-outline', + }, + { + title: 'Warehouse', + link: '/master-data/warehouse', + icon: 'hugeicons:warehouse', + }, + { + title: 'Customer', + link: '/master-data/customer', + icon: 'ix:customer', + }, + { + title: 'UOM', + link: '/master-data/uom', + icon: 'lsicon:measure-outline', + }, + { + title: 'Non-Stock', + link: '/master-data/nonstock', + icon: 'fluent:box-32-regular', + }, + { + title: 'FCR', + link: '/master-data/FCR', + icon: 'fluent:food-chicken-leg-16-regular', + }, + { + title: 'Supplier', + link: '/master-data/supplier', + icon: 'material-symbols:add-business-outline-rounded', + }, + ], + }, ] as const; export const ROWS_OPTIONS = [ - { - label: '10', - value: 10, - }, - { - label: '20', - value: 20, - }, - { - label: '50', - value: 50, - }, - { - label: '100', - value: 100, - }, + { + label: '10', + value: 10, + }, + { + label: '20', + value: 20, + }, + { + label: '50', + value: 50, + }, + { + label: '100', + value: 100, + }, ]; export const WAREHOUSE_TYPE_OPTIONS = [ - { - label: 'AREA', - value: 'AREA', - }, - { - label: 'LOKASI', - value: 'LOKASI', - }, - { - label: 'KANDANG', - value: 'KANDANG', - }, + { + label: 'AREA', + value: 'AREA', + }, + { + label: 'LOKASI', + value: 'LOKASI', + }, + { + label: 'KANDANG', + value: 'KANDANG', + }, ]; export const PRODUCT_FLAG_OPTIONS = [ - { label: 'DOC', value: 'DOC' }, - { label: 'PAKAN', value: 'PAKAN' }, - { label: 'PRE-STARTER', value: 'PRE-STARTER' }, - { label: 'STARTER', value: 'STARTER' }, - { label: 'FINISHER', value: 'FINISHER' }, - { label: 'OVK', value: 'OVK' }, - { label: 'OBAT', value: 'OBAT' }, - { label: 'VITAMIN', value: 'VITAMIN' }, - { label: 'KIMIA', value: 'KIMIA' }, + {label: 'DOC', value: 'DOC'}, + {label: 'PAKAN', value: 'PAKAN'}, + {label: 'PRE-STARTER', value: 'PRE-STARTER'}, + {label: 'STARTER', value: 'STARTER'}, + {label: 'FINISHER', value: 'FINISHER'}, + {label: 'OVK', value: 'OVK'}, + {label: 'OBAT', value: 'OBAT'}, + {label: 'VITAMIN', value: 'VITAMIN'}, + {label: 'KIMIA', value: 'KIMIA'}, ]; diff --git a/src/services/api/inventory.ts b/src/services/api/inventory.ts new file mode 100644 index 00000000..3e3115e2 --- /dev/null +++ b/src/services/api/inventory.ts @@ -0,0 +1,12 @@ +import { + CreateMovementPayload, + Movement, + UpdateMovementPayload, +} from "@/types/api/inventory/movement"; +import {BaseApiService} from "@/services/api/base"; + +export const MovementApi = new BaseApiService< + Movement, + CreateMovementPayload, + UpdateMovementPayload +>('/inventory/movements'); \ No newline at end of file diff --git a/src/types/api/inventory/movement.d.ts b/src/types/api/inventory/movement.d.ts new file mode 100644 index 00000000..fe2996ba --- /dev/null +++ b/src/types/api/inventory/movement.d.ts @@ -0,0 +1,51 @@ +import {BaseMetadata} from '@/types/api/api-general'; +import {Product} from "@/types/api/master-data/product"; +import {Supplier} from "@/types/api/master-data/supplier"; +import {Warehouse} from "@/types/api/master-data/warehouse"; + +export type BaseMovement = { + id: number; + alasan_transfer: string; + tanggal_transfer: string; + warehouse_asal: Warehouse; + warehouse_tujuan: Warehouse; + product: Array<{ + product: Product; + qty_product: number; + }>; + ekspedisi: Array<{ + product_id: number; + qty: number; + supplier: Supplier; + plat_nomor: string; + no_surat_jalan: string; + dokumen: string; + biaya_ekspedisi: number; + nama_sopir: string; + }>; + name: string; +}; + +export type Movement = BaseMetadata & BaseMovement; + +export type CreateMovementPayload = { + alasan: string; + warehouse_asal_id: number; + warehouse_tujuan_id: number; + product: Array<{ + product_id: number; + qty_product: number; + }>; + ekspedisi: Array<{ + product_id: number; + qty: number; + supplier_id: number; + plat_nomor: string; + no_surat_jalan: string; + dokumen: string; + biaya_ekspedisi: number; + nama_sopir: string; + }>; +} + +export type UpdateMovementPayload = CreateMovementPayload; \ No newline at end of file