From 26b86a21944a9337595a41e327e7a1e98ae78e41 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 10 Apr 2023 23:12:30 +1000 Subject: [PATCH] Update status codes (#307) * Extra error info when connecting to server * Adds accessors for various types of status codes * Cleanup / refactor stock status codes - No longer need to duplicate these on the app * improvements to purchase order list - Add option to show closed orders - Render order status * Add purchase order status to order detail widget * Update release notes * Cleanup for imports * linting fixes --- assets/release_notes.md | 2 + lib/api.dart | 71 ++++++++++++---- lib/inventree/status_codes.dart | 112 ++++++++++++++++++++++++++ lib/inventree/stock.dart | 57 ------------- lib/l10n/app_en.arb | 12 +++ lib/widget/purchase_order_detail.dart | 8 ++ lib/widget/purchase_order_list.dart | 19 ++++- lib/widget/stock_detail.dart | 6 +- lib/widget/stock_item_history.dart | 2 +- lib/widget/stock_list.dart | 5 +- 10 files changed, 214 insertions(+), 80 deletions(-) create mode 100644 lib/inventree/status_codes.dart diff --git a/assets/release_notes.md b/assets/release_notes.md index 5b638d9c..939abf6b 100644 --- a/assets/release_notes.md +++ b/assets/release_notes.md @@ -7,6 +7,8 @@ - Fixes keyboard bug in search widget - Adds ability to create new purchase orders directly from the app - Adds support for the "contact" field to purchase orders +- Improved rendering of status codes for stock items +- Added rendering of status codes for purchase orders ### 0.11.0 - April 2023 --- diff --git a/lib/api.dart b/lib/api.dart index 3e147774..68a4a4e7 100644 --- a/lib/api.dart +++ b/lib/api.dart @@ -6,6 +6,7 @@ import "package:flutter/foundation.dart"; import "package:http/http.dart" as http; import "package:intl/intl.dart"; import "package:inventree/app_colors.dart"; +import "package:inventree/inventree/status_codes.dart"; import "package:inventree/preferences.dart"; import "package:open_filex/open_filex.dart"; @@ -148,6 +149,9 @@ class InvenTreeAPI { InvenTreeAPI._internal(); + // Ensure we only ever create a single instance of the API class + static final InvenTreeAPI _api = InvenTreeAPI._internal(); + // List of callback functions to trigger when the connection status changes List _statusCallbacks = []; @@ -346,9 +350,6 @@ class InvenTreeAPI { return !isConnected() && _connecting; } - // Ensure we only ever create a single instance of the API class - static final InvenTreeAPI _api = InvenTreeAPI._internal(); - /* * Connect to the remote InvenTree server: * @@ -490,11 +491,33 @@ class InvenTreeAPI { debug("Received token from server"); + bool result = false; + // Request user role information (async) - getUserRoles(); + result = await getUserRoles(); + + if (!result) { + showServerError( + apiUrl, + L10().serverError, + L10().errorUserRoles, + ); + + return false; + } // Request plugin information (async) - getPluginInformation(); + result = await getPluginInformation(); + + if (!result) { + showServerError( + apiUrl, + L10().serverError, + L10().errorPluginInfo + ); + + return false; + } // Ok, probably pretty good... return true; @@ -516,9 +539,7 @@ class InvenTreeAPI { _connectionStatusChanged(); } - /* - * Public facing connection function - */ + // Public facing connection function Future connectToServer() async { // Ensure server is first disconnected @@ -590,12 +611,12 @@ class InvenTreeAPI { } // Request plugin information from the server - Future getPluginInformation() async { + Future getPluginInformation() async { // The server does not support plugins, or they are not enabled if (!pluginsEnabled()) { _plugins.clear(); - return; + return true; } debug("API: getPluginInformation()"); @@ -611,15 +632,15 @@ class InvenTreeAPI { } } } + + return true; } + /* + * Check if the user has the given role.permission assigned + * e.g. "part", "change" + */ bool checkPermission(String role, String permission) { - /* - * Check if the user has the given role.permission assigned - *e - * e.g. "part", "change" - */ - // If we do not have enough information, assume permission is allowed if (roles.isEmpty) { return true; @@ -1422,4 +1443,22 @@ class InvenTreeAPI { } }); } + + // Keep an internal map of status codes + Map _status_codes = {}; + + // Return a status class based on provided URL + InvenTreeStatusCode _get_status_class(String url) { + if (!_status_codes.containsKey(url)) { + _status_codes[url] = InvenTreeStatusCode(url); + } + + return _status_codes[url]!; + } + + // Accessors methods for various status code classes + InvenTreeStatusCode get StockHistoryStatus => _get_status_class("stock/track/status/"); + InvenTreeStatusCode get StockStatus => _get_status_class("stock/status/"); + InvenTreeStatusCode get PurchaseOrderStatus => _get_status_class("order/po/status/"); + } diff --git a/lib/inventree/status_codes.dart b/lib/inventree/status_codes.dart new file mode 100644 index 00000000..1a563a27 --- /dev/null +++ b/lib/inventree/status_codes.dart @@ -0,0 +1,112 @@ +/* + * Code for querying the server for various status code data, + * so that we do not have to duplicate those codes in the app. + * + * Ref: https://github.com/inventree/InvenTree/blob/master/InvenTree/InvenTree/status_codes.py + */ + +import "package:flutter/material.dart"; + +import "package:inventree/api.dart"; +import "package:inventree/helpers.dart"; + + +/* + * Base class definition for a "status code" definition. + */ +class InvenTreeStatusCode { + + InvenTreeStatusCode(this.URL); + + final String URL; + + // Internal status code data loaded from server + Map data = {}; + + // Load status code information from the server + Future load({bool forceReload = false}) async { + + // Return internally cached data + if (data.isNotEmpty && !forceReload) { + return; + } + + // The server must support this feature! + if (!InvenTreeAPI().supportsStatusLabelEndpoints) { + return; + } + + debug("Loading status codes from ${URL}"); + + APIResponse response = await InvenTreeAPI().get(URL); + + if (response.statusCode == 200) { + Map results = response.data as Map; + + if (results.containsKey("values")) { + data = results["values"] as Map; + } + } + } + + // Return the entry associated with the provided integer status + Map entry(int status) { + for (String key in data.keys) { + dynamic _entry = data[key]; + + if (_entry is Map) { + dynamic _status = _entry["key"]; + + if (_status is int) { + if (status == _status) { + return _entry; + } + } + } + } + + // No match - return an empty map + return {}; + } + + // Return the 'label' associated with a given status code + String label(int status) { + Map _entry = entry(status); + + String _label = (_entry["label"] ?? "") as String; + + if (_label.isEmpty) { + // If no match found, return the status code + debug("No match for status code ${status} at '${URL}'"); + return status.toString(); + } else { + return _label; + } + } + + // Return the 'color' associated with a given status code + Color color(int status) { + Map _entry = entry(status); + + String color_name = (_entry["color"] ?? "") as String; + + switch (color_name.toLowerCase()) { + case "success": + return Colors.green; + case "primary": + return Colors.blue; + case "secondary": + return Colors.grey; + case "dark": + return Colors.black; + case "danger": + return Colors.red; + case "warning": + return Colors.orange; + case "info": + return Colors.lightBlue; + default: + return Colors.black; + } + } +} diff --git a/lib/inventree/stock.dart b/lib/inventree/stock.dart index f8a9a44e..f6e8f3b9 100644 --- a/lib/inventree/stock.dart +++ b/lib/inventree/stock.dart @@ -1,6 +1,5 @@ import "dart:async"; -import "package:flutter/material.dart"; import "package:intl/intl.dart"; import "package:inventree/helpers.dart"; import "package:inventree/inventree/part.dart"; @@ -122,66 +121,10 @@ class InvenTreeStockItem extends InvenTreeModel { InvenTreeStockItem.fromJson(Map json) : super.fromJson(json); - // Stock status codes - static const int OK = 10; - static const int ATTENTION = 50; - static const int DAMAGED = 55; - static const int DESTROYED = 60; - static const int REJECTED = 65; - static const int LOST = 70; - static const int QUARANTINED = 75; - static const int RETURNED = 85; - - String statusLabel() { - - // TODO: Delete me - The translated status values should be provided by the API! - - switch (status) { - case OK: - return L10().ok; - case ATTENTION: - return L10().attention; - case DAMAGED: - return L10().damaged; - case DESTROYED: - return L10().destroyed; - case REJECTED: - return L10().rejected; - case LOST: - return L10().lost; - case QUARANTINED: - return L10().quarantined; - case RETURNED: - return L10().returned; - default: - return status.toString(); - } - } - - // Return color associated with stock status - Color get statusColor { - switch (status) { - case OK: - return Colors.black; - case ATTENTION: - return Color(0xFFfdc82a); - case DAMAGED: - case DESTROYED: - case REJECTED: - return Color(0xFFe35a57); - case QUARANTINED: - return Color(0xFF0DCAF0); - case LOST: - default: - return Color(0xFFAAAAAA); - } - } - @override String get URL => "stock/"; // URLs for performing stock actions - static String transferStockUrl() => "stock/transfer/"; static String countStockUrl() => "stock/count/"; diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index b1895543..bd306028 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -288,6 +288,12 @@ "errorFetch": "Error fetching data from server", "@errorFetch": {}, + "errorUserRoles": "Error requesting user roles from server", + "@errorUserRoles": {}, + + "errorPluginInfo": "Error requesting plugin data from server", + "@errorPluginInfo": {}, + "errorReporting": "Error Reporting", "@errorReporting": {}, @@ -579,6 +585,12 @@ "onOrderDetails": "Items currently on order", "@onOrderDetails": {}, + "outstanding": "Outstanding", + "@outstanding": {}, + + "outstandingOrderDetail": "Show outstanding orders", + "@outstandingOrderDetail": {}, + "packaging": "Packaging", "@packaging": {}, diff --git a/lib/widget/purchase_order_detail.dart b/lib/widget/purchase_order_detail.dart index 77337ef8..220596d4 100644 --- a/lib/widget/purchase_order_detail.dart +++ b/lib/widget/purchase_order_detail.dart @@ -66,6 +66,8 @@ class _PurchaseOrderDetailState extends RefreshableState request(BuildContext context) async { await order.reload(); + await api.PurchaseOrderStatus.load(); + lines = await order.getLineItems(); completedLines = 0; @@ -112,6 +114,12 @@ class _PurchaseOrderDetailState extends RefreshableState> get filterOptions => { + "outstanding": { + "label": L10().outstanding, + "help_text": L10().outstandingOrderDetail, + "tristate": true, + } + }; + @override Future requestPage(int limit, int offset, Map params) async { - params["outstanding"] = "true"; - + await InvenTreeAPI().PurchaseOrderStatus.load(); final page = await InvenTreePurchaseOrder().listPaginated(limit, offset, filters: params); return page; @@ -151,7 +159,12 @@ class _PaginatedPurchaseOrderListState extends PaginatedSearchState { @override Future request(BuildContext context) async { + await api.StockStatus.load(); + stockShowHistory = await InvenTreeSettingsManager().getValue(INV_STOCK_SHOW_HISTORY, false) as bool; final bool result = widget.item.pk > 0 && await widget.item.reload(); @@ -565,9 +567,9 @@ class _StockItemDisplayState extends RefreshableState { subtitle: Text("${widget.item.partDescription}"), leading: InvenTreeAPI().getImage(widget.item.partImage), trailing: Text( - widget.item.statusLabel(), + api.StockStatus.label(widget.item.status), style: TextStyle( - color: widget.item.statusColor + color: api.StockStatus.color(widget.item.status), ) ), onTap: () async { diff --git a/lib/widget/stock_item_history.dart b/lib/widget/stock_item_history.dart index b03263ff..15ecf83d 100644 --- a/lib/widget/stock_item_history.dart +++ b/lib/widget/stock_item_history.dart @@ -33,7 +33,7 @@ class _StockItemHistoryDisplayState extends RefreshableState results) { + await InvenTreeStockItemHistory().list(filters: {"item": "${item.pk}"}).then((List results) { for (var result in results) { if (result is InvenTreeStockItemHistory) { history.add(result); diff --git a/lib/widget/stock_list.dart b/lib/widget/stock_list.dart index 70c3928a..0d16bc1e 100644 --- a/lib/widget/stock_list.dart +++ b/lib/widget/stock_list.dart @@ -95,6 +95,9 @@ class _PaginatedStockItemListState extends PaginatedSearchState requestPage(int limit, int offset, Map params) async { + // Ensure StockStatus codes are loaded + await InvenTreeAPI().StockStatus.load(); + final page = await InvenTreeStockItem().listPaginated( limit, offset, @@ -120,7 +123,7 @@ class _PaginatedStockItemListState extends PaginatedSearchState