diff --git a/lib/app_settings.dart b/lib/app_settings.dart index 88145bd2..7d97b9c4 100644 --- a/lib/app_settings.dart +++ b/lib/app_settings.dart @@ -18,6 +18,7 @@ const String INV_SOUNDS_SERVER = "serverSounds"; const String INV_PART_SUBCATEGORY = "partSubcategory"; const String INV_STOCK_SUBLOCATION = "stockSublocation"; +const String INV_STOCK_SHOW_HISTORY = "stockShowHistory"; const String INV_REPORT_ERRORS = "reportErrors"; diff --git a/lib/inventree/stock.dart b/lib/inventree/stock.dart index 2e85a201..8b2f1ec7 100644 --- a/lib/inventree/stock.dart +++ b/lib/inventree/stock.dart @@ -56,6 +56,67 @@ class InvenTreeStockItemTestResult extends InvenTreeModel { } +class InvenTreeStockItemHistory extends InvenTreeModel { + + InvenTreeStockItemHistory() : super(); + + InvenTreeStockItemHistory.fromJson(Map json) : super.fromJson(json); + + @override + InvenTreeModel createFromJson(Map json) { + return InvenTreeStockItemHistory.fromJson(json); + } + + @override + String get URL => "stock/track/"; + + @override + Map defaultListFilters() { + + // By default, order by decreasing date + return { + "ordering": "-date", + }; + } + + DateTime? get date { + if (jsondata.containsKey("date")) { + return DateTime.tryParse((jsondata["date"] ?? "") as String); + } else { + return null; + } + } + + String get dateString { + var d = date; + + if (d == null) { + return ""; + } + + return DateFormat("yyyy-MM-dd").format(d); + } + + String get label => (jsondata["label"] ?? "") as String; + + String get quantityString { + Map deltas = (jsondata["deltas"] ?? {}) as Map; + + // Serial number takes priority here + if (deltas.containsKey("serial")) { + var serial = (deltas["serial"] ?? "") as String; + return "# ${serial}"; + } else if (deltas.containsKey("quantity")) { + double q = (deltas["quantity"] ?? 0) as double; + + return simpleNumberString(q); + } else { + return ""; + } + } +} + + class InvenTreeStockItem extends InvenTreeModel { InvenTreeStockItem() : super(); diff --git a/lib/l10n b/lib/l10n index a66dab99..10ae8c64 160000 --- a/lib/l10n +++ b/lib/l10n @@ -1 +1 @@ -Subproject commit a66dab998bc92b3cceed70aaffc92ffa8b5da836 +Subproject commit 10ae8c64e0754206e21b06531186375e247c796d diff --git a/lib/settings/app_settings.dart b/lib/settings/app_settings.dart index 2431b630..46306bbe 100644 --- a/lib/settings/app_settings.dart +++ b/lib/settings/app_settings.dart @@ -28,6 +28,7 @@ class _InvenTreeAppSettingsState extends State { // Stock settings bool stockSublocation = false; + bool stockShowHistory = false; bool reportErrors = true; @@ -48,6 +49,7 @@ class _InvenTreeAppSettingsState extends State { partSubcategory = await InvenTreeSettingsManager().getValue(INV_PART_SUBCATEGORY, true) as bool; stockSublocation = await InvenTreeSettingsManager().getValue(INV_STOCK_SUBLOCATION, true) as bool; + stockShowHistory = await InvenTreeSettingsManager().getValue(INV_STOCK_SHOW_HISTORY, false) as bool; reportErrors = await InvenTreeSettingsManager().getValue(INV_REPORT_ERRORS, true) as bool; @@ -109,6 +111,20 @@ class _InvenTreeAppSettingsState extends State { }, ), ), + ListTile( + title: Text(L10().stockItemHistory), + subtitle: Text(L10().stockItemHistoryDetail), + leading: FaIcon(FontAwesomeIcons.history), + trailing: Switch( + value: stockShowHistory, + onChanged: (bool value) { + InvenTreeSettingsManager().setValue(INV_STOCK_SHOW_HISTORY, value); + setState(() { + stockShowHistory = value; + }); + }, + ), + ), /* Sound Settings */ Divider(height: 3), ListTile( diff --git a/lib/widget/stock_detail.dart b/lib/widget/stock_detail.dart index 4733674f..389abfe5 100644 --- a/lib/widget/stock_detail.dart +++ b/lib/widget/stock_detail.dart @@ -16,12 +16,14 @@ import "package:inventree/widget/part_detail.dart"; import "package:inventree/widget/progress.dart"; import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/widget/snacks.dart"; +import "package:inventree/widget/stock_item_history.dart"; import "package:inventree/widget/stock_item_test_results.dart"; import "package:inventree/widget/stock_notes.dart"; import "package:inventree/l10.dart"; import "package:inventree/helpers.dart"; import "package:inventree/api.dart"; import "package:inventree/api_form.dart"; +import "package:inventree/app_settings.dart"; class StockDetailWidget extends StatefulWidget { @@ -50,6 +52,8 @@ class _StockItemDisplayState extends RefreshableState { final _countStockKey = GlobalKey(); final _moveStockKey = GlobalKey(); + bool stockShowHistory = false; + @override List getAppBarActions(BuildContext context) { @@ -105,6 +109,8 @@ class _StockItemDisplayState extends RefreshableState { final bool result = await item.reload(); + stockShowHistory = await InvenTreeSettingsManager().getValue(INV_STOCK_SHOW_HISTORY, false) as bool; + // Could not load this stock item for some reason // Perhaps it has been depleted? if (!result || item.pk == -1) { @@ -861,24 +867,24 @@ class _StockItemDisplayState extends RefreshableState { // TODO - Is this stock item linked to a PurchaseOrder? - // TODO - Re-enable stock item history display - /* - if (item.trackingItemCount > 0) { + if (stockShowHistory && item.trackingItemCount > 0) { tiles.add( ListTile( title: Text(L10().history), - leading: FaIcon(FontAwesomeIcons.history), + leading: FaIcon(FontAwesomeIcons.history, color: COLOR_CLICK), trailing: Text("${item.trackingItemCount}"), onTap: () { - // TODO: Load tracking history - - // TODO: Push tracking history page to the route - + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StockItemHistoryWidget(item)) + ).then((ctx) { + refresh(context); + }); }, ) ); } - */ // Notes field tiles.add( diff --git a/lib/widget/stock_item_history.dart b/lib/widget/stock_item_history.dart new file mode 100644 index 00000000..f4c5afe3 --- /dev/null +++ b/lib/widget/stock_item_history.dart @@ -0,0 +1,78 @@ + + +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; + +import "package:inventree/widget/refreshable_state.dart"; +import "package:inventree/l10.dart"; +import "package:inventree/inventree/stock.dart"; +import "package:inventree/inventree/model.dart"; + + +class StockItemHistoryWidget extends StatefulWidget { + + const StockItemHistoryWidget(this.item, {Key? key}) : super(key: key); + + final InvenTreeStockItem item; + + @override + _StockItemHistoryDisplayState createState() => _StockItemHistoryDisplayState(item); +} + + +class _StockItemHistoryDisplayState extends RefreshableState { + + _StockItemHistoryDisplayState(this.item); + + final InvenTreeStockItem item; + + @override + String getAppBarTitle(BuildContext context) => L10().stockItemHistory; + + List history = []; + + @override + Future request(BuildContext refresh) async { + + history.clear(); + + InvenTreeStockItemHistory().list(filters: {"item": "${item.pk}"}).then((List results) { + for (var result in results) { + if (result is InvenTreeStockItemHistory) { + history.add(result); + } + } + + // Refresh + setState(() { + }); + }); + } + + @override + Widget getBody(BuildContext context) { + return ListView( + children: ListTile.divideTiles( + context: context, + tiles: historyList(), + ).toList() + ); + } + + List historyList() { + List tiles = []; + + for (var entry in history) { + tiles.add( + ListTile( + leading: Text(entry.dateString), + trailing: entry.quantityString.isNotEmpty ? Text(entry.quantityString) : null, + title: Text(entry.label), + subtitle: entry.notes.isNotEmpty ? Text(entry.notes) : null, + ) + ); + } + + return tiles; + } +} \ No newline at end of file