From 4e4110317881e46ddf207fb20dba13b6a47c9c78 Mon Sep 17 00:00:00 2001
From: Oliver Walters <oliver.henry.walters@gmail.com>
Date: Sat, 19 Apr 2025 12:06:18 +0000
Subject: [PATCH] Scan item into location

---
 .../components/barcodes/BarcodeScanDialog.tsx | 46 ++++++++++++++++---
 .../src/pages/stock/LocationDetail.tsx        | 32 +++++++++++--
 2 files changed, 66 insertions(+), 12 deletions(-)

diff --git a/src/frontend/src/components/barcodes/BarcodeScanDialog.tsx b/src/frontend/src/components/barcodes/BarcodeScanDialog.tsx
index 30b2aca690..3457d55534 100644
--- a/src/frontend/src/components/barcodes/BarcodeScanDialog.tsx
+++ b/src/frontend/src/components/barcodes/BarcodeScanDialog.tsx
@@ -5,6 +5,7 @@ import { apiUrl } from '@lib/functions/Api';
 import { getDetailUrl } from '@lib/functions/Navigation';
 import { t } from '@lingui/core/macro';
 import { Box, Divider, Modal } from '@mantine/core';
+import { hideNotification, showNotification } from '@mantine/notifications';
 import { useCallback, useState } from 'react';
 import { type NavigateFunction, useNavigate } from 'react-router-dom';
 import { api } from '../../App';
@@ -14,8 +15,8 @@ import { StylishText } from '../items/StylishText';
 import { BarcodeInput } from './BarcodeInput';
 
 export type BarcodeScanResult = {
-  success: boolean;
-  error: string;
+  success?: string;
+  error?: string;
 };
 
 // Callback function for handling a barcode scan
@@ -29,10 +30,12 @@ export default function BarcodeScanDialog({
   title,
   opened,
   callback,
+  modelType,
   onClose
 }: Readonly<{
   title?: string;
   opened: boolean;
+  modelType?: ModelType;
   callback?: BarcodeScanCallback;
   onClose: () => void;
 }>) {
@@ -50,6 +53,7 @@ export default function BarcodeScanDialog({
         <ScanInputHandler
           navigate={navigate}
           onClose={onClose}
+          modelType={modelType}
           callback={callback}
         />
       </Box>
@@ -59,11 +63,13 @@ export default function BarcodeScanDialog({
 
 export function ScanInputHandler({
   callback,
+  modelType,
   onClose,
   navigate
 }: Readonly<{
   callback?: BarcodeScanCallback;
   onClose: () => void;
+  modelType?: ModelType;
   navigate: NavigateFunction;
 }>) {
   const [error, setError] = useState<string>('');
@@ -76,6 +82,11 @@ export function ScanInputHandler({
 
       // Find the matching model type
       for (const model_type of Object.keys(ModelInformationDict)) {
+        // If a specific model type is provided, check if it matches
+        if (modelType && model_type !== modelType) {
+          continue;
+        }
+
         if (data[model_type]?.['pk']) {
           if (user.hasViewPermission(model_type as ModelType)) {
             const url = getDetailUrl(
@@ -94,7 +105,7 @@ export function ScanInputHandler({
         setError(t`No matching item found`);
       }
     },
-    [navigate, onClose, user]
+    [navigate, onClose, user, modelType]
   );
 
   const onScan = useCallback(
@@ -114,12 +125,30 @@ export function ScanInputHandler({
           const data = response.data ?? {};
 
           if (callback && data.success && response.status === 200) {
+            const instance = null;
+
+            // If the caller is expecting a specific model type, check if it matches
+            if (modelType) {
+              const pk: number = data[modelType]?.['pk'];
+              if (!pk) {
+                setError(t`Barcode does not match the expected model type`);
+                return;
+              }
+            }
+
             callback(barcode, data)
               .then((result: BarcodeScanResult) => {
                 if (result.success) {
+                  hideNotification('barcode-scan');
+                  showNotification({
+                    id: 'barcode-scan',
+                    title: t`Success`,
+                    message: result.success,
+                    color: 'green'
+                  });
                   onClose();
                 } else {
-                  setError(result.error);
+                  setError(result.error ?? t`Failed to handle barcode`);
                 }
               })
               .finally(() => {
@@ -144,7 +173,7 @@ export function ScanInputHandler({
           setProcessing(false);
         });
     },
-    [callback, defaultScan]
+    [callback, defaultScan, modelType, onClose]
   );
 
   return <BarcodeInput onScan={onScan} error={error} processing={processing} />;
@@ -152,14 +181,16 @@ export function ScanInputHandler({
 
 export function useBarcodeScanDialog({
   title,
-  callback
+  callback,
+  modelType
 }: Readonly<{
   title: string;
+  modelType?: ModelType;
   callback: BarcodeScanCallback;
 }>) {
   const [opened, setOpened] = useState(false);
 
-  const open = useCallback((callback?: BarcodeScanCallback) => {
+  const open = useCallback(() => {
     setOpened(true);
   }, []);
 
@@ -168,6 +199,7 @@ export function useBarcodeScanDialog({
       title={title}
       opened={opened}
       callback={callback}
+      modelType={modelType}
       onClose={() => setOpened(false)}
     />
   );
diff --git a/src/frontend/src/pages/stock/LocationDetail.tsx b/src/frontend/src/pages/stock/LocationDetail.tsx
index 9c315c2604..dd741a28e4 100644
--- a/src/frontend/src/pages/stock/LocationDetail.tsx
+++ b/src/frontend/src/pages/stock/LocationDetail.tsx
@@ -2,11 +2,13 @@ import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
 import { ModelType } from '@lib/enums/ModelType';
 import { UserRoles } from '@lib/enums/Roles';
 import { getDetailUrl } from '@lib/functions/Navigation';
+import { apiUrl } from '@lib/index';
 import { t } from '@lingui/core/macro';
 import { Group, Skeleton, Stack, Text } from '@mantine/core';
 import { IconInfoCircle, IconPackages, IconSitemap } from '@tabler/icons-react';
 import { useMemo, useState } from 'react';
 import { useNavigate, useParams } from 'react-router-dom';
+import { api } from '../../App';
 import { useBarcodeScanDialog } from '../../components/barcodes/BarcodeScanDialog';
 import AdminButton from '../../components/buttons/AdminButton';
 import { PrintingActions } from '../../components/buttons/PrintingActions';
@@ -275,12 +277,32 @@ export default function Stock() {
 
   const scanInStockItems = useBarcodeScanDialog({
     title: t`Scan Stock Item`,
+    modelType: ModelType.stockitem,
     callback: async (barcode, response) => {
-      console.log('response:', response);
-      return {
-        success: true,
-        error: ''
-      };
+      const item = response.stockitem.instance;
+
+      // Scan the stock item into the current location
+      return api
+        .post(apiUrl(ApiEndpoints.stock_transfer), {
+          location: location.pk,
+          items: [
+            {
+              pk: item.pk,
+              quantity: item.quantity
+            }
+          ]
+        })
+        .then(() => {
+          return {
+            success: t`Scanned stock item into location`
+          };
+        })
+        .catch((error) => {
+          console.error('Error scanning stock item:', error);
+          return {
+            error: t`Error scanning stock item`
+          };
+        });
     }
   });