mirror of
				https://github.com/inventree/inventree-app.git
				synced 2025-10-31 05:15:42 +00:00 
			
		
		
		
	Barcode workflow (#485)
* Refactor stock barcode operations into new file * Add setting to control confirmation of stock transfer actions * Update details when scannign stock item * Confirm movement when moving items into location * Cleanup
This commit is contained in:
		| @@ -3,6 +3,7 @@ | ||||
|  | ||||
| - Support "active" field for Company model | ||||
| - Support "active" field for SupplierPart model | ||||
| - Adjustments to barcode scanning workflow | ||||
| - Updated translations | ||||
|  | ||||
| ### 0.14.2 - February 2024 | ||||
|   | ||||
| @@ -10,7 +10,6 @@ import "package:flutter/material.dart"; | ||||
| import "package:inventree/api.dart"; | ||||
| import "package:inventree/app_colors.dart"; | ||||
| import "package:inventree/barcode/barcode.dart"; | ||||
| import "package:inventree/barcode/tones.dart"; | ||||
| import "package:inventree/helpers.dart"; | ||||
| import "package:inventree/inventree/sales_order.dart"; | ||||
| import "package:inventree/l10.dart"; | ||||
| @@ -342,12 +341,7 @@ class APIFormField { | ||||
|               controller.text = hash; | ||||
|               data["value"] = hash; | ||||
|  | ||||
|               barcodeSuccessTone(); | ||||
|  | ||||
|               showSnackIcon( | ||||
|                   L10().barcodeAssigned, | ||||
|                   success: true | ||||
|               ); | ||||
|               barcodeSuccess(L10().barcodeAssigned); | ||||
|             }); | ||||
|  | ||||
|             scanBarcode(context, handler: handler); | ||||
|   | ||||
| @@ -9,7 +9,6 @@ import "package:one_context/one_context.dart"; | ||||
|  | ||||
|  | ||||
| import "package:inventree/api.dart"; | ||||
| import "package:inventree/helpers.dart"; | ||||
| import "package:inventree/l10.dart"; | ||||
|  | ||||
| import "package:inventree/barcode/camera_controller.dart"; | ||||
| @@ -33,6 +32,35 @@ import "package:inventree/widget/stock/stock_detail.dart"; | ||||
| import "package:inventree/widget/company/supplier_part_detail.dart"; | ||||
|  | ||||
|  | ||||
| // Signal a barcode scan success to the user | ||||
| Future<void> barcodeSuccess(String msg) async { | ||||
|  | ||||
|   barcodeSuccessTone(); | ||||
|   showSnackIcon(msg, success: true); | ||||
| } | ||||
|  | ||||
| // Signal a barcode scan failure to the user | ||||
| Future<void> barcodeFailure(String msg, dynamic extra) async { | ||||
|   barcodeFailureTone(); | ||||
|   showSnackIcon( | ||||
|       msg, | ||||
|       success: false, | ||||
|     onAction: () { | ||||
|         OneContext().showDialog( | ||||
|           builder: (BuildContext context) => SimpleDialog( | ||||
|             title: Text(L10().barcodeError), | ||||
|             children: <Widget>[ | ||||
|               ListTile( | ||||
|                 title: Text(L10().responseData), | ||||
|                 subtitle: Text(extra.toString()) | ||||
|               ) | ||||
|             ] | ||||
|           ) | ||||
|         ); | ||||
|     } | ||||
|   ); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Launch a barcode scanner with a particular context and handler. | ||||
|  *  | ||||
| @@ -266,234 +294,6 @@ class BarcodeScanHandler extends BarcodeHandler { | ||||
| } | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Generic class for scanning a StockLocation. | ||||
|  * | ||||
|  * - Validates that the scanned barcode matches a valid StockLocation | ||||
|  * - Runs a "callback" function if a valid StockLocation is found | ||||
|  */ | ||||
| class BarcodeScanStockLocationHandler extends BarcodeHandler { | ||||
|  | ||||
|   @override | ||||
|   String getOverlayText(BuildContext context) => L10().barcodeScanLocation; | ||||
|  | ||||
|   @override | ||||
|   Future<void> onBarcodeMatched(Map<String, dynamic> data) async { | ||||
|  | ||||
|     // We expect that the barcode points to a 'stocklocation' | ||||
|     if (data.containsKey("stocklocation")) { | ||||
|       int _loc = (data["stocklocation"]["pk"] ?? -1) as int; | ||||
|  | ||||
|       // A valid stock location! | ||||
|       if (_loc > 0) { | ||||
|  | ||||
|         debug("Scanned stock location ${_loc}"); | ||||
|  | ||||
|         final bool result = await onLocationScanned(_loc); | ||||
|  | ||||
|         if (result && OneContext.hasContext) { | ||||
|           OneContext().pop(); | ||||
|           return; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // If we get to this point, something went wrong during the scan process | ||||
|     barcodeFailureTone(); | ||||
|  | ||||
|     showSnackIcon( | ||||
|       L10().invalidStockLocation, | ||||
|       success: false, | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   // Callback function which runs when a valid StockLocation is scanned | ||||
|   // If this function returns 'true' the barcode scanning dialog will be closed | ||||
|   Future<bool> onLocationScanned(int locationId) async { | ||||
|     // Re-implement this for particular subclass | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Generic class for scanning a StockItem | ||||
|  * | ||||
|  * - Validates that the scanned barcode matches a valid StockItem | ||||
|  * - Runs a "callback" function if a valid StockItem is found | ||||
|  */ | ||||
| class BarcodeScanStockItemHandler extends BarcodeHandler { | ||||
|  | ||||
|   @override | ||||
|   String getOverlayText(BuildContext context) => L10().barcodeScanItem; | ||||
|  | ||||
|   @override | ||||
|   Future<void> onBarcodeMatched(Map<String, dynamic> data) async { | ||||
|     // We expect that the barcode points to a 'stockitem' | ||||
|     if (data.containsKey("stockitem")) { | ||||
|       int _item = (data["stockitem"]["pk"] ?? -1) as int; | ||||
|  | ||||
|       // A valid stock location! | ||||
|       if (_item > 0) { | ||||
|  | ||||
|         barcodeSuccessTone(); | ||||
|  | ||||
|         bool result = await onItemScanned(_item); | ||||
|  | ||||
|         if (result && OneContext.hasContext) { | ||||
|           OneContext().pop(); | ||||
|           return; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // If we get to this point, something went wrong during the scan process | ||||
|     barcodeFailureTone(); | ||||
|  | ||||
|     showSnackIcon( | ||||
|       L10().invalidStockItem, | ||||
|       success: false, | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   // Callback function which runs when a valid StockItem is scanned | ||||
|   Future<bool> onItemScanned(int itemId) async { | ||||
|     // Re-implement this for particular subclass | ||||
|     return false; | ||||
|   } | ||||
| } | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Barcode handler for scanning a provided StockItem into a scanned StockLocation. | ||||
|  * | ||||
|  * - The class is initialized by passing a valid StockItem object | ||||
|  * - Expects to scan barcode for a StockLocation | ||||
|  * - The StockItem is transferred into the scanned location | ||||
|  */ | ||||
| class StockItemScanIntoLocationHandler extends BarcodeScanStockLocationHandler { | ||||
|  | ||||
|   StockItemScanIntoLocationHandler(this.item); | ||||
|  | ||||
|   final InvenTreeStockItem item; | ||||
|  | ||||
|   @override | ||||
|   Future<bool> onLocationScanned(int locationId) async { | ||||
|  | ||||
|     final result = await item.transferStock(locationId); | ||||
|  | ||||
|     if (result) { | ||||
|       barcodeSuccessTone(); | ||||
|       showSnackIcon(L10().barcodeScanIntoLocationSuccess, success: true); | ||||
|     } else { | ||||
|       barcodeFailureTone(); | ||||
|       showSnackIcon(L10().barcodeScanIntoLocationFailure, success: false); | ||||
|     } | ||||
|  | ||||
|     return result; | ||||
|   } | ||||
| } | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Barcode handler for scanning stock item(s) into the specified StockLocation. | ||||
|  * | ||||
|  * - The class is initialized by passing a valid StockLocation object | ||||
|  * - Expects to scan a barcode for a StockItem | ||||
|  * - The scanned StockItem is transferred into the provided StockLocation | ||||
|  */ | ||||
| class StockLocationScanInItemsHandler extends BarcodeScanStockItemHandler { | ||||
|  | ||||
|   StockLocationScanInItemsHandler(this.location); | ||||
|  | ||||
|   final InvenTreeStockLocation location; | ||||
|  | ||||
|   @override | ||||
|   String getOverlayText(BuildContext context) => L10().barcodeScanItem; | ||||
|  | ||||
|   @override | ||||
|   Future<bool> onItemScanned(int itemId) async { | ||||
|  | ||||
|     final InvenTreeStockItem? item = await InvenTreeStockItem().get(itemId) as InvenTreeStockItem?; | ||||
|  | ||||
|     bool result = false; | ||||
|  | ||||
|     if (item != null) { | ||||
|  | ||||
|       // Item is already *in* the specified location | ||||
|       if (item.locationId == location.pk) { | ||||
|         barcodeFailureTone(); | ||||
|         showSnackIcon(L10().itemInLocation, success: true); | ||||
|         return false; | ||||
|       } else { | ||||
|         result = await item.transferStock(location.pk); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|         showSnackIcon( | ||||
|             result ? L10().barcodeScanIntoLocationSuccess : L10().barcodeScanIntoLocationFailure, | ||||
|             success: result | ||||
|         ); | ||||
|  | ||||
|     // We always return false here, to ensure the barcode scan dialog remains open | ||||
|     return false; | ||||
|   } | ||||
| } | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Barcode handler class for scanning a StockLocation into another StockLocation | ||||
|  * | ||||
|  * - The class is initialized by passing a valid StockLocation object | ||||
|  * - Expects to scan barcode for another *parent* StockLocation | ||||
|  * - The scanned StockLocation is set as the "parent" of the provided StockLocation | ||||
|  */ | ||||
| class ScanParentLocationHandler extends BarcodeScanStockLocationHandler { | ||||
|  | ||||
|   ScanParentLocationHandler(this.location); | ||||
|  | ||||
|   final InvenTreeStockLocation location; | ||||
|  | ||||
|   @override | ||||
|   Future<bool> onLocationScanned(int locationId) async { | ||||
|  | ||||
|     final response = await location.update( | ||||
|       values: { | ||||
|         "parent": locationId.toString(), | ||||
|       }, | ||||
|       expectedStatusCode: null, | ||||
|     ); | ||||
|  | ||||
|     switch (response.statusCode) { | ||||
|       case 200: | ||||
|       case 201: | ||||
|         barcodeSuccessTone(); | ||||
|         showSnackIcon(L10().barcodeScanIntoLocationSuccess, success: true); | ||||
|         return true; | ||||
|       case 400:  // Invalid parent location chosen | ||||
|         barcodeFailureTone(); | ||||
|         showSnackIcon(L10().invalidStockLocation, success: false); | ||||
|         return false; | ||||
|       default: | ||||
|         barcodeFailureTone(); | ||||
|         showSnackIcon( | ||||
|             L10().barcodeScanIntoLocationFailure, | ||||
|             success: false, | ||||
|             actionText: L10().details, | ||||
|             onAction: () { | ||||
|               showErrorDialog( | ||||
|                 L10().barcodeError, | ||||
|                 response: response, | ||||
|               ); | ||||
|             } | ||||
|         ); | ||||
|         return false; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Barcode handler for finding a "unique" barcode (one that does not match an item in the database) | ||||
|  */ | ||||
|   | ||||
| @@ -6,6 +6,7 @@ import "package:one_context/one_context.dart"; | ||||
| import "package:inventree/l10.dart"; | ||||
| import "package:inventree/api_form.dart"; | ||||
|  | ||||
| import "package:inventree/barcode/barcode.dart"; | ||||
| import "package:inventree/barcode/handler.dart"; | ||||
| import "package:inventree/barcode/tones.dart"; | ||||
|  | ||||
| @@ -51,8 +52,7 @@ class POReceiveBarcodeHandler extends BarcodeHandler { | ||||
|       return onBarcodeUnknown(data); | ||||
|     } | ||||
|  | ||||
|     barcodeSuccessTone(); | ||||
|     showSnackIcon(L10().receivedItem, success: true); | ||||
|     barcodeSuccess(L10().receivedItem); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import "package:one_context/one_context.dart"; | ||||
|  | ||||
| import "package:inventree/l10.dart"; | ||||
|  | ||||
| import "package:inventree/barcode/barcode.dart"; | ||||
| import "package:inventree/barcode/handler.dart"; | ||||
| import "package:inventree/barcode/tones.dart"; | ||||
|  | ||||
| @@ -115,8 +116,7 @@ class SOAllocateStockHandler extends BarcodeHandler { | ||||
|       return onBarcodeUnknown(data); | ||||
|     } | ||||
|  | ||||
|     barcodeSuccessTone(); | ||||
|     showSnackIcon(L10().allocated, success: true); | ||||
|     barcodeSuccess(L10().allocated); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   | ||||
							
								
								
									
										292
									
								
								lib/barcode/stock.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										292
									
								
								lib/barcode/stock.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,292 @@ | ||||
| import "package:flutter/cupertino.dart"; | ||||
| import "package:font_awesome_flutter/font_awesome_flutter.dart"; | ||||
| import "package:inventree/api_form.dart"; | ||||
| import "package:inventree/preferences.dart"; | ||||
| import "package:one_context/one_context.dart"; | ||||
|  | ||||
| import "package:inventree/helpers.dart"; | ||||
| import "package:inventree/l10.dart"; | ||||
|  | ||||
| import "package:inventree/barcode/barcode.dart"; | ||||
| import "package:inventree/barcode/handler.dart"; | ||||
| import "package:inventree/barcode/tones.dart"; | ||||
|  | ||||
| import "package:inventree/inventree/stock.dart"; | ||||
|  | ||||
| import "package:inventree/widget/dialogs.dart"; | ||||
| import "package:inventree/widget/snacks.dart"; | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Generic class for scanning a StockLocation. | ||||
|  * | ||||
|  * - Validates that the scanned barcode matches a valid StockLocation | ||||
|  * - Runs a "callback" function if a valid StockLocation is found | ||||
|  */ | ||||
| class BarcodeScanStockLocationHandler extends BarcodeHandler { | ||||
|  | ||||
|   @override | ||||
|   String getOverlayText(BuildContext context) => L10().barcodeScanLocation; | ||||
|  | ||||
|   @override | ||||
|   Future<void> onBarcodeMatched(Map<String, dynamic> data) async { | ||||
|  | ||||
|     // We expect that the barcode points to a 'stocklocation' | ||||
|     if (data.containsKey("stocklocation")) { | ||||
|       int _loc = (data["stocklocation"]["pk"] ?? -1) as int; | ||||
|  | ||||
|       // A valid stock location! | ||||
|       if (_loc > 0) { | ||||
|  | ||||
|         debug("Scanned stock location ${_loc}"); | ||||
|  | ||||
|         final bool result = await onLocationScanned(_loc); | ||||
|  | ||||
|         if (result && OneContext.hasContext) { | ||||
|           OneContext().pop(); | ||||
|           return; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // If we get to this point, something went wrong during the scan process | ||||
|     barcodeFailureTone(); | ||||
|  | ||||
|     showSnackIcon( | ||||
|       L10().invalidStockLocation, | ||||
|       success: false, | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   // Callback function which runs when a valid StockLocation is scanned | ||||
|   // If this function returns 'true' the barcode scanning dialog will be closed | ||||
|   Future<bool> onLocationScanned(int locationId) async { | ||||
|     // Re-implement this for particular subclass | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Generic class for scanning a StockItem | ||||
|  * | ||||
|  * - Validates that the scanned barcode matches a valid StockItem | ||||
|  * - Runs a "callback" function if a valid StockItem is found | ||||
|  */ | ||||
| class BarcodeScanStockItemHandler extends BarcodeHandler { | ||||
|  | ||||
|   @override | ||||
|   String getOverlayText(BuildContext context) => L10().barcodeScanItem; | ||||
|  | ||||
|   @override | ||||
|   Future<void> onBarcodeMatched(Map<String, dynamic> data) async { | ||||
|     // We expect that the barcode points to a 'stockitem' | ||||
|     if (data.containsKey("stockitem")) { | ||||
|       int _item = (data["stockitem"]["pk"] ?? -1) as int; | ||||
|  | ||||
|       // A valid stock location! | ||||
|       if (_item > 0) { | ||||
|  | ||||
|         barcodeSuccessTone(); | ||||
|  | ||||
|         bool result = await onItemScanned(_item); | ||||
|  | ||||
|         if (result && OneContext.hasContext) { | ||||
|           OneContext().pop(); | ||||
|           return; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // If we get to this point, something went wrong during the scan process | ||||
|     barcodeFailureTone(); | ||||
|  | ||||
|     showSnackIcon( | ||||
|       L10().invalidStockItem, | ||||
|       success: false, | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   // Callback function which runs when a valid StockItem is scanned | ||||
|   Future<bool> onItemScanned(int itemId) async { | ||||
|     // Re-implement this for particular subclass | ||||
|     return false; | ||||
|   } | ||||
| } | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Barcode handler for scanning a provided StockItem into a scanned StockLocation. | ||||
|  * | ||||
|  * - The class is initialized by passing a valid StockItem object | ||||
|  * - Expects to scan barcode for a StockLocation | ||||
|  * - The StockItem is transferred into the scanned location | ||||
|  */ | ||||
| class StockItemScanIntoLocationHandler extends BarcodeScanStockLocationHandler { | ||||
|  | ||||
|   StockItemScanIntoLocationHandler(this.item); | ||||
|  | ||||
|   final InvenTreeStockItem item; | ||||
|  | ||||
|   @override | ||||
|   Future<bool> onLocationScanned(int locationId) async { | ||||
|  | ||||
|     final bool confirm = await InvenTreeSettingsManager().getBool(INV_STOCK_CONFIRM_SCAN, false); | ||||
|  | ||||
|     bool result = false; | ||||
|  | ||||
|     if (confirm) { | ||||
|  | ||||
|       Map<String, dynamic> fields = item.transferFields(); | ||||
|  | ||||
|       // Override location with scanned value | ||||
|       fields["location"]?["value"] = locationId; | ||||
|  | ||||
|       launchApiForm( | ||||
|         OneContext().context!, | ||||
|         L10().transferStock, | ||||
|         InvenTreeStockItem.transferStockUrl(), | ||||
|         fields, | ||||
|         method: "POST", | ||||
|         icon: FontAwesomeIcons.dolly, | ||||
|         onSuccess: (data) async { | ||||
|           showSnackIcon(L10().stockItemUpdated, success: true); | ||||
|         } | ||||
|       ); | ||||
|  | ||||
|       return true; | ||||
|     } else { | ||||
|       result = await item.transferStock(locationId); | ||||
|     } | ||||
|  | ||||
|     if (result) { | ||||
|       barcodeSuccess(L10().barcodeScanIntoLocationSuccess); | ||||
|     } else { | ||||
|       barcodeFailureTone(); | ||||
|       showSnackIcon(L10().barcodeScanIntoLocationFailure, success: false); | ||||
|     } | ||||
|  | ||||
|     return result; | ||||
|   } | ||||
| } | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Barcode handler for scanning stock item(s) into the specified StockLocation. | ||||
|  * | ||||
|  * - The class is initialized by passing a valid StockLocation object | ||||
|  * - Expects to scan a barcode for a StockItem | ||||
|  * - The scanned StockItem is transferred into the provided StockLocation | ||||
|  */ | ||||
| class StockLocationScanInItemsHandler extends BarcodeScanStockItemHandler { | ||||
|  | ||||
|   StockLocationScanInItemsHandler(this.location); | ||||
|  | ||||
|   final InvenTreeStockLocation location; | ||||
|  | ||||
|   @override | ||||
|   String getOverlayText(BuildContext context) => L10().barcodeScanItem; | ||||
|  | ||||
|   @override | ||||
|   Future<bool> onItemScanned(int itemId) async { | ||||
|  | ||||
|     final InvenTreeStockItem? item = await InvenTreeStockItem().get(itemId) as InvenTreeStockItem?; | ||||
|     final bool confirm = await InvenTreeSettingsManager().getBool(INV_STOCK_CONFIRM_SCAN, false); | ||||
|  | ||||
|     bool result = false; | ||||
|  | ||||
|     if (item != null) { | ||||
|  | ||||
|       // Item is already *in* the specified location | ||||
|       if (item.locationId == location.pk) { | ||||
|         barcodeFailureTone(); | ||||
|         showSnackIcon(L10().itemInLocation, success: true); | ||||
|         return false; | ||||
|       } else { | ||||
|         if (confirm) { | ||||
|           Map<String, dynamic> fields = item.transferFields(); | ||||
|  | ||||
|           // Override location with provided location value | ||||
|           fields["location"]?["value"] = location.pk; | ||||
|  | ||||
|           launchApiForm( | ||||
|               OneContext().context!, | ||||
|               L10().transferStock, | ||||
|               InvenTreeStockItem.transferStockUrl(), | ||||
|               fields, | ||||
|               method: "POST", | ||||
|               icon: FontAwesomeIcons.dolly, | ||||
|               onSuccess: (data) async { | ||||
|                 showSnackIcon(L10().stockItemUpdated, success: true); | ||||
|               } | ||||
|           ); | ||||
|  | ||||
|           return true; | ||||
|  | ||||
|         } else { | ||||
|           result = await item.transferStock(location.pk); | ||||
|  | ||||
|           showSnackIcon( | ||||
|               result ? L10().barcodeScanIntoLocationSuccess : L10().barcodeScanIntoLocationFailure, | ||||
|               success: result | ||||
|           ); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // We always return false here, to ensure the barcode scan dialog remains open | ||||
|     return false; | ||||
|   } | ||||
| } | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Barcode handler class for scanning a StockLocation into another StockLocation | ||||
|  * | ||||
|  * - The class is initialized by passing a valid StockLocation object | ||||
|  * - Expects to scan barcode for another *parent* StockLocation | ||||
|  * - The scanned StockLocation is set as the "parent" of the provided StockLocation | ||||
|  */ | ||||
| class ScanParentLocationHandler extends BarcodeScanStockLocationHandler { | ||||
|  | ||||
|   ScanParentLocationHandler(this.location); | ||||
|  | ||||
|   final InvenTreeStockLocation location; | ||||
|  | ||||
|   @override | ||||
|   Future<bool> onLocationScanned(int locationId) async { | ||||
|  | ||||
|     final response = await location.update( | ||||
|       values: { | ||||
|         "parent": locationId.toString(), | ||||
|       }, | ||||
|       expectedStatusCode: null, | ||||
|     ); | ||||
|  | ||||
|     switch (response.statusCode) { | ||||
|       case 200: | ||||
|       case 201: | ||||
|         barcodeSuccess(L10().barcodeScanIntoLocationSuccess); | ||||
|         return true; | ||||
|       case 400:  // Invalid parent location chosen | ||||
|         barcodeFailureTone(); | ||||
|         showSnackIcon(L10().invalidStockLocation, success: false); | ||||
|         return false; | ||||
|       default: | ||||
|         barcodeFailureTone(); | ||||
|         showSnackIcon( | ||||
|             L10().barcodeScanIntoLocationFailure, | ||||
|             success: false, | ||||
|             actionText: L10().details, | ||||
|             onAction: () { | ||||
|               showErrorDialog( | ||||
|                 L10().barcodeError, | ||||
|                 response: response, | ||||
|               ); | ||||
|             } | ||||
|         ); | ||||
|         return false; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -145,6 +145,50 @@ class InvenTreeStockItem extends InvenTreeModel { | ||||
|   @override | ||||
|   List<String> get rolesRequired => ["stock"]; | ||||
|  | ||||
|   // Return a set of fields to transfer this stock item via dialog | ||||
|   Map<String, dynamic> transferFields() { | ||||
|     Map<String, dynamic> fields = { | ||||
|       "pk": { | ||||
|         "parent": "items", | ||||
|         "nested": true, | ||||
|         "hidden": true, | ||||
|         "value": pk, | ||||
|       }, | ||||
|       "quantity": { | ||||
|         "parent": "items", | ||||
|         "nested": true, | ||||
|         "value": quantity, | ||||
|       }, | ||||
|       "location": { | ||||
|         "value": locationId, | ||||
|       }, | ||||
|       "status": { | ||||
|         "parent": "items", | ||||
|         "nested": true, | ||||
|         "value": status, | ||||
|       }, | ||||
|       "packaging": { | ||||
|         "parent": "items", | ||||
|         "nested": true, | ||||
|         "value": packaging, | ||||
|       }, | ||||
|       "notes": {}, | ||||
|     }; | ||||
|  | ||||
|     if (isSerialized()) { | ||||
|       // Prevent editing of 'quantity' field if the item is serialized | ||||
|       fields["quantity"]["hidden"] = true; | ||||
|     } | ||||
|  | ||||
|     // Old API does not support these fields | ||||
|     if (!api.supportsStockAdjustExtraFields) { | ||||
|       fields.remove("packaging"); | ||||
|       fields.remove("status"); | ||||
|     } | ||||
|  | ||||
|     return fields; | ||||
|   } | ||||
|  | ||||
|   // URLs for performing stock actions | ||||
|   static String transferStockUrl() => "stock/transfer/"; | ||||
|  | ||||
|   | ||||
| @@ -240,6 +240,12 @@ | ||||
|   "configureServer": "Configure server settings", | ||||
|   "@configureServer": {}, | ||||
|  | ||||
|   "confirmScan": "Confirm Transfer", | ||||
|   "@confirmScan": {}, | ||||
|  | ||||
|   "confirmScanDetail": "Confirm stock transfer details when scanning barcodes", | ||||
|   "@confirmScan": {}, | ||||
|  | ||||
|   "connectionRefused": "Connection Refused", | ||||
|   "@connectionRefused": {}, | ||||
|  | ||||
|   | ||||
| @@ -35,6 +35,7 @@ const String INV_PART_SHOW_BOM = "partShowBom"; | ||||
| // Stock settings | ||||
| const String INV_STOCK_SHOW_HISTORY = "stockShowHistory"; | ||||
| const String INV_STOCK_SHOW_TESTS = "stockShowTests"; | ||||
| const String INV_STOCK_CONFIRM_SCAN = "stockConfirmScan"; | ||||
|  | ||||
| const String INV_REPORT_ERRORS = "reportErrors"; | ||||
| const String INV_STRICT_HTTPS = "strictHttps"; | ||||
|   | ||||
| @@ -110,7 +110,7 @@ class _InvenTreeBarcodeSettingsState extends State<InvenTreeBarcodeSettingsWidge | ||||
|     } | ||||
|  | ||||
|     return Scaffold( | ||||
|       appBar: AppBar(title: Text(L10().barcodes)), | ||||
|       appBar: AppBar(title: Text(L10().barcodeSettings)), | ||||
|       body: Container( | ||||
|         child: ListView( | ||||
|           children: [ | ||||
|   | ||||
| @@ -19,6 +19,7 @@ class _InvenTreePartSettingsState extends State<InvenTreePartSettingsWidget> { | ||||
|   bool partShowBom = true; | ||||
|   bool stockShowHistory = false; | ||||
|   bool stockShowTests = false; | ||||
|   bool stockConfirmScan = false; | ||||
|  | ||||
|   @override | ||||
|   void initState() { | ||||
| @@ -32,6 +33,7 @@ class _InvenTreePartSettingsState extends State<InvenTreePartSettingsWidget> { | ||||
|     partShowBom = await InvenTreeSettingsManager().getValue(INV_PART_SHOW_BOM, true) as bool; | ||||
|     stockShowHistory = await InvenTreeSettingsManager().getValue(INV_STOCK_SHOW_HISTORY, false) as bool; | ||||
|     stockShowTests = await InvenTreeSettingsManager().getValue(INV_STOCK_SHOW_TESTS, true) as bool; | ||||
|     stockConfirmScan = await InvenTreeSettingsManager().getValue(INV_STOCK_CONFIRM_SCAN, false) as bool; | ||||
|  | ||||
|     if (mounted) { | ||||
|       setState(() { | ||||
| @@ -42,7 +44,7 @@ class _InvenTreePartSettingsState extends State<InvenTreePartSettingsWidget> { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Scaffold( | ||||
|       appBar: AppBar(title: Text(L10().part)), | ||||
|       appBar: AppBar(title: Text(L10().partSettings)), | ||||
|       body: Container( | ||||
|         child: ListView( | ||||
|           children: [ | ||||
| @@ -74,6 +76,7 @@ class _InvenTreePartSettingsState extends State<InvenTreePartSettingsWidget> { | ||||
|                 }, | ||||
|               ), | ||||
|             ), | ||||
|             Divider(), | ||||
|             ListTile( | ||||
|               title: Text(L10().stockItemHistory), | ||||
|               subtitle: Text(L10().stockItemHistoryDetail), | ||||
| @@ -101,6 +104,20 @@ class _InvenTreePartSettingsState extends State<InvenTreePartSettingsWidget> { | ||||
|                   }); | ||||
|                 }, | ||||
|               ), | ||||
|             ), | ||||
|             ListTile( | ||||
|               title: Text(L10().confirmScan), | ||||
|               subtitle: Text(L10().confirmScanDetail), | ||||
|               leading: FaIcon(FontAwesomeIcons.qrcode), | ||||
|               trailing: Switch( | ||||
|                 value: stockConfirmScan, | ||||
|                 onChanged: (bool value) { | ||||
|                   InvenTreeSettingsManager().setValue(INV_STOCK_CONFIRM_SCAN, value); | ||||
|                   setState(() { | ||||
|                     stockConfirmScan = value; | ||||
|                   }); | ||||
|                 } | ||||
|               ), | ||||
|             ) | ||||
|           ] | ||||
|         ) | ||||
|   | ||||
| @@ -6,6 +6,7 @@ import "package:font_awesome_flutter/font_awesome_flutter.dart"; | ||||
| import "package:inventree/app_colors.dart"; | ||||
| import "package:inventree/barcode/barcode.dart"; | ||||
| import "package:inventree/barcode/purchase_order.dart"; | ||||
| import "package:inventree/barcode/stock.dart"; | ||||
| import "package:inventree/l10.dart"; | ||||
|  | ||||
| import "package:inventree/inventree/stock.dart"; | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import "package:font_awesome_flutter/font_awesome_flutter.dart"; | ||||
|  | ||||
| import "package:inventree/app_colors.dart"; | ||||
| import "package:inventree/barcode/barcode.dart"; | ||||
| import "package:inventree/barcode/stock.dart"; | ||||
| import "package:inventree/helpers.dart"; | ||||
| import "package:inventree/l10.dart"; | ||||
| import "package:inventree/api.dart"; | ||||
| @@ -436,44 +437,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> { | ||||
|    */ | ||||
|   Future <void> _transferStockDialog(BuildContext context) async { | ||||
|  | ||||
|     Map<String, dynamic> fields = { | ||||
|       "pk": { | ||||
|         "parent": "items", | ||||
|         "nested": true, | ||||
|         "hidden": true, | ||||
|         "value": widget.item.pk, | ||||
|       }, | ||||
|       "quantity": { | ||||
|         "parent": "items", | ||||
|         "nested": true, | ||||
|         "value": widget.item.quantity, | ||||
|       }, | ||||
|       "location": { | ||||
|         "value": widget.item.locationId, | ||||
|       }, | ||||
|       "status": { | ||||
|         "parent": "items", | ||||
|         "nested": true, | ||||
|         "value": widget.item.status, | ||||
|       }, | ||||
|       "packaging": { | ||||
|         "parent": "items", | ||||
|         "nested": true, | ||||
|         "value": widget.item.packaging, | ||||
|       }, | ||||
|       "notes": {}, | ||||
|     }; | ||||
|  | ||||
|     if (widget.item.isSerialized()) { | ||||
|       // Prevent editing of 'quantity' field if the item is serialized | ||||
|       fields["quantity"]["hidden"] = true; | ||||
|     } | ||||
|  | ||||
|     // Old API does not support these fields | ||||
|     if (!api.supportsStockAdjustExtraFields) { | ||||
|       fields.remove("packaging"); | ||||
|       fields.remove("status"); | ||||
|     } | ||||
|     Map<String, dynamic> fields = widget.item.transferFields(); | ||||
|  | ||||
|     launchApiForm( | ||||
|         context, | ||||
|   | ||||
| @@ -8,9 +8,11 @@ | ||||
| import "package:flutter_test/flutter_test.dart"; | ||||
|  | ||||
| import "package:inventree/api.dart"; | ||||
| import "package:inventree/barcode/barcode.dart"; | ||||
| import "package:inventree/helpers.dart"; | ||||
|  | ||||
| import "package:inventree/barcode/barcode.dart"; | ||||
| import "package:inventree/barcode/stock.dart"; | ||||
|  | ||||
| import "package:inventree/inventree/part.dart"; | ||||
| import "package:inventree/inventree/stock.dart"; | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user