mirror of
				https://github.com/inventree/inventree-app.git
				synced 2025-10-31 21:35:42 +00:00 
			
		
		
		
	Merge pull request #140 from inventree/legacy-api
Remove support for legacy stock transfer API code
This commit is contained in:
		| @@ -5,6 +5,9 @@ | ||||
| --- | ||||
|  | ||||
| - Fixes issue which prevented text input in search window | ||||
| - Remove support for legacy stock adjustment API | ||||
| - App now requires server API version 20 (or newer) | ||||
| - Updated translation files | ||||
|  | ||||
| ### 0.7.0 - May 2022 | ||||
| --- | ||||
|   | ||||
| @@ -144,7 +144,7 @@ class InvenTreeAPI { | ||||
|   InvenTreeAPI._internal(); | ||||
|  | ||||
|   // Minimum required API version for server | ||||
|   static const _minApiVersion = 7; | ||||
|   static const _minApiVersion = 20; | ||||
|  | ||||
|   bool _strictHttps = false; | ||||
|  | ||||
| @@ -294,9 +294,6 @@ class InvenTreeAPI { | ||||
|   // API endpoint for receiving purchase order line items was introduced in v12 | ||||
|   bool get supportsPoReceive => apiVersion >= 12; | ||||
|  | ||||
|   // "Modern" API transactions were implemented in API v14 | ||||
|   bool get supportsModernStockTransactions => apiVersion >= 14; | ||||
|  | ||||
|   /* | ||||
|    * Connect to the remote InvenTree server: | ||||
|    * | ||||
|   | ||||
| @@ -1161,6 +1161,72 @@ class _APIFormWidgetState extends State<APIFormWidget> { | ||||
|     nonFieldErrors = errors; | ||||
|   } | ||||
|  | ||||
|   /* Check for errors relating to an *unhandled* field name | ||||
|   * These errors will not be displayed and potentially confuse the user | ||||
|   * So, we need to know if these are ever happening | ||||
|   */ | ||||
|   void checkInvalidErrors(APIResponse response) { | ||||
|     var errors = response.asMap(); | ||||
|  | ||||
|     for (String fieldName in errors.keys) { | ||||
|  | ||||
|       bool match = false; | ||||
|  | ||||
|       switch (fieldName) { | ||||
|         case "__all__": | ||||
|         case "non_field_errors": | ||||
|         case "errors": | ||||
|           // ignore these global fields | ||||
|           match = true; | ||||
|           continue; | ||||
|         default: | ||||
|           for (var field in fields) { | ||||
|  | ||||
|             // Hidden fields can't display errors, so we won't match | ||||
|             if (field.hidden) { | ||||
|               continue; | ||||
|             } | ||||
|  | ||||
|             if (field.name == fieldName) { | ||||
|               // Direct Match found! | ||||
|               match = true; | ||||
|               break; | ||||
|             } else if (field.parent == fieldName) { | ||||
|  | ||||
|               var error = errors[fieldName]; | ||||
|  | ||||
|               if (error is List) { | ||||
|                 for (var el in error) { | ||||
|                   if (el is Map && el.containsKey(field.name)) { | ||||
|                     match = true; | ||||
|                     break; | ||||
|                   } | ||||
|                 } | ||||
|               } else if (error is Map && error.containsKey(field.name)) { | ||||
|                 match = true; | ||||
|                 break; | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|  | ||||
|           break; | ||||
|       } | ||||
|  | ||||
|       if (!match) { | ||||
|         // Match for an unknown / unsupported field | ||||
|         sentryReportMessage( | ||||
|           "API form returned error for unsupported field", | ||||
|           context: { | ||||
|             "url": response.url, | ||||
|             "status_code": response.statusCode.toString(), | ||||
|             "field": fieldName, | ||||
|             "error_message": response.data.toString(), | ||||
|           } | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /* | ||||
|    * Submit the form data to the server, and handle the results | ||||
|    */ | ||||
| @@ -1234,8 +1300,6 @@ class _APIFormWidgetState extends State<APIFormWidget> { | ||||
|         // Hide this form | ||||
|         Navigator.pop(context); | ||||
|  | ||||
|         // TODO: Display a snackBar | ||||
|  | ||||
|         if (successFunc != null) { | ||||
|  | ||||
|           // Ensure the response is a valid JSON structure | ||||
| @@ -1263,7 +1327,7 @@ class _APIFormWidgetState extends State<APIFormWidget> { | ||||
|         } | ||||
|  | ||||
|         extractNonFieldErrors(response); | ||||
|  | ||||
|         checkInvalidErrors(response); | ||||
|         break; | ||||
|       case 401: | ||||
|         showSnackIcon( | ||||
|   | ||||
| @@ -517,7 +517,6 @@ class InvenTreeStockItem extends InvenTreeModel { | ||||
|    * - Remove | ||||
|    * - Count | ||||
|    */ | ||||
|   // TODO: Remove this function when we deprecate support for the old API | ||||
|   Future<bool> adjustStock(BuildContext context, String endpoint, double q, {String? notes, int? location}) async { | ||||
|  | ||||
|     // Serialized stock cannot be adjusted (unless it is a "transfer") | ||||
| @@ -532,46 +531,29 @@ class InvenTreeStockItem extends InvenTreeModel { | ||||
|  | ||||
|     Map<String, dynamic> data = {}; | ||||
|  | ||||
|     // Note: Format of adjustment API was updated in API v14 | ||||
|     if (api.supportsModernStockTransactions) { | ||||
|       // Modern (> 14) API | ||||
|       data = { | ||||
|         "items": [ | ||||
|           { | ||||
|             "pk": "${pk}", | ||||
|             "quantity": "${quantity}", | ||||
|           } | ||||
|         ], | ||||
|       }; | ||||
|     } else { | ||||
|       // Legacy (<= 14) API | ||||
|       data = { | ||||
|         "item": { | ||||
|     data = { | ||||
|       "items": [ | ||||
|         { | ||||
|           "pk": "${pk}", | ||||
|           "quantity": "${quantity}", | ||||
|         }, | ||||
|       }; | ||||
|     } | ||||
|  | ||||
|     data["notes"] = notes ?? ""; | ||||
|         } | ||||
|       ], | ||||
|       "notes": notes ?? "", | ||||
|     }; | ||||
|  | ||||
|     if (location != null) { | ||||
|       data["location"] = location; | ||||
|     } | ||||
|  | ||||
|     // Expected API return code depends on server API version | ||||
|     final int expected_response = api.supportsModernStockTransactions ? 201 : 200; | ||||
|  | ||||
|     var response = await api.post( | ||||
|       endpoint, | ||||
|       body: data, | ||||
|       expectedStatusCode: expected_response, | ||||
|       expectedStatusCode: 200, | ||||
|     ); | ||||
|  | ||||
|     return response.isValid(); | ||||
|   } | ||||
|  | ||||
|   // TODO: Remove this function when we deprecate support for the old API | ||||
|   Future<bool> countStock(BuildContext context, double q, {String? notes}) async { | ||||
|  | ||||
|     final bool result = await adjustStock(context, "/stock/count/", q, notes: notes); | ||||
| @@ -579,7 +561,6 @@ class InvenTreeStockItem extends InvenTreeModel { | ||||
|     return result; | ||||
|   } | ||||
|  | ||||
|   // TODO: Remove this function when we deprecate support for the old API | ||||
|   Future<bool> addStock(BuildContext context, double q, {String? notes}) async { | ||||
|  | ||||
|     final bool result = await adjustStock(context,  "/stock/add/", q, notes: notes); | ||||
| @@ -587,7 +568,6 @@ class InvenTreeStockItem extends InvenTreeModel { | ||||
|     return result; | ||||
|   } | ||||
|  | ||||
|   // TODO: Remove this function when we deprecate support for the old API | ||||
|   Future<bool> removeStock(BuildContext context, double q, {String? notes}) async { | ||||
|  | ||||
|     final bool result = await adjustStock(context, "/stock/remove/", q, notes: notes); | ||||
| @@ -595,7 +575,6 @@ class InvenTreeStockItem extends InvenTreeModel { | ||||
|     return result; | ||||
|   } | ||||
|  | ||||
|   // TODO: Remove this function when we deprecate support for the old API | ||||
|   Future<bool> transferStock(BuildContext context, int location, {double? quantity, String? notes}) async { | ||||
|  | ||||
|     double q = this.quantity; | ||||
|   | ||||
| @@ -1,15 +1,12 @@ | ||||
| import "package:flutter/material.dart"; | ||||
|  | ||||
| import "package:dropdown_search/dropdown_search.dart"; | ||||
| import "package:font_awesome_flutter/font_awesome_flutter.dart"; | ||||
|  | ||||
| import "package:inventree/app_colors.dart"; | ||||
| import "package:inventree/barcode.dart"; | ||||
| import "package:inventree/inventree/model.dart"; | ||||
| import "package:inventree/inventree/stock.dart"; | ||||
| import "package:inventree/inventree/part.dart"; | ||||
| import "package:inventree/widget/dialogs.dart"; | ||||
| import "package:inventree/widget/fields.dart"; | ||||
| import "package:inventree/widget/location_display.dart"; | ||||
| import "package:inventree/widget/part_detail.dart"; | ||||
| import "package:inventree/widget/progress.dart"; | ||||
| @@ -42,14 +39,6 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> { | ||||
|   @override | ||||
|   String getAppBarTitle(BuildContext context) => L10().stockItem; | ||||
|  | ||||
|   final TextEditingController _quantityController = TextEditingController(); | ||||
|   final TextEditingController _notesController = TextEditingController(); | ||||
|  | ||||
|   final _addStockKey = GlobalKey<FormState>(); | ||||
|   final _removeStockKey = GlobalKey<FormState>(); | ||||
|   final _countStockKey = GlobalKey<FormState>(); | ||||
|   final _moveStockKey = GlobalKey<FormState>(); | ||||
|  | ||||
|   bool stockShowHistory = false; | ||||
|  | ||||
|   @override | ||||
| @@ -295,76 +284,37 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> { | ||||
|  | ||||
|   } | ||||
|  | ||||
|   Future <void> _addStock() async { | ||||
|  | ||||
|     double quantity = double.parse(_quantityController.text); | ||||
|     _quantityController.clear(); | ||||
|  | ||||
|     final bool result = await item.addStock(context, quantity, notes: _notesController.text); | ||||
|     _notesController.clear(); | ||||
|  | ||||
|     _stockUpdateMessage(result); | ||||
|  | ||||
|     refresh(context); | ||||
|   } | ||||
|  | ||||
|   /* | ||||
|    * Launch a dialog to 'add' quantity to this StockItem | ||||
|    */ | ||||
|   Future <void> _addStockDialog() async { | ||||
|  | ||||
|     // TODO: In future, deprecate support for older API | ||||
|     if (InvenTreeAPI().supportsModernStockTransactions) { | ||||
|  | ||||
|       Map<String, dynamic> fields = { | ||||
|         "pk": { | ||||
|           "parent": "items", | ||||
|           "nested": true, | ||||
|           "hidden": true, | ||||
|           "value": item.pk, | ||||
|         }, | ||||
|         "quantity": { | ||||
|           "parent": "items", | ||||
|           "nested": true, | ||||
|           "value": 0, | ||||
|         }, | ||||
|         "notes": {}, | ||||
|       }; | ||||
|  | ||||
|       launchApiForm( | ||||
|         context, | ||||
|         L10().addStock, | ||||
|         InvenTreeStockItem.addStockUrl(), | ||||
|         fields, | ||||
|         method: "POST", | ||||
|         icon: FontAwesomeIcons.plusCircle, | ||||
|         onSuccess: (data) async { | ||||
|           _stockUpdateMessage(true); | ||||
|           refresh(context); | ||||
|         } | ||||
|       ); | ||||
|  | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     _quantityController.clear(); | ||||
|     _notesController.clear(); | ||||
|  | ||||
|     showFormDialog( L10().addStock, | ||||
|       key: _addStockKey, | ||||
|       callback: () { | ||||
|         _addStock(); | ||||
|     Map<String, dynamic> fields = { | ||||
|       "pk": { | ||||
|         "parent": "items", | ||||
|         "nested": true, | ||||
|         "hidden": true, | ||||
|         "value": item.pk, | ||||
|       }, | ||||
|       fields: <Widget> [ | ||||
|         Text("Current stock: ${item.quantity}"), | ||||
|         QuantityField( | ||||
|           label: L10().addStock, | ||||
|           controller: _quantityController, | ||||
|         ), | ||||
|         TextFormField( | ||||
|           decoration: InputDecoration( | ||||
|             labelText: L10().notes, | ||||
|           ), | ||||
|           controller: _notesController, | ||||
|         ) | ||||
|       ], | ||||
|       "quantity": { | ||||
|         "parent": "items", | ||||
|         "nested": true, | ||||
|         "value": 0, | ||||
|       }, | ||||
|       "notes": {}, | ||||
|     }; | ||||
|  | ||||
|     launchApiForm( | ||||
|       context, | ||||
|       L10().addStock, | ||||
|       InvenTreeStockItem.addStockUrl(), | ||||
|       fields, | ||||
|       method: "POST", | ||||
|       icon: FontAwesomeIcons.plusCircle, | ||||
|       onSuccess: (data) async { | ||||
|         _stockUpdateMessage(true); | ||||
|         refresh(context); | ||||
|       } | ||||
|     ); | ||||
|   } | ||||
|  | ||||
| @@ -375,149 +325,68 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   Future <void> _removeStock() async { | ||||
|  | ||||
|     double quantity = double.parse(_quantityController.text); | ||||
|     _quantityController.clear(); | ||||
|  | ||||
|     final bool result = await item.removeStock(context, quantity, notes: _notesController.text); | ||||
|  | ||||
|     _stockUpdateMessage(result); | ||||
|  | ||||
|     refresh(context); | ||||
|  | ||||
|   } | ||||
|  | ||||
|   /* | ||||
|    * Launch a dialog to 'remove' quantity from this StockItem | ||||
|    */ | ||||
|   void _removeStockDialog() { | ||||
|  | ||||
|     // TODO: In future, deprecate support for the older API | ||||
|     if (InvenTreeAPI().supportsModernStockTransactions) { | ||||
|       Map<String, dynamic> fields = { | ||||
|         "pk": { | ||||
|           "parent": "items", | ||||
|           "nested": true, | ||||
|           "hidden": true, | ||||
|           "value": item.pk, | ||||
|         }, | ||||
|         "quantity": { | ||||
|           "parent": "items", | ||||
|           "nested": true, | ||||
|           "value": 0, | ||||
|         }, | ||||
|         "notes": {}, | ||||
|       }; | ||||
|     Map<String, dynamic> fields = { | ||||
|       "pk": { | ||||
|         "parent": "items", | ||||
|         "nested": true, | ||||
|         "hidden": true, | ||||
|         "value": item.pk, | ||||
|       }, | ||||
|       "quantity": { | ||||
|         "parent": "items", | ||||
|         "nested": true, | ||||
|         "value": 0, | ||||
|       }, | ||||
|       "notes": {}, | ||||
|     }; | ||||
|  | ||||
|       launchApiForm( | ||||
|           context, | ||||
|           L10().removeStock, | ||||
|           InvenTreeStockItem.removeStockUrl(), | ||||
|           fields, | ||||
|           method: "POST", | ||||
|           icon: FontAwesomeIcons.minusCircle, | ||||
|           onSuccess: (data) async { | ||||
|             _stockUpdateMessage(true); | ||||
|             refresh(context); | ||||
|           } | ||||
|       ); | ||||
|  | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     _quantityController.clear(); | ||||
|     _notesController.clear(); | ||||
|  | ||||
|     showFormDialog(L10().removeStock, | ||||
|         key: _removeStockKey, | ||||
|         callback: () { | ||||
|           _removeStock(); | ||||
|         }, | ||||
|         fields: <Widget>[ | ||||
|           Text("Current stock: ${item.quantity}"), | ||||
|           QuantityField( | ||||
|             label: L10().removeStock, | ||||
|             controller: _quantityController, | ||||
|             max: item.quantity, | ||||
|           ), | ||||
|           TextFormField( | ||||
|             decoration: InputDecoration( | ||||
|               labelText: L10().notes, | ||||
|             ), | ||||
|             controller: _notesController, | ||||
|           ), | ||||
|         ], | ||||
|     launchApiForm( | ||||
|         context, | ||||
|         L10().removeStock, | ||||
|         InvenTreeStockItem.removeStockUrl(), | ||||
|         fields, | ||||
|         method: "POST", | ||||
|         icon: FontAwesomeIcons.minusCircle, | ||||
|         onSuccess: (data) async { | ||||
|           _stockUpdateMessage(true); | ||||
|           refresh(context); | ||||
|         } | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Future <void> _countStock() async { | ||||
|  | ||||
|     double quantity = double.parse(_quantityController.text); | ||||
|     _quantityController.clear(); | ||||
|  | ||||
|     final bool result = await item.countStock(context, quantity, notes: _notesController.text); | ||||
|  | ||||
|     _stockUpdateMessage(result); | ||||
|  | ||||
|     refresh(context); | ||||
|   } | ||||
|  | ||||
|   Future <void> _countStockDialog() async { | ||||
|  | ||||
|     // TODO: In future, deprecate support for older API | ||||
|     if (InvenTreeAPI().supportsModernStockTransactions) { | ||||
|  | ||||
|       Map<String, dynamic> fields = { | ||||
|         "pk": { | ||||
|           "parent": "items", | ||||
|           "nested": true, | ||||
|           "hidden": true, | ||||
|           "value": item.pk, | ||||
|         }, | ||||
|         "quantity": { | ||||
|           "parent": "items", | ||||
|           "nested": true, | ||||
|           "value": item.quantity, | ||||
|         }, | ||||
|         "notes": {}, | ||||
|       }; | ||||
|  | ||||
|       launchApiForm( | ||||
|           context, | ||||
|           L10().countStock, | ||||
|           InvenTreeStockItem.countStockUrl(), | ||||
|           fields, | ||||
|           method: "POST", | ||||
|           icon: FontAwesomeIcons.clipboardCheck, | ||||
|           onSuccess: (data) async { | ||||
|             _stockUpdateMessage(true); | ||||
|             refresh(context); | ||||
|           } | ||||
|       ); | ||||
|  | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     _quantityController.text = item.quantity.toString(); | ||||
|     _notesController.clear(); | ||||
|  | ||||
|     showFormDialog(L10().countStock, | ||||
|       key: _countStockKey, | ||||
|       callback: () { | ||||
|         _countStock(); | ||||
|     Map<String, dynamic> fields = { | ||||
|       "pk": { | ||||
|         "parent": "items", | ||||
|         "nested": true, | ||||
|         "hidden": true, | ||||
|         "value": item.pk, | ||||
|       }, | ||||
|       acceptText: L10().count, | ||||
|       fields: <Widget> [ | ||||
|         QuantityField( | ||||
|           label: L10().countStock, | ||||
|           hint: "${item.quantityString}", | ||||
|           controller: _quantityController, | ||||
|         ), | ||||
|         TextFormField( | ||||
|           decoration: InputDecoration( | ||||
|             labelText: L10().notes, | ||||
|           ), | ||||
|           controller: _notesController, | ||||
|         ) | ||||
|       ] | ||||
|       "quantity": { | ||||
|         "parent": "items", | ||||
|         "nested": true, | ||||
|         "value": item.quantity, | ||||
|       }, | ||||
|       "notes": {}, | ||||
|     }; | ||||
|  | ||||
|     launchApiForm( | ||||
|         context, | ||||
|         L10().countStock, | ||||
|         InvenTreeStockItem.countStockUrl(), | ||||
|         fields, | ||||
|         method: "POST", | ||||
|         icon: FontAwesomeIcons.clipboardCheck, | ||||
|         onSuccess: (data) async { | ||||
|           _stockUpdateMessage(true); | ||||
|           refresh(context); | ||||
|         } | ||||
|     ); | ||||
|   } | ||||
|  | ||||
| @@ -542,130 +411,43 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> { | ||||
|   } | ||||
|  | ||||
|  | ||||
|   // TODO: Delete this function once support for old API is deprecated | ||||
|   Future <void> _transferStock(int locationId) async { | ||||
|  | ||||
|     double quantity = double.tryParse(_quantityController.text) ?? item.quantity; | ||||
|     String notes = _notesController.text; | ||||
|  | ||||
|     _quantityController.clear(); | ||||
|     _notesController.clear(); | ||||
|  | ||||
|     var result = await item.transferStock(context, locationId, quantity: quantity, notes: notes); | ||||
|  | ||||
|     refresh(context); | ||||
|  | ||||
|     if (result) { | ||||
|       showSnackIcon(L10().stockItemTransferred, success: true); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /* | ||||
|    * Launches an API Form to transfer this stock item to a new location | ||||
|    */ | ||||
|   Future <void> _transferStockDialog(BuildContext context) async { | ||||
|  | ||||
|     // TODO: In future, deprecate support for older API | ||||
|     if (InvenTreeAPI().supportsModernStockTransactions) { | ||||
|     Map<String, dynamic> fields = { | ||||
|       "pk": { | ||||
|         "parent": "items", | ||||
|         "nested": true, | ||||
|         "hidden": true, | ||||
|         "value": item.pk, | ||||
|       }, | ||||
|       "quantity": { | ||||
|         "parent": "items", | ||||
|         "nested": true, | ||||
|         "value": item.quantity, | ||||
|       }, | ||||
|       "location": {}, | ||||
|       "notes": {}, | ||||
|     }; | ||||
|  | ||||
|       Map<String, dynamic> fields = { | ||||
|         "pk": { | ||||
|           "parent": "items", | ||||
|           "nested": true, | ||||
|           "hidden": true, | ||||
|           "value": item.pk, | ||||
|         }, | ||||
|         "quantity": { | ||||
|           "parent": "items", | ||||
|           "nested": true, | ||||
|           "value": item.quantity, | ||||
|         }, | ||||
|         "location": {}, | ||||
|         "notes": {}, | ||||
|       }; | ||||
|  | ||||
|       launchApiForm( | ||||
|           context, | ||||
|           L10().transferStock, | ||||
|           InvenTreeStockItem.transferStockUrl(), | ||||
|           fields, | ||||
|           method: "POST", | ||||
|           icon: FontAwesomeIcons.dolly, | ||||
|           onSuccess: (data) async { | ||||
|             _stockUpdateMessage(true); | ||||
|             refresh(context); | ||||
|           } | ||||
|       ); | ||||
|  | ||||
|       return; | ||||
|     if (item.isSerialized()) { | ||||
|       // Prevent editing of 'quantity' field if the item is serialized | ||||
|       fields["quantity"]["hidden"] = true; | ||||
|     } | ||||
|  | ||||
|     int? location_pk; | ||||
|  | ||||
|     _quantityController.text = "${item.quantity}"; | ||||
|  | ||||
|     showFormDialog(L10().transferStock, | ||||
|         key: _moveStockKey, | ||||
|         callback: () { | ||||
|           var _pk = location_pk; | ||||
|  | ||||
|           if (_pk != null) { | ||||
|             _transferStock(_pk); | ||||
|           } | ||||
|         }, | ||||
|         fields: <Widget>[ | ||||
|           QuantityField( | ||||
|             label: L10().quantity, | ||||
|             controller: _quantityController, | ||||
|             max: item.quantity, | ||||
|           ), | ||||
|           DropdownSearch<dynamic>( | ||||
|             mode: Mode.BOTTOM_SHEET, | ||||
|             showSelectedItem: false, | ||||
|             autoFocusSearchBox: true, | ||||
|             selectedItem: null, | ||||
|             errorBuilder: (context, entry, exception) { | ||||
|               print("entry: $entry"); | ||||
|               print(exception.toString()); | ||||
|  | ||||
|               return Text( | ||||
|                 exception.toString(), | ||||
|                 style: TextStyle( | ||||
|                   fontSize: 10, | ||||
|                 ) | ||||
|               ); | ||||
|             }, | ||||
|             onFind: (String filter) async { | ||||
|  | ||||
|               final results = await InvenTreeStockLocation().search(filter); | ||||
|  | ||||
|               List<dynamic> items = []; | ||||
|  | ||||
|               for (InvenTreeModel loc in results) { | ||||
|                 if (loc is InvenTreeStockLocation) { | ||||
|                   items.add(loc.jsondata); | ||||
|                 } | ||||
|               } | ||||
|  | ||||
|               return items; | ||||
|             }, | ||||
|             label: L10().stockLocation, | ||||
|             hint: L10().searchLocation, | ||||
|             onChanged: null, | ||||
|             itemAsString: (dynamic location) { | ||||
|               return (location["pathstring"] ?? "") as String; | ||||
|             }, | ||||
|             onSaved: (dynamic location) { | ||||
|               if (location == null) { | ||||
|                 location_pk = null; | ||||
|               } else { | ||||
|                 location_pk = location["pk"] as int; | ||||
|               } | ||||
|             }, | ||||
|             isFilteredOnline: true, | ||||
|             showSearchBox:  true, | ||||
|           ), | ||||
|         ], | ||||
|     launchApiForm( | ||||
|         context, | ||||
|         L10().transferStock, | ||||
|         InvenTreeStockItem.transferStockUrl(), | ||||
|         fields, | ||||
|         method: "POST", | ||||
|         icon: FontAwesomeIcons.dolly, | ||||
|         onSuccess: (data) async { | ||||
|           _stockUpdateMessage(true); | ||||
|           refresh(context); | ||||
|         } | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -127,7 +127,6 @@ void main() { | ||||
|       assert(api.apiVersion >= 50); | ||||
|       assert(api.supportsSettings); | ||||
|       assert(api.supportsNotifications); | ||||
|       assert(api.supportsModernStockTransactions); | ||||
|       assert(api.supportsPoReceive); | ||||
|  | ||||
|       // Check available permissions | ||||
|   | ||||
		Reference in New Issue
	
	Block a user