From 78a5a9090d0caeba00a767622fb199b622dd217e Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 5 Jul 2022 19:16:06 +1000 Subject: [PATCH] Adds new custom widget for displaying Bill of Materials data --- lib/inventree/bom.dart | 69 ++++++++++++++++++++++ lib/inventree/part.dart | 10 +++- lib/widget/bom_list.dart | 113 ++++++++++++++++++++++++++++++++++++ lib/widget/part_detail.dart | 19 +++--- 4 files changed, 200 insertions(+), 11 deletions(-) create mode 100644 lib/inventree/bom.dart create mode 100644 lib/widget/bom_list.dart diff --git a/lib/inventree/bom.dart b/lib/inventree/bom.dart new file mode 100644 index 00000000..52ad5782 --- /dev/null +++ b/lib/inventree/bom.dart @@ -0,0 +1,69 @@ + + +import "package:inventree/inventree/model.dart"; +import "package:inventree/inventree/part.dart"; + +/* + * Class representing the BomItem database model + */ +class InvenTreeBomItem extends InvenTreeModel { + + InvenTreeBomItem() : super(); + + InvenTreeBomItem.fromJson(Map json) : super.fromJson(json); + + @override + InvenTreeModel createFromJson(Map json) { + return InvenTreeBomItem.fromJson(json); + } + + @override + String get URL => "bom/"; + + @override + Map defaultListFilters() { + return { + "sub_part_detail": "true", + }; + } + + @override + Map defaultGetFilters() { + return { + "sub_part_detail": "true", + }; + } + + // Extract the 'quantity' value associated with this BomItem + double get quantity => double.tryParse(jsondata["quantity"].toString()) ?? 0; + + // Extract the ID of the related part + int get partId => int.tryParse(jsondata["part"].toString()) ?? -1; + + // Return a Part instance for the referenced part + InvenTreePart? get part { + if (jsondata.containsKey("part_detail")) { + dynamic data = jsondata["part_detail"] ?? {}; + if (data is Map) { + return InvenTreePart.fromJson(data); + } + } + + return null; + } + + // Return a Part instance for the referenced sub-part + InvenTreePart? get subPart { + if (jsondata.containsKey("sub_part_detail")) { + dynamic data = jsondata["sub_part_detail"] ?? {}; + if (data is Map) { + return InvenTreePart.fromJson(data); + } + } + + return null; +} + + // Extract the ID of the related sub-part + int get subPartId => int.tryParse(jsondata["sub_part"].toString()) ?? -1; +} \ No newline at end of file diff --git a/lib/inventree/part.dart b/lib/inventree/part.dart index 4b86896e..8e58eaf2 100644 --- a/lib/inventree/part.dart +++ b/lib/inventree/part.dart @@ -10,6 +10,9 @@ import "package:inventree/l10.dart"; import "package:inventree/inventree/model.dart"; +/* + * Class representing the PartCategory database model + */ class InvenTreePartCategory extends InvenTreeModel { InvenTreePartCategory() : super(); @@ -70,6 +73,9 @@ class InvenTreePartCategory extends InvenTreeModel { } +/* + * Class representing the PartTestTemplate database model + */ class InvenTreePartTestTemplate extends InvenTreeModel { InvenTreePartTestTemplate() : super(); @@ -122,6 +128,9 @@ class InvenTreePartTestTemplate extends InvenTreeModel { } +/* + * Class representing the Part database model + */ class InvenTreePart extends InvenTreeModel { InvenTreePart() : super(); @@ -219,7 +228,6 @@ class InvenTreePart extends InvenTreeModel { return _supplierParts; } - // Cached list of test templates List testingTemplates = []; diff --git a/lib/widget/bom_list.dart b/lib/widget/bom_list.dart new file mode 100644 index 00000000..5e51feeb --- /dev/null +++ b/lib/widget/bom_list.dart @@ -0,0 +1,113 @@ + + +import "package:flutter/material.dart"; + +import "package:inventree/api.dart"; +import "package:inventree/helpers.dart"; +import "package:inventree/inventree/bom.dart"; +import "package:inventree/l10.dart"; + +import "package:inventree/inventree/model.dart"; +import "package:inventree/inventree/part.dart"; + +import "package:inventree/widget/paginator.dart"; +import "package:inventree/widget/part_detail.dart"; +import "package:inventree/widget/refreshable_state.dart"; + + +/* + * Widget for displaying a list of BomItems for the specified 'parent' Part instance + */ +class BomList extends StatefulWidget { + + const BomList(this.parent); + + final InvenTreePart parent; + + @override + _BomListState createState() => _BomListState(parent); + +} + + +class _BomListState extends RefreshableState { + + _BomListState(this.parent); + + final InvenTreePart parent; + + @override + String getAppBarTitle(BuildContext context) => L10().billOfMaterials; + + @override + Widget getBody(BuildContext context) { + return PaginatedBomList({ + "part": parent.pk.toString(), + }); + } +} + + +/* + * Create a paginated widget displaying a list of BomItem objects + */ +class PaginatedBomList extends StatefulWidget { + + const PaginatedBomList(this.filters, {this.onTotalChanged}); + + final Map filters; + + final Function(int)? onTotalChanged; + + @override + _PaginatedBomListState createState() => _PaginatedBomListState(filters, onTotalChanged); + +} + + +class _PaginatedBomListState extends PaginatedSearchState { + + _PaginatedBomListState(Map filters, this.onTotalChanged) : super(filters); + + Function(int)? onTotalChanged; + + @override + Future requestPage(int limit, int offset, Map params) async { + + final page = await InvenTreeBomItem().listPaginated(limit, offset, filters: params); + + return page; + } + + @override + Widget buildItem(BuildContext context, InvenTreeModel model) { + + InvenTreeBomItem bomItem = model as InvenTreeBomItem; + + InvenTreePart? subPart = bomItem.subPart; + + String title = subPart?.fullname ?? "error - no name"; + String description = subPart?.description ?? "error - no description"; + + return ListTile( + title: Text(title), + subtitle: Text(description), + trailing: Text( + simpleNumberString(bomItem.quantity), + style: TextStyle(fontWeight: FontWeight.bold), + ), + leading: InvenTreeAPI().getImage( + subPart?.thumbnail ?? "", + width: 40, + height: 40, + ), + onTap: subPart == null ? null : () async { + InvenTreePart().get(bomItem.subPartId).then((var part) { + if (part is InvenTreePart) { + Navigator.push(context, MaterialPageRoute(builder: (context) => PartDetailWidget(part))); + } + }); + }, + ); + } +} \ No newline at end of file diff --git a/lib/widget/part_detail.dart b/lib/widget/part_detail.dart index 2621725f..c7e36b85 100644 --- a/lib/widget/part_detail.dart +++ b/lib/widget/part_detail.dart @@ -10,6 +10,7 @@ import "package:inventree/helpers.dart"; import "package:inventree/inventree/part.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/progress.dart"; @@ -136,8 +137,6 @@ class _PartDisplayState extends RefreshableState { "variant_of": part.pk.toString(), } ); - - print("Variant count: ${variantCount}"); } Future _toggleStar() async { @@ -286,7 +285,7 @@ class _PartDisplayState extends RefreshableState { tiles.add( ListTile( title: Text(L10().variants), - leading: FaIcon(FontAwesomeIcons.sitemap, color: COLOR_CLICK), + leading: FaIcon(FontAwesomeIcons.shapes, color: COLOR_CLICK), trailing: Text(variantCount.toString()), onTap: () { Navigator.push( @@ -310,7 +309,12 @@ class _PartDisplayState extends RefreshableState { title: Text(L10().availableStock), subtitle: Text(L10().stockDetails), leading: FaIcon(FontAwesomeIcons.boxes, color: COLOR_CLICK), - trailing: Text(part.stockString()), + trailing: Text( + part.stockString(), + style: TextStyle( + fontWeight: FontWeight.bold, + ), + ), onTap: () { setState(() { tabIndex = 1; @@ -350,12 +354,7 @@ class _PartDisplayState extends RefreshableState { Navigator.push( context, MaterialPageRoute( - builder: (context) => PartList( - { - "in_bom_for": part.pk.toString(), - }, - title: L10().billOfMaterials, - ) + builder: (context) => BomList(part) ) ); }