diff --git a/assets/release_notes.md b/assets/release_notes.md index c7dfde30..38487ba7 100644 --- a/assets/release_notes.md +++ b/assets/release_notes.md @@ -7,6 +7,7 @@ - Refactor home screen display - Display unread notifications on home screen - Fixes duplicated display of units when showing stock quantity +- Adds ability to locate / identify stock items or locations (requires server plugin) - Improve rendering of home screen when server is not connected - Adds ability to load global and user settings from the server diff --git a/lib/api.dart b/lib/api.dart index fc900c1d..51a414c3 100644 --- a/lib/api.dart +++ b/lib/api.dart @@ -22,6 +22,8 @@ import "package:inventree/user_profile.dart"; import "package:inventree/widget/snacks.dart"; import "package:path_provider/path_provider.dart"; +import "package:inventree/api_form.dart"; + /* * Class representing an API response from the server @@ -1200,4 +1202,80 @@ class InvenTreeAPI { } } + /* + * Send a request to the server to locate / identify either a StockItem or StockLocation + */ + Future locateItemOrLocation(BuildContext context, {int? item, int? location}) async { + + var plugins = getPlugins(mixin: "locate"); + + print("locateItemOrLocation"); + + if (plugins.isEmpty) { + // TODO: Error message + return; + } + + String plugin_name = ""; + + if (plugins.length == 1) { + plugin_name = plugins.first.key; + } else { + // User selects which plugin to use + List> plugin_options = []; + + for (var plugin in plugins) { + plugin_options.add({ + "display_name": plugin.humanName, + "value": plugin.key, + }); + } + + Map fields = { + "plugin": { + "label": L10().plugin, + "type": "choice", + "value": plugins.first.key, + "choices": plugin_options, + "required": true, + } + }; + + await launchApiForm( + context, + L10().locateLocation, + "", + fields, + icon: FontAwesomeIcons.searchLocation, + onSuccess: (Map data) async { + plugin_name = (data["plugin"] ?? "") as String; + } + ); + } + + Map body = { + "plugin": plugin_name, + }; + + if (item != null) { + body["item"] = item.toString(); + } + + if (location != null) { + body["location"] = location.toString(); + } + + post( + "/api/locate/", + body: body, + expectedStatusCode: 200, + ).then((APIResponse response) { + if (response.successful()) { + showSnackIcon( + L10().requestSuccessful, + success: true, + ); + } + }); + } } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 56371b44..3b13d9cf 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -416,6 +416,9 @@ "keywords": "Keywords", "@keywords": {}, + "labelTemplate": "Label Template", + "@labelTemplate": {}, + "lastStocktake": "Last Stocktake", "@lastStocktake": {}, @@ -428,6 +431,12 @@ "lineItems": "Line Items", "@lineItems": {}, + "locateItem": "Locate stock item", + "@locateItem": {}, + + "locateLocation": "Locate stock location", + "@locateLocation": {}, + "locationCreate": "New Location", "@locationCreate": {}, @@ -575,6 +584,18 @@ "printLabel": "Print Label", "@printLabel": {}, + "plugin": "Plugin", + "@plugin": {}, + + "pluginPrinter": "Printer", + "@pluginPrinter": {}, + + "pluginSupport": "Plugin Support Enabled", + "@pluginSupport": {}, + + "pluginSupportDetail": "The server supports custom plugins", + "@pluginSupportDetail": {}, + "printLabelFailure": "Label printing failed", "@printLabelFailure": {}, @@ -692,6 +713,9 @@ "request": "Request", "@request": {}, + "requestSuccessful": "Request successful", + "@requestSuccessful": {}, + "requestingData": "Requesting Data", "@requestingData": {}, diff --git a/lib/settings/about.dart b/lib/settings/about.dart index f62179cd..7ba8c5dc 100644 --- a/lib/settings/about.dart +++ b/lib/settings/about.dart @@ -102,6 +102,18 @@ class InvenTreeAboutWidget extends StatelessWidget { leading: FaIcon(FontAwesomeIcons.server), ) ); + + // Display extra tile if the server supports plugins + if (InvenTreeAPI().pluginsEnabled()) { + tiles.add( + ListTile( + title: Text(L10().pluginSupport), + subtitle: Text(L10().pluginSupportDetail), + leading: FaIcon(FontAwesomeIcons.plug), + ) + ); + } + } else { tiles.add( ListTile( diff --git a/lib/widget/location_display.dart b/lib/widget/location_display.dart index cdf1856e..c1647eeb 100644 --- a/lib/widget/location_display.dart +++ b/lib/widget/location_display.dart @@ -61,19 +61,51 @@ class _LocationDisplayState extends RefreshableState { ); */ - if ((location != null) && (InvenTreeAPI().checkPermission("stock_location", "change"))) { - actions.add( - IconButton( - icon: FaIcon(FontAwesomeIcons.edit), - tooltip: L10().edit, - onPressed: () { _editLocationDialog(context); }, - ) - ); + if (location != null) { + + // Add "locate" button + if (InvenTreeAPI().supportsMixin("locate")) { + actions.add( + IconButton( + icon: FaIcon(FontAwesomeIcons.searchLocation), + tooltip: L10().locateLocation, + onPressed: () async { + _locateStockLocation(context); + }, + ) + ); + } + + // Add "edit" button + if (InvenTreeAPI().checkPermission("stock_location", "change")) { + actions.add( + IconButton( + icon: FaIcon(FontAwesomeIcons.edit), + tooltip: L10().edit, + onPressed: () { _editLocationDialog(context); }, + ) + ); + } } return actions; } + /* + * Request identification of this location + */ + Future _locateStockLocation(BuildContext context) async { + + final _loc = location; + + if (_loc != null) { + InvenTreeAPI().locateItemOrLocation(context, location: _loc.pk); + } + } + + /* + * Launch a dialog form to edit this stock location + */ void _editLocationDialog(BuildContext context) { final _loc = location; diff --git a/lib/widget/stock_detail.dart b/lib/widget/stock_detail.dart index 3b400b26..b0b3cdab 100644 --- a/lib/widget/stock_detail.dart +++ b/lib/widget/stock_detail.dart @@ -67,6 +67,18 @@ class _StockItemDisplayState extends RefreshableState { ); } + if (InvenTreeAPI().supportsMixin("locate")) { + actions.add( + IconButton( + icon: FaIcon(FontAwesomeIcons.searchLocation), + tooltip: L10().locateItem, + onPressed: () async { + InvenTreeAPI().locateItemOrLocation(context, item: item.pk); + }, + ) + ); + } + if (InvenTreeAPI().checkPermission("stock", "change")) { actions.add( IconButton( @@ -217,14 +229,14 @@ class _StockItemDisplayState extends RefreshableState { Map fields = { "label": { - "label": "Label Template", + "label": L10().labelTemplate, "type": "choice", "value": initial_label, "choices": label_options, "required": true, }, "plugin": { - "label": "Printer", + "label": L10().pluginPrinter, "type": "choice", "value": initial_plugin, "choices": plugin_options,