summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/app/api/product/import/route.tsx55
-rw-r--r--src/app/api/product/route.tsx2
-rw-r--r--src/modules/result/components/ImportModal.tsx11
-rw-r--r--src/modules/result/components/ProductModal.tsx4
-rw-r--r--src/modules/stock-opname/index.tsx29
5 files changed, 85 insertions, 16 deletions
diff --git a/src/app/api/product/import/route.tsx b/src/app/api/product/import/route.tsx
index 37b4690..f96c2d9 100644
--- a/src/app/api/product/import/route.tsx
+++ b/src/app/api/product/import/route.tsx
@@ -16,18 +16,49 @@ export async function POST(request: NextRequest) {
const fileData: any[] = XLSX.utils.sheet_to_json(worksheet, { header: 1 })
fileData.shift();
- const newProducts = fileData.map(row => ({
- id: undefined,
- isDifferent: false,
- name: row[0]?.toString() || '', // Handle undefined values
- barcode: row[1]?.toString() || '', // Handle undefined values
- itemCode: row[2]?.toString() || '', // Handle undefined values
- onhandQty: row[3] || 0, // Handle undefined values
- differenceQty: row[4] || 0, // Handle undefined values
- companyId: intCompanyId, // Use the parsed company ID
- externalId: row[6] ? row[6].toString() : null, // Handle undefined values
- value: row[7] != null ? Number(row[7]) || 0 : null,
- }));
+ // Fetch all locations for this company for location validation
+ const locations = await prisma.location.findMany({
+ where: { companyId: intCompanyId }
+ })
+
+ // Create a map for quick location lookup by name (case-insensitive) and by id
+ const locationByName = new Map(
+ locations.map(loc => [loc.name.toLowerCase(), loc.id])
+ )
+ const locationById = new Map(
+ locations.map(loc => [loc.id.toString(), loc.id])
+ )
+
+ const newProducts = fileData.map(row => {
+ let locationId: number | null = null
+
+ // Parse locationId from column 8
+ const locationValue = row[8]?.toString().trim()
+
+ if (locationValue) {
+ // Try to match by ID first, then by name
+ const idAsNumber = parseInt(locationValue)
+ if (!isNaN(idAsNumber) && locationById.has(idAsNumber.toString())) {
+ locationId = idAsNumber
+ } else if (locationByName.has(locationValue.toLowerCase())) {
+ locationId = locationByName.get(locationValue.toLowerCase()) || null
+ }
+ }
+
+ return {
+ id: undefined,
+ isDifferent: false,
+ name: row[0]?.toString() || '', // Handle undefined values
+ barcode: row[1]?.toString() || '', // Handle undefined values
+ itemCode: row[2]?.toString() || '', // Handle undefined values
+ onhandQty: row[3] || 0, // Handle undefined values
+ differenceQty: row[4] || 0, // Handle undefined values
+ companyId: intCompanyId, // Use the parsed company ID
+ externalId: row[6] ? row[6].toString() : null, // Handle undefined values
+ value: row[7] != null ? Number(row[7]) || 0 : null,
+ locationId: locationId, // Set locationId or null if not found/provided
+ }
+ });
const whereCompany = { companyId: intCompanyId }
diff --git a/src/app/api/product/route.tsx b/src/app/api/product/route.tsx
index de8a482..23bb065 100644
--- a/src/app/api/product/route.tsx
+++ b/src/app/api/product/route.tsx
@@ -36,7 +36,7 @@ export async function GET(request: NextRequest) {
const products = await prisma.product.findMany({
where,
- include: { company: true },
+ include: { company: true, location: true },
take: PAGE_SIZE,
skip: (intPage - 1) * PAGE_SIZE,
orderBy: { name: 'asc' }
diff --git a/src/modules/result/components/ImportModal.tsx b/src/modules/result/components/ImportModal.tsx
index 1bb7b26..daacb55 100644
--- a/src/modules/result/components/ImportModal.tsx
+++ b/src/modules/result/components/ImportModal.tsx
@@ -62,6 +62,17 @@ const ImportModal = ({ modal }: Props) => {
<form className='pb-6' onSubmit={handleSubmit}>
<input type='file' onChange={handleFileChange} accept='.xls, .xlsx' />
<Spacer y={4} />
+
+ <div className='text-xs p-3 bg-info-100 border border-info-200 text-info-700 rounded-medium flex items-start gap-2'>
+ <div className='flex-shrink-0'>ℹ️</div>
+ <div>
+ <p className='font-semibold'>Format File Excel:</p>
+ <p>Kolom 1-7: Nama, Barcode, Item Code, Qty On-hand, Qty Gudang Selisih, companyId, ID / External ID, value (per Unit)</p>
+ <p>Kolom 8 (opsional): Location</p>
+ </div>
+ </div>
+ <Spacer y={4} />
+
<Input
type="text"
label='Ketik ulang untuk konfirmasi'
diff --git a/src/modules/result/components/ProductModal.tsx b/src/modules/result/components/ProductModal.tsx
index cfd7382..df0a911 100644
--- a/src/modules/result/components/ProductModal.tsx
+++ b/src/modules/result/components/ProductModal.tsx
@@ -36,7 +36,7 @@ const ProductModal = ({ modal }: Props) => {
})
const response = await fetch(`/api/product?${searchParams}`)
const data: {
- products: (Product & { company: { id: number, name: string } })[],
+ products: (Product & { company: { id: number, name: string }, location?: { id: number, name: string } | null })[],
page: number,
totalPage: number
} = await response.json()
@@ -89,6 +89,7 @@ const ProductModal = ({ modal }: Props) => {
<TableColumn>BARCODE</TableColumn>
<TableColumn>ON-HAND QTY</TableColumn>
<TableColumn>DIFFERENCE QTY</TableColumn>
+ <TableColumn>LOCATION</TableColumn>
<TableColumn>COMPANY</TableColumn>
</TableHeader>
<TableBody items={data?.products || []}>
@@ -99,6 +100,7 @@ const ProductModal = ({ modal }: Props) => {
<TableCell>{product.barcode}</TableCell>
<TableCell>{product.onhandQty}</TableCell>
<TableCell>{product.differenceQty}</TableCell>
+ <TableCell>{(product as any).location?.name || '-'}</TableCell>
<TableCell>{product.company.name}</TableCell>
</TableRow>
)}
diff --git a/src/modules/stock-opname/index.tsx b/src/modules/stock-opname/index.tsx
index 2937a98..4456b11 100644
--- a/src/modules/stock-opname/index.tsx
+++ b/src/modules/stock-opname/index.tsx
@@ -37,6 +37,31 @@ const StockOpname = () => {
}, [form.product, form.location, setOldOpname])
+ // Auto-populate location when a product with assigned location is selected
+ useEffect(() => {
+ const loadProductAndSetLocation = async () => {
+ if (form.product?.value) {
+ try {
+ const response = await fetch(`/api/product?search=`)
+ const data: { products: Product[] } = await response.json()
+ const selectedProduct = data?.products.find(p => p.id === form.product?.value)
+
+ if (selectedProduct && (selectedProduct as any).location && !form.location?.value) {
+ const location = (selectedProduct as any).location
+ updateForm('location', {
+ value: location.id,
+ label: location.name
+ })
+ }
+ } catch (error) {
+ console.error('Error loading product location:', error)
+ }
+ }
+ }
+
+ loadProductAndSetLocation()
+ }, [form.product?.value, form.location?.value, updateForm])
+
const handleSelectChange = (val: SingleValue<SelectOption>, action: ActionMeta<SelectOption>) => {
updateForm(action.name as keyof typeof form, val)
}
@@ -253,9 +278,9 @@ const loadProduct = async (inputValue: string) => {
const response = await fetch(`/api/product?search=${inputValue}`)
const data: { products: Product[] } = await response.json() || []
- return data?.products.map((product) => ({
+ return data?.products.map((product: any) => ({
value: product.id,
- label: `${product.itemCode ? `[${product.itemCode}]` : ''} ${product.name} ${product.barcode ? ` [${product.barcode}]` : ''}`
+ label: `${product.itemCode ? `[${product.itemCode}]` : ''} ${product.name} ${product.barcode ? ` [${product.barcode}]` : ''} ${product.location ? `(Lokasi: ${product.location.name})` : ''}`
}
))
}