From aac13ed5d6ddcedec4d9b622065ff73f6408c8c2 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 23 Dec 2024 22:25:55 +1100 Subject: [PATCH] Stock expiry (#590) * Add stock expiry getters * refactor date getters * Display expiry information * Update release notes * Remove unused import --- assets/release_notes.md | 1 + lib/api.dart | 4 +- lib/inventree/model.dart | 25 +++++++++++ lib/inventree/stock.dart | 69 +++++++----------------------- lib/l10n/app_en.arb | 9 ++++ lib/widget/stock/stock_detail.dart | 23 ++++++++++ 6 files changed, 75 insertions(+), 56 deletions(-) diff --git a/assets/release_notes.md b/assets/release_notes.md index 3b1237cc..2f83b6d2 100644 --- a/assets/release_notes.md +++ b/assets/release_notes.md @@ -3,6 +3,7 @@ - Fixed error message when printing a label to a remote machine - Prevent notification sounds from pause media playback +- Display stock expiry information - Updated translations ### 0.17.1 - December 2024 diff --git a/lib/api.dart b/lib/api.dart index 24e8c1fd..549b000f 100644 --- a/lib/api.dart +++ b/lib/api.dart @@ -1532,7 +1532,7 @@ class InvenTreeAPI { // Return a boolean global setting value Future getGlobalBooleanSetting(String key) async { String value = await getGlobalSetting(key); - return value.toLowerCase() == "true"; + return value.toLowerCase().trim() == "true"; } Future getUserSetting(String key) async { @@ -1557,7 +1557,7 @@ class InvenTreeAPI { // Return a boolean user setting value Future getUserBooleanSetting(String key) async { String value = await getUserSetting(key); - return value.toLowerCase() == "true"; + return value.toLowerCase().trim() == "true"; } /* diff --git a/lib/inventree/model.dart b/lib/inventree/model.dart index fc90a2ba..0804699c 100644 --- a/lib/inventree/model.dart +++ b/lib/inventree/model.dart @@ -3,6 +3,7 @@ import "dart:io"; import "package:flutter_tabler_icons/flutter_tabler_icons.dart"; import "package:flutter/material.dart"; +import "package:intl/intl.dart"; import "package:inventree/widget/snacks.dart"; import "package:url_launcher/url_launcher.dart"; import "package:path/path.dart" as path; @@ -156,6 +157,30 @@ class InvenTreeModel { return value.toString().toLowerCase() == "true"; } + // Helper function to get date value from json data + DateTime? getDate(String key, {DateTime? backup, String subKey = ""}) { + dynamic value = getValue(key, backup: backup, subKey: subKey); + + if (value == null) { + return backup; + } + + return DateTime.tryParse(value as String); + } + + // Helper function to get date as a string + String getDateString(String key, {DateTime? backup, String subKey = ""}) { + DateTime? dt = getDate(key, backup: backup, subKey: subKey); + + if (dt == null) { + return ""; + } + + final DateFormat fmt = DateFormat("yyyy-MM-dd"); + + return fmt.format(dt); + } + // Return the InvenTree web server URL for this object String get webUrl { diff --git a/lib/inventree/stock.dart b/lib/inventree/stock.dart index 1ae5e7ff..baa328a9 100644 --- a/lib/inventree/stock.dart +++ b/lib/inventree/stock.dart @@ -1,7 +1,5 @@ import "dart:async"; -import "package:intl/intl.dart"; - import "package:inventree/api.dart"; import "package:inventree/helpers.dart"; import "package:inventree/l10.dart"; @@ -107,23 +105,9 @@ class InvenTreeStockItemHistory extends InvenTreeModel { }; } - DateTime? get date { - if (jsondata.containsKey("date")) { - return DateTime.tryParse((jsondata["date"] ?? "") as String); - } else { - return null; - } - } + DateTime? get date => getDate("date"); - String get dateString { - var d = date; - - if (d == null) { - return ""; - } - - return DateFormat("yyyy-MM-dd").format(d); - } + String get dateString => getDateString("date"); String get label => getString("label"); @@ -258,6 +242,7 @@ class InvenTreeStockItem extends InvenTreeModel { "part_detail": "true", "location_detail": "true", "supplier_detail": "true", + "supplier_part_detail": "true", "cascade": "false" }; } @@ -347,46 +332,22 @@ class InvenTreeStockItem extends InvenTreeModel { bool get hasCustomer => customerId > 0; + bool get stale => getBool("stale"); + + bool get expired => getBool("expired"); + + DateTime? get expiryDate => getDate("expiry_date"); + + String get expiryDateString => getDateString("expiry_date"); + // Date of last update - DateTime? get updatedDate { - if (jsondata.containsKey("updated")) { - return DateTime.tryParse((jsondata["updated"] ?? "") as String); - } else { - return null; - } - } + DateTime? get updatedDate => getDate("updated"); - String get updatedDateString { - var _updated = updatedDate; + String get updatedDateString => getDateString("updated"); - if (_updated == null) { - return ""; - } + DateTime? get stocktakeDate => getDate("stocktake_date"); - final DateFormat _format = DateFormat("yyyy-MM-dd"); - - return _format.format(_updated); - } - - DateTime? get stocktakeDate { - if (jsondata.containsKey("stocktake_date")) { - return DateTime.tryParse((jsondata["stocktake_date"] ?? "") as String); - } else { - return null; - } - } - - String get stocktakeDateString { - var _stocktake = stocktakeDate; - - if (_stocktake == null) { - return ""; - } - - final DateFormat _format = DateFormat("yyyy-MM-dd"); - - return _format.format(_stocktake); - } + String get stocktakeDateString => getDateString("stocktake_date"); String get partName { diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 8ea01ad6..3fef1f36 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -399,6 +399,15 @@ "errorReportUploadDetails": "Upload anonymous error reports and crash logs", "@errorReportUploadDetails": {}, + "expiryDate": "Expiry Date", + "@expiryDate": {}, + + "expiryExpired": "Expired", + "@expiryExpired": {}, + + "expiryStale": "Stale", + "@expiryStale": {}, + "feedback": "Feedback", "@feedback": {}, diff --git a/lib/widget/stock/stock_detail.dart b/lib/widget/stock/stock_detail.dart index 1b215988..1390637f 100644 --- a/lib/widget/stock/stock_detail.dart +++ b/lib/widget/stock/stock_detail.dart @@ -53,6 +53,7 @@ class _StockItemDisplayState extends RefreshableState { bool stockShowHistory = false; bool stockShowTests = true; + bool expiryEnabled = false; // Linked data fields InvenTreePart? part; @@ -231,6 +232,8 @@ class _StockItemDisplayState extends RefreshableState { Navigator.of(context).pop(); } + expiryEnabled = await api.getGlobalBooleanSetting("STOCK_ENABLE_EXPIRY"); + // Request part information part = await InvenTreePart().get(widget.item.partId) as InvenTreePart?; @@ -731,6 +734,26 @@ class _StockItemDisplayState extends RefreshableState { ); } + if (expiryEnabled && widget.item.expiryDate != null) { + + Widget? _expiryIcon; + + if (widget.item.stale) { + _expiryIcon = Text(L10().expiryStale, style: TextStyle(color: COLOR_WARNING)); + } else if (widget.item.expired) { + _expiryIcon = Text(L10().expiryExpired, style: TextStyle(color: COLOR_DANGER)); + } + + tiles.add( + ListTile( + title: Text(L10().expiryDate), + subtitle: Text(widget.item.expiryDateString), + leading: Icon(TablerIcons.calendar_x), + trailing: _expiryIcon, + ) + ); + } + // Last update? if (widget.item.updatedDateString.isNotEmpty) {