mirror of
https://github.com/inventree/inventree-app.git
synced 2025-04-28 05:26:47 +00:00
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
This commit is contained in:
parent
efb7ff4170
commit
26b86a2194
@ -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
|
||||
---
|
||||
|
71
lib/api.dart
71
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<Function()> _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<bool> connectToServer() async {
|
||||
|
||||
// Ensure server is first disconnected
|
||||
@ -590,12 +611,12 @@ class InvenTreeAPI {
|
||||
}
|
||||
|
||||
// Request plugin information from the server
|
||||
Future<void> getPluginInformation() async {
|
||||
Future<bool> 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<String, InvenTreeStatusCode> _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/");
|
||||
|
||||
}
|
||||
|
112
lib/inventree/status_codes.dart
Normal file
112
lib/inventree/status_codes.dart
Normal file
@ -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<String, dynamic> data = {};
|
||||
|
||||
// Load status code information from the server
|
||||
Future<void> 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<String, dynamic> results = response.data as Map<String, dynamic>;
|
||||
|
||||
if (results.containsKey("values")) {
|
||||
data = results["values"] as Map<String, dynamic>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the entry associated with the provided integer status
|
||||
Map<String, dynamic> entry(int status) {
|
||||
for (String key in data.keys) {
|
||||
dynamic _entry = data[key];
|
||||
|
||||
if (_entry is Map<String, dynamic>) {
|
||||
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<String, dynamic> _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<String, dynamic> _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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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<String, dynamic> 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/";
|
||||
|
@ -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": {},
|
||||
|
||||
|
@ -66,6 +66,8 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
|
||||
Future<void> 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<PurchaseOrderDetailWidg
|
||||
title: Text(order.reference),
|
||||
subtitle: Text(order.description),
|
||||
leading: supplier == null ? null : InvenTreeAPI().getImage(supplier.thumbnail, width: 40, height: 40),
|
||||
trailing: Text(
|
||||
api.PurchaseOrderStatus.label(order.status),
|
||||
style: TextStyle(
|
||||
color: api.PurchaseOrderStatus.color(order.status)
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -125,11 +125,19 @@ class _PaginatedPurchaseOrderListState extends PaginatedSearchState<PaginatedPur
|
||||
"target_date": L10().targetDate,
|
||||
};
|
||||
|
||||
@override
|
||||
Map<String, Map<String, dynamic>> get filterOptions => {
|
||||
"outstanding": {
|
||||
"label": L10().outstanding,
|
||||
"help_text": L10().outstandingOrderDetail,
|
||||
"tristate": true,
|
||||
}
|
||||
};
|
||||
|
||||
@override
|
||||
Future<InvenTreePageResponse?> requestPage(int limit, int offset, Map<String, String> 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<PaginatedPur
|
||||
width: 40,
|
||||
height: 40,
|
||||
),
|
||||
trailing: Text("${order.lineItemCount}"),
|
||||
trailing: Text(
|
||||
InvenTreeAPI().PurchaseOrderStatus.label(order.status),
|
||||
style: TextStyle(
|
||||
color: InvenTreeAPI().PurchaseOrderStatus.color(order.status),
|
||||
),
|
||||
),
|
||||
onTap: () async {
|
||||
Navigator.push(
|
||||
context,
|
||||
|
@ -211,6 +211,8 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
||||
@override
|
||||
Future<void> 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<StockDetailWidget> {
|
||||
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 {
|
||||
|
@ -33,7 +33,7 @@ class _StockItemHistoryDisplayState extends RefreshableState<StockItemHistoryWid
|
||||
|
||||
history.clear();
|
||||
|
||||
InvenTreeStockItemHistory().list(filters: {"item": "${item.pk}"}).then((List<InvenTreeModel> results) {
|
||||
await InvenTreeStockItemHistory().list(filters: {"item": "${item.pk}"}).then((List<InvenTreeModel> results) {
|
||||
for (var result in results) {
|
||||
if (result is InvenTreeStockItemHistory) {
|
||||
history.add(result);
|
||||
|
@ -95,6 +95,9 @@ class _PaginatedStockItemListState extends PaginatedSearchState<PaginatedStockIt
|
||||
@override
|
||||
Future<InvenTreePageResponse?> requestPage(int limit, int offset, Map<String, String> 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<PaginatedStockIt
|
||||
trailing: Text("${item.displayQuantity}",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: item.statusColor,
|
||||
color: InvenTreeAPI().StockStatus.color(item.status),
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
|
Loading…
x
Reference in New Issue
Block a user