mirror of
				https://github.com/inventree/inventree-app.git
				synced 2025-10-31 13:25:40 +00:00 
			
		
		
		
	Sales order barcode (#466)
* Add barcode handler for allocating stock to sales order * Refactor sales order allocation fields * Improve barcode handling * Handle barcode scan from sales order line detail view * Remove debug statements
This commit is contained in:
		| @@ -336,6 +336,9 @@ class InvenTreeAPI { | ||||
|   // Does the server support adding line items to a PO using barcodes? | ||||
|   bool get supportsBarcodePOAddLineEndpoint => isConnected() && apiVersion >= 153; | ||||
|  | ||||
|   // Does the server support allocating stock to sales order using barcodes? | ||||
|   bool get supportsBarcodeSOAllocateEndpoint => isConnected() && apiVersion >= 160; | ||||
|  | ||||
|   // Cached list of plugins (refreshed when we connect to the server) | ||||
|   List<InvenTreePlugin> _plugins = []; | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,6 @@ | ||||
| import "package:flutter/material.dart"; | ||||
| import "package:font_awesome_flutter/font_awesome_flutter.dart"; | ||||
| import "package:inventree/api_form.dart"; | ||||
|  | ||||
| import "package:inventree/inventree/part.dart"; | ||||
| import "package:inventree/inventree/sales_order.dart"; | ||||
| @@ -77,5 +79,98 @@ class SOAddItemBarcodeHandler extends BarcodeHandler { | ||||
|     } | ||||
|  | ||||
|   } | ||||
| } | ||||
|  | ||||
|  | ||||
| class SOAllocateStockHandler extends BarcodeHandler { | ||||
|  | ||||
|   SOAllocateStockHandler({this.salesOrder, this.lineItem, this.shipment}); | ||||
|  | ||||
|   InvenTreeSalesOrder? salesOrder; | ||||
|   InvenTreeSOLineItem? lineItem; | ||||
|   InvenTreeSalesOrderShipment? shipment; | ||||
|  | ||||
|   @override | ||||
|   String getOverlayText(BuildContext context) => L10().allocateStock; | ||||
|  | ||||
|   @override | ||||
|   Future<void> processBarcode(String barcode, | ||||
|   { | ||||
|     String url = "barcode/so-allocate/", | ||||
|     Map<String, dynamic> extra_data = const {}}) { | ||||
|  | ||||
|     final so_extra_data = { | ||||
|       "sales_order": salesOrder?.pk, | ||||
|       "shipment": shipment?.pk, | ||||
|       "line": lineItem?.pk, | ||||
|       ...extra_data | ||||
|     }; | ||||
|  | ||||
|     return super.processBarcode(barcode, url: url, extra_data: so_extra_data); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Future<void> onBarcodeMatched(Map<String, dynamic> data) async { | ||||
|     if (!data.containsKey("line_item")) { | ||||
|       return onBarcodeUnknown(data); | ||||
|     } | ||||
|  | ||||
|     barcodeSuccessTone(); | ||||
|     showSnackIcon(L10().allocated, success: true); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Future<void> onBarcodeUnhandled(Map<String, dynamic> data) async { | ||||
|  | ||||
|     if (!data.containsKey("action_required") || !data.containsKey("line_item")) { | ||||
|       return super.onBarcodeUnhandled(data); | ||||
|     } | ||||
|  | ||||
|     // Prompt user for extra information to create the allocation | ||||
|     var fields = InvenTreeSOLineItem().allocateFormFields(); | ||||
|  | ||||
|     // Update fields with data gathered from the API response | ||||
|     fields["line_item"]?["value"] = data["line_item"]; | ||||
|  | ||||
|     Map<String, dynamic> stock_filters = { | ||||
|       "in_stock": true, | ||||
|       "available": true, | ||||
|     }; | ||||
|  | ||||
|     if (data.containsKey("part")) { | ||||
|       stock_filters["part"] = data["part"]; | ||||
|     } | ||||
|  | ||||
|     fields["stock_item"]?["filters"] = stock_filters; | ||||
|     fields["stock_item"]?["value"] = data["stock_item"]; | ||||
|  | ||||
|     fields["quantity"]?["value"] = data["quantity"]; | ||||
|  | ||||
|     fields["shipment"]?["value"] = data["shipment"]; | ||||
|     fields["shipment"]?["filters"] = { | ||||
|       "order": salesOrder!.pk.toString() | ||||
|     }; | ||||
|  | ||||
|     final context = OneContext().context!; | ||||
|  | ||||
|     launchApiForm( | ||||
|       context, | ||||
|       L10().allocateStock, | ||||
|       salesOrder!.allocate_url, | ||||
|     fields, | ||||
|     method: "POST", | ||||
|     icon: FontAwesomeIcons.rightToBracket, | ||||
|     onSuccess: (data) async { | ||||
|         showSnackIcon(L10().allocated, success: true); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Future<void> onBarcodeUnknown(Map<String, dynamic> data) async { | ||||
|     barcodeFailureTone(); | ||||
|     showSnackIcon( | ||||
|         data["error"] as String? ?? L10().barcodeError, | ||||
|         success: false | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -132,6 +132,29 @@ class InvenTreeSOLineItem extends InvenTreeOrderLine { | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   Map<String, Map<String, dynamic>> allocateFormFields() { | ||||
|  | ||||
|     return { | ||||
|       "line_item": { | ||||
|         "parent": "items", | ||||
|         "nested": true, | ||||
|         "hidden": true, | ||||
|       }, | ||||
|       "stock_item": { | ||||
|         "parent": "items", | ||||
|         "nested": true, | ||||
|         "filters": {}, | ||||
|       }, | ||||
|       "quantity": { | ||||
|         "parent": "items", | ||||
|         "nested": true, | ||||
|       }, | ||||
|       "shipment": { | ||||
|         "filters": {} | ||||
|       } | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Map<String, String> defaultGetFilters() { | ||||
|     return { | ||||
|   | ||||
| @@ -146,6 +146,23 @@ class _SalesOrderDetailState extends RefreshableState<SalesOrderDetailWidget> { | ||||
|           } | ||||
|         ) | ||||
|       ); | ||||
|  | ||||
|       if (api.supportsBarcodeSOAllocateEndpoint) { | ||||
|         actions.add( | ||||
|           SpeedDialChild( | ||||
|             child: FaIcon(FontAwesomeIcons.rightToBracket), | ||||
|             label: L10().allocateStock, | ||||
|             onTap: () async { | ||||
|               scanBarcode( | ||||
|                 context, | ||||
|                 handler: SOAllocateStockHandler( | ||||
|                   salesOrder: widget.order, | ||||
|                 ) | ||||
|               ); | ||||
|             } | ||||
|           ) | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return actions; | ||||
|   | ||||
| @@ -7,6 +7,8 @@ | ||||
| import "package:flutter/material.dart"; | ||||
| import "package:flutter_speed_dial/flutter_speed_dial.dart"; | ||||
| import "package:font_awesome_flutter/font_awesome_flutter.dart"; | ||||
| import "package:inventree/barcode/barcode.dart"; | ||||
| import "package:inventree/barcode/sales_order.dart"; | ||||
|  | ||||
| import "package:inventree/inventree/part.dart"; | ||||
| import "package:inventree/inventree/sales_order.dart"; | ||||
| @@ -66,31 +68,17 @@ class _SOLineDetailWidgetState extends RefreshableState<SoLineDetailWidget> { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     Map<String, dynamic> fields = { | ||||
|       "line_item": { | ||||
|         "parent": "items", | ||||
|         "nested": true, | ||||
|         "hidden": true, | ||||
|         "value": widget.item.pk, | ||||
|       }, | ||||
|       "stock_item": { | ||||
|         "parent": "items", | ||||
|         "nested": true, | ||||
|         "filters": { | ||||
|           "part": widget.item.partId, | ||||
|           "in_stock": true, | ||||
|         } | ||||
|       }, | ||||
|       "quantity": { | ||||
|         "parent": "items", | ||||
|         "nested": true, | ||||
|         "value": widget.item.unallocatedQuantity, | ||||
|       }, | ||||
|       "shipment": { | ||||
|         "filters": { | ||||
|           "order": order!.pk.toString(), | ||||
|         } | ||||
|       }, | ||||
|     var fields = InvenTreeSOLineItem().allocateFormFields(); | ||||
|  | ||||
|     fields["line_item"]?["value"] = widget.item.pk.toString(); | ||||
|     fields["stock_item"]?["filters"] = { | ||||
|       "in_stock": true, | ||||
|       "available": true, | ||||
|       "part": widget.item.partId.toString() | ||||
|     }; | ||||
|     fields["quantity"]?["value"] = widget.item.unallocatedQuantity.toString(); | ||||
|     fields["shipment"]?["filters"] = { | ||||
|       "order": order!.pk.toString() | ||||
|     }; | ||||
|  | ||||
|     launchApiForm( | ||||
| @@ -146,6 +134,34 @@ class _SOLineDetailWidgetState extends RefreshableState<SoLineDetailWidget> { | ||||
|     return buttons; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   List<SpeedDialChild> barcodeButtons(BuildContext context) { | ||||
|     List<SpeedDialChild> actions = []; | ||||
|  | ||||
|     if (order != null && order!.isOpen && InvenTreeSOLineItem().canCreate) { | ||||
|  | ||||
|       if (api.supportsBarcodeSOAllocateEndpoint) { | ||||
|         actions.add( | ||||
|             SpeedDialChild( | ||||
|               child: FaIcon(FontAwesomeIcons.rightToBracket), | ||||
|               label: L10().allocateStock, | ||||
|               onTap: () async { | ||||
|                 scanBarcode( | ||||
|                   context, | ||||
|                   handler: SOAllocateStockHandler( | ||||
|                     salesOrder: order, | ||||
|                     lineItem: widget.item | ||||
|                   ) | ||||
|                 ); | ||||
|               } | ||||
|             ) | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return actions; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Future<void> request(BuildContext context) async { | ||||
|     await widget.item.reload(); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user