diff --git a/assets/release_notes.md b/assets/release_notes.md index 2a9fd1cf..af036fa7 100644 --- a/assets/release_notes.md +++ b/assets/release_notes.md @@ -4,6 +4,7 @@ ### - December 2022 --- +- Support Part parameters - Add support for structural part categories - Add support for structural stock locations - Allow deletion of attachments via app diff --git a/lib/api.dart b/lib/api.dart index d8dc6503..56d92cb7 100644 --- a/lib/api.dart +++ b/lib/api.dart @@ -262,6 +262,9 @@ class InvenTreeAPI { // Structural categories requires API v83 or newer bool get supportsStructuralCategories => isConnected() && apiVersion >= 83; + // Part parameter support requires API v56 or newer + bool get supportsPartParameters => isConnected() && apiVersion >= 56; + // Are plugins enabled on the server? bool _pluginsEnabled = false; diff --git a/lib/inventree/part.dart b/lib/inventree/part.dart index 37095aaf..762ba9d5 100644 --- a/lib/inventree/part.dart +++ b/lib/inventree/part.dart @@ -125,6 +125,49 @@ class InvenTreePartTestTemplate extends InvenTreeModel { } +/* + Class representing the PartParameter database model + */ +class InvenTreePartParameter extends InvenTreeModel { + + InvenTreePartParameter() : super(); + + InvenTreePartParameter.fromJson(Map json) : super.fromJson(json); + + @override + String get URL => "part/parameter/"; + + @override + InvenTreeModel createFromJson(Map json) { + return InvenTreePartParameter.fromJson(json); + } + + @override + Map formFields() { + return {}; + } + + @override + String get name => (jsondata["template_detail"]?["name"] ?? "") as String; + + @override + String get description => (jsondata["template_detail"]?["description"] ?? "") as String; + + String get value => jsondata["data"] as String; + + String get valueString { + String v = value; + + if (units.isNotEmpty) { + v += " "; + v += units; + } + + return v; + } + + String get units => (jsondata["template_detail"]?["units"] ?? "") as String; +} /* * Class representing the Part database model @@ -437,4 +480,4 @@ class InvenTreePartAttachment extends InvenTreeAttachment { return InvenTreePartAttachment.fromJson(json); } -} \ No newline at end of file +} diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 11a111a9..6e17fdbe 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -579,6 +579,12 @@ "packageName": "Package Name", "@packageName": {}, + "parameters": "Parameters", + "@parameters": {}, + + "parametersSettingDetail": "Display part parameters", + "@parametersSettingDetail": {}, + "parent": "Parent", "@parent": {}, diff --git a/lib/preferences.dart b/lib/preferences.dart index 55ad2a7e..2f5610f4 100644 --- a/lib/preferences.dart +++ b/lib/preferences.dart @@ -18,6 +18,10 @@ const String INV_HOME_SHOW_SUPPLIERS = "homeShowSuppliers"; const String INV_SOUNDS_BARCODE = "barcodeSounds"; const String INV_SOUNDS_SERVER = "serverSounds"; +// Part settings +const String INV_PART_SHOW_PARAMETERS = "partShowParameters"; + +// Stock settings const String INV_STOCK_SHOW_HISTORY = "stockShowHistory"; const String INV_REPORT_ERRORS = "reportErrors"; diff --git a/lib/settings/app_settings.dart b/lib/settings/app_settings.dart index cf534452..79c12346 100644 --- a/lib/settings/app_settings.dart +++ b/lib/settings/app_settings.dart @@ -25,6 +25,9 @@ class _InvenTreeAppSettingsState extends State { bool barcodeSounds = true; bool serverSounds = true; + // Part settings + bool partShowParameters = true; + // Stock settings bool stockShowHistory = false; @@ -47,6 +50,8 @@ class _InvenTreeAppSettingsState extends State { barcodeSounds = await InvenTreeSettingsManager().getValue(INV_SOUNDS_BARCODE, true) as bool; serverSounds = await InvenTreeSettingsManager().getValue(INV_SOUNDS_SERVER, true) as bool; + partShowParameters = await InvenTreeSettingsManager().getValue(INV_PART_SHOW_PARAMETERS, true) as bool; + stockShowHistory = await InvenTreeSettingsManager().getValue(INV_STOCK_SHOW_HISTORY, false) as bool; reportErrors = await InvenTreeSettingsManager().getValue(INV_REPORT_ERRORS, true) as bool; @@ -132,6 +137,26 @@ class _InvenTreeAppSettingsState extends State { body: Container( child: ListView( children: [ + /* Part Settings */ + ListTile( + title: Text(L10().part, style: TextStyle(fontWeight: FontWeight.bold)), + leading: FaIcon(FontAwesomeIcons.shapes), + ), + ListTile( + title: Text(L10().parameters), + subtitle: Text(L10().parametersSettingDetail), + leading: FaIcon(FontAwesomeIcons.thList), + trailing: Switch( + value: partShowParameters, + onChanged: (bool value) { + InvenTreeSettingsManager().setValue(INV_PART_SHOW_PARAMETERS, value); + setState(() { + partShowParameters = value; + }); + }, + ), + ), + Divider(), /* Stock Settings */ ListTile( title: Text(L10().stock, diff --git a/lib/widget/part_detail.dart b/lib/widget/part_detail.dart index f7384ca8..90088a32 100644 --- a/lib/widget/part_detail.dart +++ b/lib/widget/part_detail.dart @@ -10,11 +10,13 @@ import "package:inventree/helpers.dart"; import "package:inventree/inventree/bom.dart"; import "package:inventree/inventree/part.dart"; import "package:inventree/inventree/stock.dart"; +import "package:inventree/preferences.dart"; import "package:inventree/widget/attachment_widget.dart"; import "package:inventree/widget/bom_list.dart"; import "package:inventree/widget/part_list.dart"; import "package:inventree/widget/part_notes.dart"; +import "package:inventree/widget/part_parameter_widget.dart"; import "package:inventree/widget/progress.dart"; import "package:inventree/widget/category_display.dart"; import "package:inventree/widget/refreshable_state.dart"; @@ -47,6 +49,10 @@ class _PartDisplayState extends RefreshableState { InvenTreePart? parentPart; + int parameterCount = 0; + + bool showParameters = false; + int attachmentCount = 0; int bomCount = 0; @@ -132,6 +138,24 @@ class _PartDisplayState extends RefreshableState { } }); + // Request the number of parameters for this part + if (InvenTreeAPI().supportsPartParameters) { + + showParameters = await InvenTreeSettingsManager().getValue(INV_PART_SHOW_PARAMETERS, true) as bool; + + InvenTreePartParameter().count( + filters: { + "part": part.pk.toString(), + } + ).then((int value) { + if (mounted) { + setState(() { + parameterCount = value; + }); + } + }); + } + // Request the number of attachments InvenTreePartAttachment().count( filters: { @@ -510,20 +534,6 @@ class _PartDisplayState extends RefreshableState { ); } - - // TODO - Add request tests? - /* - if (part.isTrackable) { - tiles.add(ListTile( - title: Text(L10().testsRequired), - leading: FaIcon(FontAwesomeIcons.tasks), - trailing: Text("${part.testTemplateCount}"), - onTap: null, - ) - ); - } - */ - // Notes field tiles.add( ListTile( @@ -539,6 +549,24 @@ class _PartDisplayState extends RefreshableState { ) ); + if (showParameters) { + tiles.add( + ListTile( + title: Text(L10().parameters), + leading: FaIcon(FontAwesomeIcons.thList, color: COLOR_CLICK), + trailing: parameterCount > 0 ? Text(parameterCount.toString()) : null, + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => PartParameterWidget(part) + ) + ); + } + ) + ); + } + tiles.add( ListTile( title: Text(L10().attachments), diff --git a/lib/widget/part_parameter_widget.dart b/lib/widget/part_parameter_widget.dart new file mode 100644 index 00000000..50e8d8dc --- /dev/null +++ b/lib/widget/part_parameter_widget.dart @@ -0,0 +1,105 @@ +import "package:flutter/material.dart"; +import "package:inventree/inventree/model.dart"; + +import "package:inventree/l10.dart"; +import "package:inventree/inventree/part.dart"; +import "package:inventree/widget/paginator.dart"; +import "package:inventree/widget/refreshable_state.dart"; + +/* + * Widget for displaying a list of parameters associated with a given Part instance + */ +class PartParameterWidget extends StatefulWidget { + + const PartParameterWidget(this.part); + + final InvenTreePart part; + + @override + _ParameterWidgetState createState() => _ParameterWidgetState(); +} + + +class _ParameterWidgetState extends RefreshableState { + _ParameterWidgetState(); + + @override + String getAppBarTitle(BuildContext context) { + return L10().parameters; + } + + @override + List getAppBarActions(BuildContext context) { + return []; + } + + @override + Widget getBody(BuildContext context) { + + Map filters = { + "part": widget.part.pk.toString() + }; + + return Column( + children: [ + Expanded( + child: PaginatedParameterList( + filters, + false, + ) + ) + ], + ); + } +} + + +/* + * Widget for displaying a paginated list of Part parameters + */ +class PaginatedParameterList extends PaginatedSearchWidget { + + const PaginatedParameterList(Map filters, bool showSearch) : super(filters: filters, showSearch: showSearch); + + @override + _PaginatedParameterState createState() => _PaginatedParameterState(); +} + + +class _PaginatedParameterState extends PaginatedSearchState { + + _PaginatedParameterState() : super(); + + @override + String get prefix => "parameters_"; + + @override + Map get orderingOptions => { + // TODO + }; + + @override + Map> get filterOptions => { + // TODO + }; + + @override + Future requestPage(int limit, int offset, Map params) async { + + final page = await InvenTreePartParameter().listPaginated(limit, offset, filters: params); + + return page; + } + + @override + Widget buildItem(BuildContext context, InvenTreeModel model) { + + InvenTreePartParameter parameter = model as InvenTreePartParameter; + + return ListTile( + title: Text(parameter.name), + subtitle: Text(parameter.description), + trailing: Text(parameter.valueString), + ); + } +} \ No newline at end of file