mirror of
				https://github.com/inventree/inventree-app.git
				synced 2025-10-31 13:25:40 +00:00 
			
		
		
		
	Add POReceiveBarcodeHandler to support barcode/po-receive/ endpoint (#421)
* Add POReceiveBarcodeHandler to support barcode/po-receive/ endpoint * Remove german translation * Add api version checks * Add getOverlayText method to barcode handler * Bump required API version to 139 * Update barcode.dart The "quantity" field is not an integer, and can cause the app to crash if not handled correctly --------- Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
This commit is contained in:
		| @@ -328,6 +328,8 @@ class InvenTreeAPI { | ||||
|   // Does the server support extra fields on stock adjustment actions? | ||||
|   bool get supportsStockAdjustExtraFields => isConnected() && apiVersion >= 133; | ||||
|  | ||||
|   bool get supportsBarcodePOReceiveEndpoint => isConnected() && apiVersion >= 139; | ||||
|  | ||||
|   // Are plugins enabled on the server? | ||||
|   bool _pluginsEnabled = false; | ||||
|  | ||||
|   | ||||
| @@ -6,6 +6,7 @@ import "package:one_context/one_context.dart"; | ||||
|  | ||||
|  | ||||
| import "package:inventree/api.dart"; | ||||
| import "package:inventree/api_form.dart"; | ||||
| import "package:inventree/helpers.dart"; | ||||
| import "package:inventree/l10.dart"; | ||||
|  | ||||
| @@ -462,6 +463,116 @@ class ScanParentLocationHandler extends BarcodeScanStockLocationHandler { | ||||
| } | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Barcode handler class for scanning a supplier barcode to receive a part | ||||
|  * | ||||
|  * - The class can be initialized by optionally passing a valid, placed PurchaseOrder object | ||||
|  * - Expects to scan supplier barcode, possibly containing order_number and quantity | ||||
|  * - If location or quantity information wasn't provided, show a form to fill it in | ||||
|  */ | ||||
| class POReceiveBarcodeHandler extends BarcodeHandler { | ||||
|  | ||||
|   POReceiveBarcodeHandler({this.purchaseOrder, this.location}); | ||||
|  | ||||
|   InvenTreePurchaseOrder? purchaseOrder; | ||||
|   InvenTreeStockLocation? location; | ||||
|  | ||||
|   @override | ||||
|   String getOverlayText(BuildContext context) => L10().barcodeReceivePart; | ||||
|  | ||||
|   @override | ||||
|   Future<void> processBarcode(String barcode, | ||||
|       {String url = "barcode/po-receive/", | ||||
|       Map<String, dynamic> extra_data = const {}}) { | ||||
|  | ||||
|     final po_extra_data = { | ||||
|       "purchase_order": purchaseOrder?.pk, | ||||
|       "location": location?.pk, | ||||
|       ...extra_data, | ||||
|     }; | ||||
|  | ||||
|     return super.processBarcode(barcode, url: url, extra_data: po_extra_data); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Future<void> onBarcodeMatched(Map<String, dynamic> data) async { | ||||
|       if (!data.containsKey("lineitem")) { | ||||
|         return onBarcodeUnknown(data); | ||||
|       } | ||||
|  | ||||
|       barcodeSuccessTone(); | ||||
|       showSnackIcon(L10().receivedItem, success: true); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Future<void> onBarcodeUnhandled(Map<String, dynamic> data) async { | ||||
|     if (!data.containsKey("action_required") || !data.containsKey("lineitem")) { | ||||
|       return super.onBarcodeUnhandled(data); | ||||
|     } | ||||
|  | ||||
|     final lineItemData = data["lineitem"] as Map<String, dynamic>; | ||||
|     if (!lineItemData.containsKey("pk") || !lineItemData.containsKey("purchase_order")) { | ||||
|       barcodeFailureTone(); | ||||
|       showSnackIcon(L10().missingData, success: false); | ||||
|     } | ||||
|  | ||||
|     // Construct fields to receive | ||||
|     Map<String, dynamic> fields = { | ||||
|       "line_item": { | ||||
|         "parent": "items", | ||||
|         "nested": true, | ||||
|         "hidden": true, | ||||
|         "value": lineItemData["pk"] as int, | ||||
|       }, | ||||
|       "quantity": { | ||||
|         "parent": "items", | ||||
|         "nested": true, | ||||
|         "value": lineItemData["quantity"] as double?, | ||||
|       }, | ||||
|       "status": { | ||||
|         "parent": "items", | ||||
|         "nested": true, | ||||
|       }, | ||||
|       "location": { | ||||
|         "value": lineItemData["location"] as int?, | ||||
|       }, | ||||
|       "barcode": { | ||||
|         "parent": "items", | ||||
|         "nested": true, | ||||
|         "hidden": true, | ||||
|         "type": "barcode", | ||||
|         "value": data["barcode_data"] as String, | ||||
|       } | ||||
|     }; | ||||
|  | ||||
|     final context = OneContext().context!; | ||||
|     final purchase_order_pk = lineItemData["purchase_order"]; | ||||
|     final receive_url = "${InvenTreePurchaseOrder().URL}${purchase_order_pk}/receive/"; | ||||
|  | ||||
|     launchApiForm( | ||||
|       context, | ||||
|       L10().receiveItem, | ||||
|       receive_url, | ||||
|       fields, | ||||
|       method: "POST", | ||||
|       icon: FontAwesomeIcons.rightToBracket, | ||||
|       onSuccess: (data) async { | ||||
|         showSnackIcon(L10().receivedItem, success: true); | ||||
|       } | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Future<void> onBarcodeUnknown(Map<String, dynamic> data) async { | ||||
|     barcodeFailureTone(); | ||||
|     showSnackIcon( | ||||
|       data["error"] as String? ?? L10().barcodeError, | ||||
|       success: false | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Barcode handler for finding a "unique" barcode (one that does not match an item in the database) | ||||
|  */ | ||||
|   | ||||
| @@ -58,8 +58,9 @@ class BarcodeHandler { | ||||
|     * | ||||
|     * Returns true only if the barcode scanner should remain open | ||||
|     */ | ||||
|   Future<void> processBarcode(String barcode, {String url = "barcode/"}) async { | ||||
|  | ||||
|   Future<void> processBarcode(String barcode, | ||||
|       {String url = "barcode/", | ||||
|       Map<String, dynamic> extra_data = const {}}) async { | ||||
|     debug("Scanned barcode data: '${barcode}'"); | ||||
|  | ||||
|     barcode = barcode.trim(); | ||||
| @@ -82,6 +83,7 @@ class BarcodeHandler { | ||||
|         url, | ||||
|         body: { | ||||
|           "barcode": barcode, | ||||
|           ...extra_data, | ||||
|         }, | ||||
|         expectedStatusCode: null,  // Do not show an error on "unexpected code" | ||||
|     ); | ||||
|   | ||||
| @@ -112,6 +112,9 @@ | ||||
|   "barcodeNotAssigned": "Barcode not assigned", | ||||
|   "@barcodeNotAssigned": {}, | ||||
|  | ||||
|   "barcodeReceivePart": "Scan barcode to receive part", | ||||
|   "@barcodeReceivePart": {}, | ||||
|  | ||||
|   "barcodeScanAssign": "Scan to assign barcode", | ||||
|   "@barcodeScanAssign": {}, | ||||
|  | ||||
| @@ -988,6 +991,9 @@ | ||||
|   "scanIntoLocationDetail": "Scan this item into location", | ||||
|   "@scanIntoLocationDetail": {}, | ||||
|  | ||||
|   "scanReceivedParts": "Scan Received Parts", | ||||
|   "@scanReceivedParts": {}, | ||||
|  | ||||
|   "search": "Search", | ||||
|   "@search": { | ||||
|     "description": "search" | ||||
|   | ||||
| @@ -104,6 +104,21 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> { | ||||
|         ); | ||||
|       } | ||||
|  | ||||
|       if (api.supportsBarcodePOReceiveEndpoint) { | ||||
|         actions.add( | ||||
|           SpeedDialChild( | ||||
|             child: Icon(Icons.barcode_reader), | ||||
|             label: L10().scanReceivedParts, | ||||
|             onTap:() async { | ||||
|               scanBarcode( | ||||
|                 context, | ||||
|                 handler: POReceiveBarcodeHandler(location: location), | ||||
|               ); | ||||
|             }, | ||||
|           ) | ||||
|         ); | ||||
|       } | ||||
|  | ||||
|       // Scan this location into another one | ||||
|       if (InvenTreeStockLocation().canEdit) { | ||||
|         actions.add( | ||||
|   | ||||
| @@ -6,6 +6,7 @@ import "package:inventree/widget/po_line_list.dart"; | ||||
|  | ||||
| import "package:inventree/api.dart"; | ||||
| import "package:inventree/app_colors.dart"; | ||||
| import "package:inventree/barcode/barcode.dart"; | ||||
| import "package:inventree/helpers.dart"; | ||||
| import "package:inventree/l10.dart"; | ||||
|  | ||||
| @@ -132,6 +133,29 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   List<SpeedDialChild> barcodeButtons(BuildContext context) { | ||||
|     List<SpeedDialChild> actions = []; | ||||
|  | ||||
|     if (api.supportsBarcodePOReceiveEndpoint) { | ||||
|       actions.add( | ||||
|         SpeedDialChild( | ||||
|           child: Icon(Icons.barcode_reader), | ||||
|           label: L10().scanReceivedParts, | ||||
|           onTap:() async { | ||||
|             scanBarcode( | ||||
|               context, | ||||
|               handler: POReceiveBarcodeHandler(purchaseOrder: order), | ||||
|             ); | ||||
|           }, | ||||
|         ) | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return actions; | ||||
|   } | ||||
|  | ||||
|  | ||||
|   @override | ||||
|   Future<void> request(BuildContext context) async { | ||||
|     await order.reload(); | ||||
|   | ||||
| @@ -9,6 +9,7 @@ import "package:inventree/widget/purchase_order_detail.dart"; | ||||
| import "package:inventree/widget/refreshable_state.dart"; | ||||
| import "package:inventree/l10.dart"; | ||||
| import "package:inventree/api.dart"; | ||||
| import "package:inventree/barcode/barcode.dart"; | ||||
| import "package:inventree/inventree/purchase_order.dart"; | ||||
|  | ||||
| /* | ||||
| @@ -79,6 +80,28 @@ class _PurchaseOrderListWidgetState extends RefreshableState<PurchaseOrderListWi | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   List<SpeedDialChild> barcodeButtons(BuildContext context) { | ||||
|     List<SpeedDialChild> actions = []; | ||||
|  | ||||
|     if (api.supportsBarcodePOReceiveEndpoint) { | ||||
|       actions.add( | ||||
|         SpeedDialChild( | ||||
|           child: Icon(Icons.barcode_reader), | ||||
|           label: L10().scanReceivedParts, | ||||
|           onTap:() async { | ||||
|             scanBarcode( | ||||
|               context, | ||||
|               handler: POReceiveBarcodeHandler(), | ||||
|             ); | ||||
|           }, | ||||
|         ) | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return actions; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget getBody(BuildContext context) { | ||||
|     return PaginatedPurchaseOrderList(filters); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user