diff --git a/assets/release_notes.md b/assets/release_notes.md index 43b58e39..09e86ee4 100644 --- a/assets/release_notes.md +++ b/assets/release_notes.md @@ -1,3 +1,8 @@ +### 0.17.0 - November 2024 +--- + +- Clearly indicate if a StockItem is unavailable + ### 0.16.5 - September 2024 --- diff --git a/lib/inventree/stock.dart b/lib/inventree/stock.dart index 4d125533..bd7a6cfe 100644 --- a/lib/inventree/stock.dart +++ b/lib/inventree/stock.dart @@ -291,7 +291,9 @@ class InvenTreeStockItem extends InvenTreeModel { } int get status => getInt("status"); - + + bool get isInStock => getBool("in_stock", backup: true); + String get packaging => getString("packaging"); String get batch => getString("batch"); @@ -321,26 +323,34 @@ class InvenTreeStockItem extends InvenTreeModel { bool get isBuilding => getBool("is_building"); - // Date of last update - DateTime? get updatedDate { - if (jsondata.containsKey("updated")) { - return DateTime.tryParse((jsondata["updated"] ?? "") as String); - } else { - return null; - } + int get salesOrderId => getInt("sales_order"); + + bool get hasSalesOrder => salesOrderId > 0; + + int get customerId => getInt("customer"); + + bool get hasCustomer => customerId > 0; + + // Date of last update + DateTime? get updatedDate { + if (jsondata.containsKey("updated")) { + return DateTime.tryParse((jsondata["updated"] ?? "") as String); + } else { + return null; + } + } + + String get updatedDateString { + var _updated = updatedDate; + + if (_updated == null) { + return ""; } - String get updatedDateString { - var _updated = updatedDate; + final DateFormat _format = DateFormat("yyyy-MM-dd"); - if (_updated == null) { - return ""; - } - - final DateFormat _format = DateFormat("yyyy-MM-dd"); - - return _format.format(_updated); - } + return _format.format(_updated); + } DateTime? get stocktakeDate { if (jsondata.containsKey("stocktake_date")) { diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 2f622ff8..cbe1f14e 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1430,6 +1430,12 @@ "translateHelp": "Help translate the InvenTree app", "@translateHelp": {}, + "unavailable": "Unavailable", + "@unavailable": {}, + + "unavailableDetail": "Item is not available", + "@unavailableDetail": {}, + "unitPrice": "Unit Price", "@unitPrice": {}, diff --git a/lib/widget/stock/stock_detail.dart b/lib/widget/stock/stock_detail.dart index 93b02b13..bf874b22 100644 --- a/lib/widget/stock/stock_detail.dart +++ b/lib/widget/stock/stock_detail.dart @@ -7,6 +7,7 @@ import "package:inventree/app_colors.dart"; import "package:inventree/barcode/barcode.dart"; import "package:inventree/barcode/stock.dart"; import "package:inventree/helpers.dart"; +import "package:inventree/inventree/sales_order.dart"; import "package:inventree/l10.dart"; import "package:inventree/api.dart"; import "package:inventree/api_form.dart"; @@ -16,10 +17,12 @@ import "package:inventree/preferences.dart"; import "package:inventree/inventree/company.dart"; import "package:inventree/inventree/stock.dart"; import "package:inventree/inventree/part.dart"; +import "package:inventree/widget/company/company_detail.dart"; import "package:inventree/widget/company/supplier_part_detail.dart"; import "package:inventree/widget/dialogs.dart"; import "package:inventree/widget/attachment_widget.dart"; +import "package:inventree/widget/order/sales_order_detail.dart"; import "package:inventree/widget/stock/location_display.dart"; import "package:inventree/widget/part/part_detail.dart"; import "package:inventree/widget/progress.dart"; @@ -51,6 +54,11 @@ class _StockItemDisplayState extends RefreshableState { bool stockShowHistory = false; bool stockShowTests = true; + // Linked data fields + InvenTreePart? part; + InvenTreeSalesOrder? salesOrder; + InvenTreeCompany? customer; + @override List appBarActions(BuildContext context) { List actions = []; @@ -199,9 +207,6 @@ class _StockItemDisplayState extends RefreshableState { // This will be determined when the widget is loaded List> labels = []; - // Part object - InvenTreePart? part; - int attachmentCount = 0; @override @@ -252,6 +257,40 @@ class _StockItemDisplayState extends RefreshableState { } }); + // Request SalesOrder information + if (widget.item.hasSalesOrder) { + InvenTreeSalesOrder().get(widget.item.salesOrderId).then((instance) => { + if (mounted) { + setState(() { + salesOrder = instance as InvenTreeSalesOrder?; + }) + } + }); + } else { + if (mounted) { + setState(() { + salesOrder = null; + }); + } + } + + // Request Customer information + if (widget.item.hasCustomer) { + InvenTreeCompany().get(widget.item.customerId).then((instance) => { + if (mounted) { + setState(() { + customer = instance as InvenTreeCompany?; + }) + } + }); + } else { + if (mounted) { + setState(() { + customer = null; + }); + } + } + List> _labels = []; bool allowLabelPrinting = await InvenTreeSettingsManager().getBool(INV_ENABLE_LABEL_PRINTING, true); allowLabelPrinting &= api.supportsMixin("labels"); @@ -455,18 +494,32 @@ class _StockItemDisplayState extends RefreshableState { } Widget headerTile() { - return Card( - child: ListTile( - title: Text("${widget.item.partName}"), - subtitle: Text("${widget.item.partDescription}"), - leading: InvenTreeAPI().getThumbnail(widget.item.partImage), - trailing: widget.item.isSerialized() ? null : Text( + + Widget? trailing; + + if (!widget.item.isInStock) { + trailing = Text( + L10().unavailable, + style: TextStyle( + color: COLOR_DANGER + ) + ); + } else if (!widget.item.isSerialized()) { + trailing = Text( widget.item.quantityString(), style: TextStyle( fontSize: 20, color: api.StockStatus.color(widget.item.status), ) - ), + ); + } + + return Card( + child: ListTile( + title: Text("${widget.item.partName}"), + subtitle: Text("${widget.item.partDescription}"), + leading: InvenTreeAPI().getThumbnail(widget.item.partImage), + trailing: trailing, onTap: () async { if (widget.item.partId > 0) { @@ -544,7 +597,7 @@ class _StockItemDisplayState extends RefreshableState { subtitle: Text("${widget.item.serialNumber}"), ) ); - } else { + } else if (widget.item.isInStock) { tiles.add( ListTile( title: widget.item.allocated > 0 ? Text(L10().quantityAvailable) : Text(L10().quantity), @@ -554,6 +607,27 @@ class _StockItemDisplayState extends RefreshableState { ); } + if (!widget.item.isInStock) { + tiles.add( + ListTile( + leading: Icon(TablerIcons.box_off), + title: Text( + L10().unavailable, + style: TextStyle( + color: COLOR_DANGER, + fontWeight: FontWeight.bold, + ), + ), + subtitle: Text( + L10().unavailableDetail, + style: TextStyle( + color: COLOR_DANGER + ) + ) + ) + ); + } + // Stock item status information tiles.add( ListTile( @@ -605,6 +679,38 @@ class _StockItemDisplayState extends RefreshableState { ); } + if (widget.item.hasSalesOrder && salesOrder != null) { + tiles.add( + ListTile( + title: Text(L10().salesOrder), + subtitle: Text(salesOrder?.description ?? ""), + leading: Icon(TablerIcons.truck_delivery, color: COLOR_ACTION), + trailing: Text(salesOrder?.reference ?? ""), + onTap: () { + Navigator.push(context, MaterialPageRoute( + builder: (context) => SalesOrderDetailWidget(salesOrder!) + )); + } + ) + ); + } + + if (widget.item.hasCustomer && customer != null) { + tiles.add( + ListTile( + title: Text(L10().customer), + subtitle: Text(customer?.description ?? ""), + leading: Icon(TablerIcons.building_store, color: COLOR_ACTION), + trailing: Text(customer?.name ?? ""), + onTap: () { + Navigator.push(context, MaterialPageRoute( + builder: (context) => CompanyDetailWidget(customer!) + )); + }, + ) + ); + } + if (widget.item.batch.isNotEmpty) { tiles.add( ListTile( diff --git a/pubspec.yaml b/pubspec.yaml index 0d175208..5cbee61c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: inventree description: InvenTree stock management -version: 0.16.5+91 +version: 0.17.0+92 environment: sdk: ">=2.19.5 <3.13.0"