From 33a225b42fb5860604b5c5e86b6e867c68c10c0f Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 3 Feb 2023 12:54:47 +1100 Subject: [PATCH 01/23] Bump version and release noes --- assets/release_notes.md | 5 +++++ pubspec.yaml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/assets/release_notes.md b/assets/release_notes.md index cc6bbcbb..d65e939a 100644 --- a/assets/release_notes.md +++ b/assets/release_notes.md @@ -1,6 +1,11 @@ ## InvenTree App Release Notes --- +### 0.10.0 - February 2023 +--- + +- Add support for Supplier Parts + ### 0.9.3 - February 2023 --- diff --git a/pubspec.yaml b/pubspec.yaml index d8c2154f..a12fa6dd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: inventree description: InvenTree stock management -version: 0.9.3+53 +version: 0.10.0+54 environment: sdk: ">=2.16.0 <3.0.0" From c33e1d10da7d6effa7200ae7cc43270b3e77c51a Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 3 Feb 2023 13:39:01 +1100 Subject: [PATCH 02/23] Add barebone list and detail widgets for the SupplierPart model --- lib/l10n/app_en.arb | 6 ++ lib/widget/supplier_part_detail.dart | 68 ++++++++++++++++++++++ lib/widget/supplier_part_list.dart | 86 ++++++++++++++++++++++++++++ 3 files changed, 160 insertions(+) create mode 100644 lib/widget/supplier_part_detail.dart create mode 100644 lib/widget/supplier_part_list.dart diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 06223ea1..f2d17e22 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1107,6 +1107,12 @@ "supplier": "Supplier", "@supplier": {}, + "supplierPart": "Supplier Part", + "@supplierPart": {}, + + "supplierParts": "Supplier Parts", + "@supplierParts": {}, + "suppliers": "Suppliers", "@suppliers": {}, diff --git a/lib/widget/supplier_part_detail.dart b/lib/widget/supplier_part_detail.dart new file mode 100644 index 00000000..98960c3c --- /dev/null +++ b/lib/widget/supplier_part_detail.dart @@ -0,0 +1,68 @@ +import "package:flutter/material.dart"; + +import "package:inventree/l10.dart"; + +import "package:inventree/inventree/company.dart"; +import 'package:inventree/widget/progress.dart'; + +import "package:inventree/widget/refreshable_state.dart"; + + +/* + * Detail widget for viewing a single SupplierPart instance + */ +class SupplierPartDetailWidget extends StatefulWidget { + + const SupplierPartDetailWidget(this.supplierPart, {Key? key}) : super(key: key); + + final InvenTreeSupplierPart supplierPart; + + @override + _SupplierPartDisplayState createState() => _SupplierPartDisplayState(); +} + + +class _SupplierPartDisplayState extends RefreshableState { + + _SupplierPartDisplayState(); + + @override + String getAppBarTitle(BuildContext context) => L10().supplierPart; + + @override + Future request(BuildContext context) async { + final bool result = widget.supplierPart.pk > 0 && await widget.supplierPart.reload(); + + if (!result) { + Navigator.of(context).pop(); + } + } + + /* + * Build a set of tiles to display for this SupplierPart + */ + List detailTiles(BuildContext context) { + List tiles = []; + + if (loading) { + tiles.add(progressIndicator()); + return tiles; + } + + return tiles; + } + + /* + * Build the widget + */ + @override + Widget getBody(BuildContext context) { + return ListView( + children: ListTile.divideTiles( + context: context, + tiles: detailTiles(context), + ).toList() + ); + } + +} \ No newline at end of file diff --git a/lib/widget/supplier_part_list.dart b/lib/widget/supplier_part_list.dart new file mode 100644 index 00000000..f516191d --- /dev/null +++ b/lib/widget/supplier_part_list.dart @@ -0,0 +1,86 @@ +import "package:flutter/material.dart"; + +import "package:inventree/l10.dart"; + +import "package:inventree/inventree/company.dart"; +import "package:inventree/inventree/model.dart"; + +import "package:inventree/widget/paginator.dart"; +import "package:inventree/widget/refreshable_state.dart"; +import "package:inventree/widget/supplier_part_detail.dart"; + + +/* + * Widget for displaying a list of Supplier Part instances + */ +class SupplierPartList extends StatefulWidget { + + const SupplierPartList(this.filters); + + final Map filters; + + @override + _SupplierPartListState createState() => _SupplierPartListState(); +} + + +class _SupplierPartListState extends RefreshableState { + + @override + String getAppBarTitle(BuildContext context) => L10().supplierPart; + + @override + Widget getBody(BuildContext context) { + return PaginatedSupplierPartList(widget.filters); + } + +} + + +class PaginatedSupplierPartList extends PaginatedSearchWidget { + + const PaginatedSupplierPartList(Map filters) : super(filters: filters); + + @override + _PaginatedSupplierPartListState createState() => _PaginatedSupplierPartListState(); + +} + + +class _PaginatedSupplierPartListState extends PaginatedSearchState { + + _PaginatedSupplierPartListState() : super(); + + @override + String get prefix => "supplierpart_"; + + @override + Map get orderingOptions => {}; + + @override + Map> get filterOptions => {}; + + @override + Future requestPage(int limit, int offset, Map params) async { + final page = await InvenTreeSupplierPart().listPaginated(limit, offset, filters: params); + return page; + } + + @override + Widget buildItem(BuildContext context, InvenTreeModel model) { + InvenTreeSupplierPart supplierPart = model as InvenTreeSupplierPart; + + return ListTile( + title: Text(supplierPart.SKU), + subtitle: Text(supplierPart.partName), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SupplierPartDetailWidget(supplierPart) + ) + ); + }, + ); + } +} \ No newline at end of file From d5c022bd320dd4503779899cd0d60fb3f67ff0dc Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 3 Feb 2023 13:39:23 +1100 Subject: [PATCH 03/23] Launch into SupplierPartList from CompanyDetail --- lib/widget/company_detail.dart | 107 +++++++++++++++++++++++---------- 1 file changed, 74 insertions(+), 33 deletions(-) diff --git a/lib/widget/company_detail.dart b/lib/widget/company_detail.dart index b7ac259e..8e7791f8 100644 --- a/lib/widget/company_detail.dart +++ b/lib/widget/company_detail.dart @@ -1,14 +1,17 @@ +import "package:flutter/material.dart"; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; +import "package:inventree/l10.dart"; import "package:inventree/api.dart"; import "package:inventree/app_colors.dart"; + import "package:inventree/inventree/company.dart"; import "package:inventree/inventree/purchase_order.dart"; + import "package:inventree/widget/purchase_order_list.dart"; import "package:inventree/widget/refreshable_state.dart"; -import "package:flutter/material.dart"; -import "package:font_awesome_flutter/font_awesome_flutter.dart"; -import "package:inventree/l10.dart"; import "package:inventree/widget/snacks.dart"; +import "package:inventree/widget/supplier_part_list.dart"; class CompanyDetailWidget extends StatefulWidget { @@ -18,18 +21,18 @@ class CompanyDetailWidget extends StatefulWidget { final InvenTreeCompany company; @override - _CompanyDetailState createState() => _CompanyDetailState(company); + _CompanyDetailState createState() => _CompanyDetailState(); } class _CompanyDetailState extends RefreshableState { - _CompanyDetailState(this.company); - - final InvenTreeCompany company; + _CompanyDetailState(); List outstandingOrders = []; + + int supplierPartCount = 0; @override String getAppBarTitle(BuildContext context) => L10().company; @@ -43,7 +46,7 @@ class _CompanyDetailState extends RefreshableState { IconButton( icon: FaIcon(FontAwesomeIcons.globe), onPressed: () async { - company.goToInvenTreePage(); + widget.company.goToInvenTreePage(); }, ) ); @@ -64,16 +67,35 @@ class _CompanyDetailState extends RefreshableState { @override Future request(BuildContext context) async { - await company.reload(); + + final bool result = await widget.company.reload(); - if (company.isSupplier) { - outstandingOrders = await company.getPurchaseOrders(outstanding: true); + if (!result || widget.company.pk <= 0) { + // Company could not be loaded for some reason + Navigator.of(context).pop(); + return; + } + + if (widget.company.isSupplier) { + outstandingOrders = await widget.company.getPurchaseOrders(outstanding: true); } + + InvenTreeSupplierPart().count( + filters: { + "supplier": widget.company.pk.toString() + } + ).then((value) { + if (mounted) { + setState(() { + supplierPartCount = value; + }); + } + }); } Future editCompany(BuildContext context) async { - company.editForm( + widget.company.editForm( context, L10().companyEdit, onSuccess: (data) async { @@ -83,6 +105,9 @@ class _CompanyDetailState extends RefreshableState { ); } + /* + * Construct a list of tiles to display for this Company instance + */ List _companyTiles() { List tiles = []; @@ -91,15 +116,15 @@ class _CompanyDetailState extends RefreshableState { tiles.add(Card( child: ListTile( - title: Text("${company.name}"), - subtitle: Text("${company.description}"), - leading: InvenTreeAPI().getImage(company.image, width: 40, height: 40), + title: Text("${widget.company.name}"), + subtitle: Text("${widget.company.description}"), + leading: InvenTreeAPI().getImage(widget.company.image, width: 40, height: 40), ), )); - if (company.website.isNotEmpty) { + if (widget.company.website.isNotEmpty) { tiles.add(ListTile( - title: Text("${company.website}"), + title: Text("${widget.company.website}"), leading: FaIcon(FontAwesomeIcons.globe), onTap: () { // TODO - Open website @@ -109,9 +134,9 @@ class _CompanyDetailState extends RefreshableState { sep = true; } - if (company.email.isNotEmpty) { + if (widget.company.email.isNotEmpty) { tiles.add(ListTile( - title: Text("${company.email}"), + title: Text("${widget.company.email}"), leading: FaIcon(FontAwesomeIcons.at), onTap: () { // TODO - Open email @@ -121,9 +146,9 @@ class _CompanyDetailState extends RefreshableState { sep = true; } - if (company.phone.isNotEmpty) { + if (widget.company.phone.isNotEmpty) { tiles.add(ListTile( - title: Text("${company.phone}"), + title: Text("${widget.company.phone}"), leading: FaIcon(FontAwesomeIcons.phone), onTap: () { // TODO - Call phone number @@ -134,12 +159,12 @@ class _CompanyDetailState extends RefreshableState { } // External link - if (company.link.isNotEmpty) { + if (widget.company.link.isNotEmpty) { tiles.add(ListTile( - title: Text("${company.link}"), + title: Text("${widget.company.link}"), leading: FaIcon(FontAwesomeIcons.link, color: COLOR_CLICK), onTap: () { - company.openLink(); + widget.company.openLink(); }, )); @@ -150,11 +175,27 @@ class _CompanyDetailState extends RefreshableState { tiles.add(Divider()); } - if (company.isSupplier) { - // TODO - Add list of supplier parts - // TODO - Add list of purchase orders - - tiles.add(Divider()); + if (widget.company.isSupplier) { + + if (supplierPartCount > 0) { + tiles.add( + ListTile( + title: Text(L10().supplierParts), + leading: FaIcon(FontAwesomeIcons.building, color: COLOR_CLICK), + trailing: Text(supplierPartCount.toString()), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SupplierPartList({ + "supplier": widget.company.pk.toString() + }) + ) + ); + } + ) + ); + } tiles.add( ListTile( @@ -167,7 +208,7 @@ class _CompanyDetailState extends RefreshableState { MaterialPageRoute( builder: (context) => PurchaseOrderListWidget( filters: { - "supplier": "${company.pk}" + "supplier": "${widget.company.pk}" } ) ) @@ -188,18 +229,18 @@ class _CompanyDetailState extends RefreshableState { */ } - if (company.isManufacturer) { + if (widget.company.isManufacturer) { // TODO - Add list of manufacturer parts } - if (company.isCustomer) { + if (widget.company.isCustomer) { // TODO - Add list of sales orders tiles.add(Divider()); } - if (company.notes.isNotEmpty) { + if (widget.company.notes.isNotEmpty) { tiles.add(ListTile( title: Text(L10().notes), leading: FaIcon(FontAwesomeIcons.stickyNote), From a8e45a9d3b2b522d1019ce68cb8c40e24c29642a Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 3 Feb 2023 13:39:41 +1100 Subject: [PATCH 04/23] Update StockDetail widget --- lib/widget/part_detail.dart | 1 + lib/widget/part_list.dart | 3 - lib/widget/stock_detail.dart | 127 +++++++++++++++++------------------ 3 files changed, 63 insertions(+), 68 deletions(-) diff --git a/lib/widget/part_detail.dart b/lib/widget/part_detail.dart index 9c6b1141..94c72908 100644 --- a/lib/widget/part_detail.dart +++ b/lib/widget/part_detail.dart @@ -117,6 +117,7 @@ class _PartDisplayState extends RefreshableState { if (!result || part.pk == -1) { // Part could not be loaded, for some reason Navigator.of(context).pop(); + return; } // If the part points to a parent "template" part, request that too diff --git a/lib/widget/part_list.dart b/lib/widget/part_list.dart index 3a3cc2f9..4e2646b6 100644 --- a/lib/widget/part_list.dart +++ b/lib/widget/part_list.dart @@ -120,13 +120,10 @@ class _PaginatedPartListState extends PaginatedSearchState { @override Future requestPage(int limit, int offset, Map params) async { - final page = await InvenTreePart().listPaginated(limit, offset, filters: params); - return page; } - @override Widget buildItem(BuildContext context, InvenTreeModel model) { diff --git a/lib/widget/stock_detail.dart b/lib/widget/stock_detail.dart index 91f641ca..ac66a5f2 100644 --- a/lib/widget/stock_detail.dart +++ b/lib/widget/stock_detail.dart @@ -29,13 +29,13 @@ class StockDetailWidget extends StatefulWidget { final InvenTreeStockItem item; @override - _StockItemDisplayState createState() => _StockItemDisplayState(item); + _StockItemDisplayState createState() => _StockItemDisplayState(); } class _StockItemDisplayState extends RefreshableState { - _StockItemDisplayState(this.item); + _StockItemDisplayState(); @override String getAppBarTitle(BuildContext context) => L10().stockItem; @@ -62,7 +62,7 @@ class _StockItemDisplayState extends RefreshableState { icon: FaIcon(FontAwesomeIcons.searchLocation), tooltip: L10().locateItem, onPressed: () async { - InvenTreeAPI().locateItemOrLocation(context, item: item.pk); + InvenTreeAPI().locateItemOrLocation(context, item: widget.item.pk); }, ) ); @@ -82,12 +82,9 @@ class _StockItemDisplayState extends RefreshableState { } Future _openInvenTreePage() async { - item.goToInvenTreePage(); + widget.item.goToInvenTreePage(); } - // StockItem object - final InvenTreeStockItem item; - // Is label printing enabled for this StockItem? // This will be determined when the widget is loaded List> labels = []; @@ -111,7 +108,7 @@ class _StockItemDisplayState extends RefreshableState { stockShowHistory = await InvenTreeSettingsManager().getValue(INV_STOCK_SHOW_HISTORY, false) as bool; - final bool result = item.pk > 0 && await item.reload(); + final bool result = widget.item.pk > 0 && await widget.item.reload(); // Could not load this stock item for some reason // Perhaps it has been depleted? @@ -120,10 +117,10 @@ class _StockItemDisplayState extends RefreshableState { } // Request part information - part = await InvenTreePart().get(item.partId) as InvenTreePart?; + part = await InvenTreePart().get(widget.item.partId) as InvenTreePart?; // Request test results (async) - item.getTestResults().then((value) { + widget.item.getTestResults().then((value) { if (mounted) { setState(() { @@ -135,7 +132,7 @@ class _StockItemDisplayState extends RefreshableState { // Request the number of attachments InvenTreeStockItemAttachment().count( filters: { - "stock_item": item.pk.toString() + "stock_item": widget.item.pk.toString() } ).then((int value) { @@ -165,7 +162,7 @@ class _StockItemDisplayState extends RefreshableState { "/label/stock/", params: { "enabled": "true", - "item": "${item.pk}", + "item": "${widget.item.pk}", }, ).then((APIResponse response) { if (response.isValid() && response.statusCode == 200) { @@ -190,7 +187,7 @@ class _StockItemDisplayState extends RefreshableState { L10().stockItemDeleteConfirm, icon: FontAwesomeIcons.trashAlt, onAccept: () async { - final bool result = await item.delete(); + final bool result = await widget.item.delete(); if (result) { Navigator.of(context).pop(); @@ -264,7 +261,7 @@ class _StockItemDisplayState extends RefreshableState { String pluginKey = (data["plugin"] ?? "") as String; if (labelId != -1 && pluginKey.isNotEmpty) { - String url = "/label/stock/${labelId}/print/?item=${item.pk}&plugin=${pluginKey}"; + String url = "/label/stock/${labelId}/print/?item=${widget.item.pk}&plugin=${pluginKey}"; InvenTreeAPI().get(url).then((APIResponse response) { if (response.isValid() && response.statusCode == 200) { @@ -298,7 +295,7 @@ class _StockItemDisplayState extends RefreshableState { fields.remove("serial"); } - item.editForm( + widget.item.editForm( context, L10().editItem, fields: fields, @@ -320,7 +317,7 @@ class _StockItemDisplayState extends RefreshableState { "parent": "items", "nested": true, "hidden": true, - "value": item.pk, + "value": widget.item.pk, }, "quantity": { "parent": "items", @@ -361,7 +358,7 @@ class _StockItemDisplayState extends RefreshableState { "parent": "items", "nested": true, "hidden": true, - "value": item.pk, + "value": widget.item.pk, }, "quantity": { "parent": "items", @@ -392,12 +389,12 @@ class _StockItemDisplayState extends RefreshableState { "parent": "items", "nested": true, "hidden": true, - "value": item.pk, + "value": widget.item.pk, }, "quantity": { "parent": "items", "nested": true, - "value": item.quantity, + "value": widget.item.quantity, }, "notes": {}, }; @@ -426,18 +423,18 @@ class _StockItemDisplayState extends RefreshableState { "parent": "items", "nested": true, "hidden": true, - "value": item.pk, + "value": widget.item.pk, }, "quantity": { "parent": "items", "nested": true, - "value": item.quantity, + "value": widget.item.quantity, }, "location": {}, "notes": {}, }; - if (item.isSerialized()) { + if (widget.item.isSerialized()) { // Prevent editing of 'quantity' field if the item is serialized fields["quantity"]["hidden"] = true; } @@ -459,20 +456,20 @@ class _StockItemDisplayState extends RefreshableState { Widget headerTile() { return Card( child: ListTile( - title: Text("${item.partName}"), - subtitle: Text("${item.partDescription}"), - leading: InvenTreeAPI().getImage(item.partImage), + title: Text("${widget.item.partName}"), + subtitle: Text("${widget.item.partDescription}"), + leading: InvenTreeAPI().getImage(widget.item.partImage), trailing: Text( - item.statusLabel(), + widget.item.statusLabel(), style: TextStyle( - color: item.statusColor + color: widget.item.statusColor ) ), onTap: () async { - if (item.partId > 0) { + if (widget.item.partId > 0) { showLoadingOverlay(context); - var part = await InvenTreePart().get(item.partId); + var part = await InvenTreePart().get(widget.item.partId); hideLoadingOverlay(); if (part is InvenTreePart) { @@ -501,39 +498,39 @@ class _StockItemDisplayState extends RefreshableState { } // Quantity information - if (item.isSerialized()) { + if (widget.item.isSerialized()) { tiles.add( ListTile( title: Text(L10().serialNumber), leading: FaIcon(FontAwesomeIcons.hashtag), - trailing: Text("${item.serialNumber}"), + trailing: Text("${widget.item.serialNumber}"), ) ); } else { tiles.add( ListTile( - title: item.allocated > 0 ? Text(L10().quantityAvailable) : Text(L10().quantity), + title: widget.item.allocated > 0 ? Text(L10().quantityAvailable) : Text(L10().quantity), leading: FaIcon(FontAwesomeIcons.cubes), - trailing: Text("${item.quantityString()}"), + trailing: Text("${widget.item.quantityString()}"), ) ); } // Location information - if ((item.locationId > 0) && (item.locationName.isNotEmpty)) { + if ((widget.item.locationId > 0) && (widget.item.locationName.isNotEmpty)) { tiles.add( ListTile( title: Text(L10().stockLocation), - subtitle: Text("${item.locationPathString}"), + subtitle: Text("${widget.item.locationPathString}"), leading: FaIcon( FontAwesomeIcons.mapMarkerAlt, color: COLOR_CLICK, ), onTap: () async { - if (item.locationId > 0) { + if (widget.item.locationId > 0) { showLoadingOverlay(context); - var loc = await InvenTreeStockLocation().get(item.locationId); + var loc = await InvenTreeStockLocation().get(widget.item.locationId); hideLoadingOverlay(); if (loc is InvenTreeStockLocation) { @@ -554,7 +551,7 @@ class _StockItemDisplayState extends RefreshableState { ); } - if (item.isBuilding) { + if (widget.item.isBuilding) { tiles.add( ListTile( title: Text(L10().inProduction), @@ -567,44 +564,44 @@ class _StockItemDisplayState extends RefreshableState { ); } - if (item.batch.isNotEmpty) { + if (widget.item.batch.isNotEmpty) { tiles.add( ListTile( title: Text(L10().batchCode), - subtitle: Text(item.batch), + subtitle: Text(widget.item.batch), leading: FaIcon(FontAwesomeIcons.layerGroup), ) ); } - if (item.packaging.isNotEmpty) { + if (widget.item.packaging.isNotEmpty) { tiles.add( ListTile( title: Text(L10().packaging), - subtitle: Text(item.packaging), + subtitle: Text(widget.item.packaging), leading: FaIcon(FontAwesomeIcons.box), ) ); } // Last update? - if (item.updatedDateString.isNotEmpty) { + if (widget.item.updatedDateString.isNotEmpty) { tiles.add( ListTile( title: Text(L10().lastUpdated), - subtitle: Text(item.updatedDateString), + subtitle: Text(widget.item.updatedDateString), leading: FaIcon(FontAwesomeIcons.calendarAlt) ) ); } // Stocktake? - if (item.stocktakeDateString.isNotEmpty) { + if (widget.item.stocktakeDateString.isNotEmpty) { tiles.add( ListTile( title: Text(L10().lastStocktake), - subtitle: Text(item.stocktakeDateString), + subtitle: Text(widget.item.stocktakeDateString), leading: FaIcon(FontAwesomeIcons.calendarAlt) ) ); @@ -626,29 +623,29 @@ class _StockItemDisplayState extends RefreshableState { } */ - if (item.link.isNotEmpty) { + if (widget.item.link.isNotEmpty) { tiles.add( ListTile( - title: Text("${item.link}"), + title: Text("${widget.item.link}"), leading: FaIcon(FontAwesomeIcons.link, color: COLOR_CLICK), onTap: () { - item.openLink(); + widget.item.openLink(); }, ) ); } - if ((item.testResultCount > 0) || (part?.isTrackable ?? false)) { + if ((widget.item.testResultCount > 0) || (part?.isTrackable ?? false)) { tiles.add( ListTile( title: Text(L10().testResults), leading: FaIcon(FontAwesomeIcons.tasks, color: COLOR_CLICK), - trailing: Text("${item.testResultCount}"), + trailing: Text("${widget.item.testResultCount}"), onTap: () { Navigator.push( context, MaterialPageRoute( - builder: (context) => StockItemTestResultsWidget(item)) + builder: (context) => StockItemTestResultsWidget(widget.item)) ).then((ctx) { refresh(context); }); @@ -657,29 +654,29 @@ class _StockItemDisplayState extends RefreshableState { ); } - if (item.hasPurchasePrice) { + if (widget.item.hasPurchasePrice) { tiles.add( ListTile( title: Text(L10().purchasePrice), leading: FaIcon(FontAwesomeIcons.dollarSign), - trailing: Text(item.purchasePrice), + trailing: Text(widget.item.purchasePrice), ) ); } // TODO - Is this stock item linked to a PurchaseOrder? - if (stockShowHistory && item.trackingItemCount > 0) { + if (stockShowHistory && widget.item.trackingItemCount > 0) { tiles.add( ListTile( title: Text(L10().history), leading: FaIcon(FontAwesomeIcons.history, color: COLOR_CLICK), - trailing: Text("${item.trackingItemCount}"), + trailing: Text("${widget.item.trackingItemCount}"), onTap: () { Navigator.push( context, MaterialPageRoute( - builder: (context) => StockItemHistoryWidget(item)) + builder: (context) => StockItemHistoryWidget(widget.item)) ).then((ctx) { refresh(context); }); @@ -696,7 +693,7 @@ class _StockItemDisplayState extends RefreshableState { onTap: () { Navigator.push( context, - MaterialPageRoute(builder: (context) => StockNotesWidget(item)) + MaterialPageRoute(builder: (context) => StockNotesWidget(widget.item)) ); } ) @@ -713,7 +710,7 @@ class _StockItemDisplayState extends RefreshableState { MaterialPageRoute( builder: (context) => AttachmentWidget( InvenTreeStockItemAttachment(), - item.pk, + widget.item.pk, InvenTreeAPI().checkPermission("stock", "change")) ) ); @@ -748,13 +745,13 @@ class _StockItemDisplayState extends RefreshableState { } // "Count" is not available for serialized stock - if (!item.isSerialized()) { + if (!widget.item.isSerialized()) { tiles.add( ListTile( title: Text(L10().countStock), leading: FaIcon(FontAwesomeIcons.checkCircle, color: COLOR_CLICK), onTap: _countStockDialog, - trailing: Text(item.quantityString(includeUnits: true)), + trailing: Text(widget.item.quantityString(includeUnits: true)), ) ); @@ -794,7 +791,7 @@ class _StockItemDisplayState extends RefreshableState { onTap: () { Navigator.push( context, - MaterialPageRoute(builder: (context) => InvenTreeQRView(StockItemScanIntoLocationHandler(item))) + MaterialPageRoute(builder: (context) => InvenTreeQRView(StockItemScanIntoLocationHandler(widget.item))) ).then((ctx) { refresh(context); }); @@ -802,8 +799,8 @@ class _StockItemDisplayState extends RefreshableState { ) ); - if (InvenTreeAPI().supportModernBarcodes || item.customBarcode.isEmpty) { - tiles.add(customBarcodeActionTile(context, this, item.customBarcode, "stockitem", item.pk)); + if (InvenTreeAPI().supportModernBarcodes || widget.item.customBarcode.isEmpty) { + tiles.add(customBarcodeActionTile(context, this, widget.item.customBarcode, "stockitem", widget.item.pk)); } else { // Note: Custom legacy barcodes (only for StockItem model) are handled differently tiles.add( @@ -811,7 +808,7 @@ class _StockItemDisplayState extends RefreshableState { title: Text(L10().barcodeUnassign), leading: Icon(Icons.qr_code, color: COLOR_CLICK), onTap: () async { - await item.update(values: {"uid": ""}); + await widget.item.update(values: {"uid": ""}); refresh(context); } ) From 2398f53b974912b5ba79e5ef2a0143f7526ba488 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 3 Feb 2023 13:41:21 +1100 Subject: [PATCH 05/23] Fixes for SupplierPart model --- lib/inventree/company.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/inventree/company.dart b/lib/inventree/company.dart index 2dec8922..a5afc96d 100644 --- a/lib/inventree/company.dart +++ b/lib/inventree/company.dart @@ -118,17 +118,17 @@ class InvenTreeSupplierPart extends InvenTreeModel { int get manufacturerId => (jsondata["manufacturer"] ?? -1) as int; - String get manufacturerName => (jsondata["manufacturer_detail"]["name"] ?? "") as String; + String get manufacturerName => (jsondata["manufacturer_detail"]?["name"] ?? "") as String; - String get manufacturerImage => (jsondata["manufacturer_detail"]["image"] ?? jsondata["manufacturer_detail"]["thumbnail"] ?? InvenTreeAPI.staticThumb) as String; + String get manufacturerImage => (jsondata["manufacturer_detail"]?["image"] ?? jsondata["manufacturer_detail"]["thumbnail"] ?? InvenTreeAPI.staticThumb) as String; int get manufacturerPartId => (jsondata["manufacturer_part"] ?? -1) as int; int get supplierId => (jsondata["supplier"] ?? -1) as int; - String get supplierName => (jsondata["supplier_detail"]["name"] ?? "") as String; + String get supplierName => (jsondata["supplier_detail"]?["name"] ?? "") as String; - String get supplierImage => (jsondata["supplier_detail"]["image"] ?? jsondata["supplier_detail"]["thumbnail"] ?? InvenTreeAPI.staticThumb) as String; + String get supplierImage => (jsondata["supplier_detail"]?["image"] ?? jsondata["supplier_detail"]["thumbnail"] ?? InvenTreeAPI.staticThumb) as String; String get SKU => (jsondata["SKU"] ?? "") as String; @@ -136,9 +136,9 @@ class InvenTreeSupplierPart extends InvenTreeModel { int get partId => (jsondata["part"] ?? -1) as int; - String get partImage => (jsondata["part_detail"]["thumbnail"] ?? InvenTreeAPI.staticThumb) as String; + String get partImage => (jsondata["part_detail"]?["thumbnail"] ?? InvenTreeAPI.staticThumb) as String; - String get partName => (jsondata["part_detail"]["full_name"] ?? "") as String; + String get partName => (jsondata["part_detail"]?["full_name"] ?? "") as String; @override InvenTreeModel createFromJson(Map json) { From 3491c88be16afe6def4ccca47718a0b907ead56a Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 3 Feb 2023 13:45:38 +1100 Subject: [PATCH 06/23] Add images to supplier part list --- lib/inventree/company.dart | 2 +- lib/widget/supplier_part_list.dart | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/inventree/company.dart b/lib/inventree/company.dart index a5afc96d..bfb77534 100644 --- a/lib/inventree/company.dart +++ b/lib/inventree/company.dart @@ -102,7 +102,7 @@ class InvenTreeSupplierPart extends InvenTreeModel { return { "manufacturer_detail": "true", "supplier_detail": "true", - "manufacturer_part_detail": "true", + "part_detail": "true", }; } diff --git a/lib/widget/supplier_part_list.dart b/lib/widget/supplier_part_list.dart index f516191d..d0bb1d8f 100644 --- a/lib/widget/supplier_part_list.dart +++ b/lib/widget/supplier_part_list.dart @@ -1,5 +1,6 @@ import "package:flutter/material.dart"; +import "package:inventree/api.dart"; import "package:inventree/l10.dart"; import "package:inventree/inventree/company.dart"; @@ -27,7 +28,7 @@ class SupplierPartList extends StatefulWidget { class _SupplierPartListState extends RefreshableState { @override - String getAppBarTitle(BuildContext context) => L10().supplierPart; + String getAppBarTitle(BuildContext context) => L10().supplierParts; @override Widget getBody(BuildContext context) { @@ -73,6 +74,16 @@ class _PaginatedSupplierPartListState extends PaginatedSearchState Date: Fri, 3 Feb 2023 13:49:57 +1100 Subject: [PATCH 07/23] Add search functionality to SupplierPart list --- lib/widget/supplier_part_list.dart | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/widget/supplier_part_list.dart b/lib/widget/supplier_part_list.dart index d0bb1d8f..7a7ea8c1 100644 --- a/lib/widget/supplier_part_list.dart +++ b/lib/widget/supplier_part_list.dart @@ -1,4 +1,5 @@ import "package:flutter/material.dart"; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; import "package:inventree/api.dart"; import "package:inventree/l10.dart"; @@ -30,9 +31,23 @@ class _SupplierPartListState extends RefreshableState { @override String getAppBarTitle(BuildContext context) => L10().supplierParts; + bool showFilterOptions = false; + + @override + List getAppBarActions(BuildContext context) => [ + IconButton( + icon: FaIcon(FontAwesomeIcons.filter), + onPressed: () async { + setState(() { + showFilterOptions = !showFilterOptions; + }); + }, + ) + ]; + @override Widget getBody(BuildContext context) { - return PaginatedSupplierPartList(widget.filters); + return PaginatedSupplierPartList(widget.filters, showFilterOptions); } } @@ -40,7 +55,7 @@ class _SupplierPartListState extends RefreshableState { class PaginatedSupplierPartList extends PaginatedSearchWidget { - const PaginatedSupplierPartList(Map filters) : super(filters: filters); + const PaginatedSupplierPartList(Map filters, bool showSearch) : super(filters: filters, showSearch: showSearch); @override _PaginatedSupplierPartListState createState() => _PaginatedSupplierPartListState(); From cb2bbf9114a49e37cd3e2fbd5731f49045ad31cf Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 3 Feb 2023 14:08:43 +1100 Subject: [PATCH 08/23] Added details to SupplierPartDetail widget --- lib/inventree/company.dart | 8 ++-- lib/widget/supplier_part_detail.dart | 59 +++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/lib/inventree/company.dart b/lib/inventree/company.dart index bfb77534..9acbc870 100644 --- a/lib/inventree/company.dart +++ b/lib/inventree/company.dart @@ -116,10 +116,10 @@ class InvenTreeSupplierPart extends InvenTreeModel { return _filters(); } - int get manufacturerId => (jsondata["manufacturer"] ?? -1) as int; - String get manufacturerName => (jsondata["manufacturer_detail"]?["name"] ?? "") as String; + String get MPN => (jsondata["manufacturer_part_detail"]?["MPN"] ?? "") as String; + String get manufacturerImage => (jsondata["manufacturer_detail"]?["image"] ?? jsondata["manufacturer_detail"]["thumbnail"] ?? InvenTreeAPI.staticThumb) as String; int get manufacturerPartId => (jsondata["manufacturer_part"] ?? -1) as int; @@ -132,14 +132,14 @@ class InvenTreeSupplierPart extends InvenTreeModel { String get SKU => (jsondata["SKU"] ?? "") as String; - String get MPN => (jsondata["MPN"] ?? "") as String; - int get partId => (jsondata["part"] ?? -1) as int; String get partImage => (jsondata["part_detail"]?["thumbnail"] ?? InvenTreeAPI.staticThumb) as String; String get partName => (jsondata["part_detail"]?["full_name"] ?? "") as String; + String get partDescription => (jsondata["part_detail"]?["description"] ?? "") as String; + @override InvenTreeModel createFromJson(Map json) { var part = InvenTreeSupplierPart.fromJson(json); diff --git a/lib/widget/supplier_part_detail.dart b/lib/widget/supplier_part_detail.dart index 98960c3c..8f3882cc 100644 --- a/lib/widget/supplier_part_detail.dart +++ b/lib/widget/supplier_part_detail.dart @@ -1,10 +1,14 @@ import "package:flutter/material.dart"; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; +import "package:inventree/api.dart"; import "package:inventree/l10.dart"; +import "package:inventree/inventree/part.dart"; import "package:inventree/inventree/company.dart"; -import 'package:inventree/widget/progress.dart'; +import "package:inventree/widget/part_detail.dart"; +import "package:inventree/widget/progress.dart"; import "package:inventree/widget/refreshable_state.dart"; @@ -49,6 +53,59 @@ class _SupplierPartDisplayState extends RefreshableState PartDetailWidget(part))); + } + }, + ) + ); + + // Supplier part details + tiles.add( + ListTile( + title: Text(widget.supplierPart.SKU), + subtitle: Text(widget.supplierPart.supplierName), + leading: FaIcon(FontAwesomeIcons.building), + trailing: InvenTreeAPI().getImage( + widget.supplierPart.supplierImage, + width: 40, + height: 40, + ), + ) + ); + + // Manufacturer information + if (widget.supplierPart.manufacturerPartId > 0) { + tiles.add( + ListTile( + subtitle: Text(widget.supplierPart.manufacturerName), + title: Text(widget.supplierPart.MPN), + leading: FaIcon(FontAwesomeIcons.industry), + trailing: InvenTreeAPI().getImage( + widget.supplierPart.manufacturerImage, + width: 40, + height: 40, + ) + ) + ); + } + return tiles; } From f36b3205bef28c2e3f17366c5537f37395921425 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 3 Feb 2023 14:11:33 +1100 Subject: [PATCH 09/23] Link through to supplier company --- lib/widget/supplier_part_detail.dart | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/widget/supplier_part_detail.dart b/lib/widget/supplier_part_detail.dart index 8f3882cc..6d32a2e5 100644 --- a/lib/widget/supplier_part_detail.dart +++ b/lib/widget/supplier_part_detail.dart @@ -7,6 +7,7 @@ import "package:inventree/l10.dart"; import "package:inventree/inventree/part.dart"; import "package:inventree/inventree/company.dart"; +import "package:inventree/widget/company_detail.dart"; import "package:inventree/widget/part_detail.dart"; import "package:inventree/widget/progress.dart"; import "package:inventree/widget/refreshable_state.dart"; @@ -87,6 +88,17 @@ class _SupplierPartDisplayState extends RefreshableState CompanyDetailWidget(supplier) + )); + } + } ) ); From 11a463ea403a9cac0f14adfecea9b01cec3c9e74 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 3 Feb 2023 14:15:51 +1100 Subject: [PATCH 10/23] Add some more details --- lib/inventree/company.dart | 2 ++ lib/widget/supplier_part_detail.dart | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/lib/inventree/company.dart b/lib/inventree/company.dart index 9acbc870..d1a90542 100644 --- a/lib/inventree/company.dart +++ b/lib/inventree/company.dart @@ -140,6 +140,8 @@ class InvenTreeSupplierPart extends InvenTreeModel { String get partDescription => (jsondata["part_detail"]?["description"] ?? "") as String; + String get note => (jsondata["note"] ?? "") as String; + @override InvenTreeModel createFromJson(Map json) { var part = InvenTreeSupplierPart.fromJson(json); diff --git a/lib/widget/supplier_part_detail.dart b/lib/widget/supplier_part_detail.dart index 6d32a2e5..0d2a9ff3 100644 --- a/lib/widget/supplier_part_detail.dart +++ b/lib/widget/supplier_part_detail.dart @@ -11,6 +11,7 @@ import "package:inventree/widget/company_detail.dart"; import "package:inventree/widget/part_detail.dart"; import "package:inventree/widget/progress.dart"; import "package:inventree/widget/refreshable_state.dart"; +import 'package:url_launcher/url_launcher.dart'; /* @@ -118,6 +119,29 @@ class _SupplierPartDisplayState extends RefreshableState Date: Fri, 3 Feb 2023 14:26:29 +1100 Subject: [PATCH 11/23] Adds ability to edit SupplierPart information --- lib/api_form.dart | 19 ++++++++++++++--- lib/inventree/company.dart | 12 +++++++++++ lib/l10n/app_en.arb | 6 ++++++ lib/widget/supplier_part_detail.dart | 32 ++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 3 deletions(-) diff --git a/lib/api_form.dart b/lib/api_form.dart index db9e6673..e21eedbf 100644 --- a/lib/api_form.dart +++ b/lib/api_form.dart @@ -5,19 +5,21 @@ import "package:intl/intl.dart"; import "package:font_awesome_flutter/font_awesome_flutter.dart"; import "package:dropdown_search/dropdown_search.dart"; import "package:datetime_picker_formfield/datetime_picker_formfield.dart"; +import "package:flutter/material.dart"; import "package:inventree/api.dart"; import "package:inventree/app_colors.dart"; import "package:inventree/barcode.dart"; import "package:inventree/helpers.dart"; +import "package:inventree/l10.dart"; + +import "package:inventree/inventree/company.dart"; import "package:inventree/inventree/part.dart"; import "package:inventree/inventree/sentry.dart"; import "package:inventree/inventree/stock.dart"; + import "package:inventree/widget/dialogs.dart"; import "package:inventree/widget/fields.dart"; -import "package:inventree/l10.dart"; - -import "package:flutter/material.dart"; import "package:inventree/widget/progress.dart"; import "package:inventree/widget/snacks.dart"; @@ -642,6 +644,17 @@ class APIFormField { title: Text(name), leading: FaIcon(isGroup ? FontAwesomeIcons.users : FontAwesomeIcons.user), ); + case "company": + var company = InvenTreeCompany.fromJson(data); + return ListTile( + title: Text(company.name), + subtitle: extended ? Text(company.description) : null, + leading: InvenTreeAPI().getImage( + company.thumbnail, + width: 40, + height: 40 + ) + ); default: return ListTile( title: Text( diff --git a/lib/inventree/company.dart b/lib/inventree/company.dart index d1a90542..85845541 100644 --- a/lib/inventree/company.dart +++ b/lib/inventree/company.dart @@ -98,6 +98,18 @@ class InvenTreeSupplierPart extends InvenTreeModel { @override String get URL => "company/part/"; + @override + Map formFields() { + return { + "supplier": {}, + "SKU": {}, + "link": {}, + "note": {}, + "packaging": {}, + "pack_size": {}, + }; + } + Map _filters() { return { "manufacturer_detail": "true", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index f2d17e22..73061de3 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1110,6 +1110,12 @@ "supplierPart": "Supplier Part", "@supplierPart": {}, + "supplierPartEdit": "Edit Supplier Part", + "@supplierPartEdit": {}, + + "supplierPartUpdated": "Supplier Part Updated", + "@supplierPartUpdated": {}, + "supplierParts": "Supplier Parts", "@supplierParts": {}, diff --git a/lib/widget/supplier_part_detail.dart b/lib/widget/supplier_part_detail.dart index 0d2a9ff3..281b0c57 100644 --- a/lib/widget/supplier_part_detail.dart +++ b/lib/widget/supplier_part_detail.dart @@ -11,6 +11,7 @@ import "package:inventree/widget/company_detail.dart"; 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:url_launcher/url_launcher.dart'; @@ -35,6 +36,37 @@ class _SupplierPartDisplayState extends RefreshableState L10().supplierPart; + @override + List getAppBarActions(BuildContext context) { + List actions = []; + + actions.add( + IconButton( + icon: FaIcon(FontAwesomeIcons.edit), + tooltip: L10().edit, + onPressed: () { + editSupplierPart(context); + }, + ) + ); + + return actions; + } + + /* + * Launch a form to edit the current SupplierPart instance + */ + Future editSupplierPart(BuildContext context) async { + widget.supplierPart.editForm( + context, + L10().supplierPartEdit, + onSuccess: (data) async { + refresh(context); + showSnackIcon(L10().supplierPartUpdated, success: true); + } + ); + } + @override Future request(BuildContext context) async { final bool result = widget.supplierPart.pk > 0 && await widget.supplierPart.reload(); From 81ddae69a62ea15aa11b7fc4406f372d475e0ddd Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 3 Feb 2023 14:29:18 +1100 Subject: [PATCH 12/23] Navigate to supplier part list from part detail page --- lib/widget/part_detail.dart | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/lib/widget/part_detail.dart b/lib/widget/part_detail.dart index 94c72908..b347e497 100644 --- a/lib/widget/part_detail.dart +++ b/lib/widget/part_detail.dart @@ -25,6 +25,7 @@ import "package:inventree/widget/part_image_widget.dart"; import "package:inventree/widget/snacks.dart"; import "package:inventree/widget/stock_detail.dart"; import "package:inventree/widget/stock_list.dart"; +import "package:inventree/widget/supplier_part_list.dart"; /* @@ -523,21 +524,24 @@ class _PartDisplayState extends RefreshableState { } if (part.isPurchaseable) { - tiles.add( - ListTile( - title: Text(L10().suppliers), - leading: FaIcon(FontAwesomeIcons.industry), - trailing: Text("${part.supplierCount}"), - /* TODO: - onTap: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => PartSupplierWidget(part)) - ); - }, - */ - ) - ); + + if (part.supplierCount > 0) { + tiles.add( + ListTile( + title: Text(L10().suppliers), + leading: FaIcon(FontAwesomeIcons.industry, color: COLOR_CLICK), + trailing: Text("${part.supplierCount}"), + onTap: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => SupplierPartList({ + "part": part.pk.toString() + })) + ); + }, + ) + ); + } } if (showParameters) { From b203d87e368e314cddfca062ad8cbeaa0df71ea4 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 3 Feb 2023 14:41:43 +1100 Subject: [PATCH 13/23] Display supplier part information on stock item detail page --- lib/inventree/stock.dart | 10 ++++++---- lib/widget/stock_detail.dart | 32 ++++++++++++++++---------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/lib/inventree/stock.dart b/lib/inventree/stock.dart index ff29cd49..be76ce72 100644 --- a/lib/inventree/stock.dart +++ b/lib/inventree/stock.dart @@ -416,8 +416,10 @@ class InvenTreeStockItem extends InvenTreeModel { String get supplierImage { String thumb = ""; - if (jsondata.containsKey("supplier_detail")) { - thumb = (jsondata["supplier_detail"]["supplier_logo"] ?? "") as String; + if (jsondata.containsKey("supplier_part_detail")) { + thumb = (jsondata["supplier_part_detail"]?["supplier_detail"]?["image"] ?? "") as String; + } else if (jsondata.containsKey("supplier_detail")) { + thumb = (jsondata["supplier_detail"]["image"] ?? "") as String; } return thumb; @@ -440,8 +442,8 @@ class InvenTreeStockItem extends InvenTreeModel { String get supplierSKU { String sku = ""; - if (jsondata.containsKey("supplier_detail")) { - sku = (jsondata["supplier_detail"]["SKU"] ?? "") as String; + if (jsondata.containsKey("supplier_part_detail")) { + sku = (jsondata["supplier_part_detail"]["SKU"] ?? "") as String; } return sku; diff --git a/lib/widget/stock_detail.dart b/lib/widget/stock_detail.dart index ac66a5f2..dcccbc1d 100644 --- a/lib/widget/stock_detail.dart +++ b/lib/widget/stock_detail.dart @@ -551,6 +551,22 @@ class _StockItemDisplayState extends RefreshableState { ); } + // Supplier part information (if available) + if (widget.item.supplierPartId > 0) { + tiles.add( + ListTile( + title: Text(L10().supplierPart), + subtitle: Text(widget.item.supplierSKU), + leading: FaIcon(FontAwesomeIcons.building, color: COLOR_CLICK), + trailing: InvenTreeAPI().getImage( + widget.item.supplierImage, + width: 40, + height: 40, + ), + ) + ); + } + if (widget.item.isBuilding) { tiles.add( ListTile( @@ -607,22 +623,6 @@ class _StockItemDisplayState extends RefreshableState { ); } - // Supplier part? - // TODO: Display supplier part info page? - /* - if (item.supplierPartId > 0) { - tiles.add( - ListTile( - title: Text("${item.supplierName}"), - subtitle: Text("${item.supplierSKU}"), - leading: FaIcon(FontAwesomeIcons.industry), - trailing: InvenTreeAPI().getImage(item.supplierImage), - onTap: null, - ) - ); - } - */ - if (widget.item.link.isNotEmpty) { tiles.add( ListTile( From ad4db201ad449c99ad9e25d3f61b585be4db5982 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 3 Feb 2023 14:57:19 +1100 Subject: [PATCH 14/23] Add barcode scan response for SupplierPart --- lib/barcode.dart | 51 ++++++++++++++++++++++++++++++++------------- lib/l10n/app_en.arb | 3 +++ 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/lib/barcode.dart b/lib/barcode.dart index 16c98fc4..55427d9f 100644 --- a/lib/barcode.dart +++ b/lib/barcode.dart @@ -1,7 +1,6 @@ import "dart:io"; import "package:flutter/material.dart"; import "package:font_awesome_flutter/font_awesome_flutter.dart"; -import "package:inventree/widget/refreshable_state.dart"; import "package:one_context/one_context.dart"; import "package:qr_code_scanner/qr_code_scanner.dart"; @@ -11,10 +10,13 @@ import "package:inventree/helpers.dart"; import "package:inventree/l10.dart"; import "package:inventree/preferences.dart"; +import "package:inventree/inventree/company.dart"; +import "package:inventree/inventree/part.dart"; import "package:inventree/inventree/sentry.dart"; import "package:inventree/inventree/stock.dart"; -import "package:inventree/inventree/part.dart"; +import "package:inventree/widget/refreshable_state.dart"; +import "package:inventree/widget/supplier_part_detail.dart"; import "package:inventree/widget/dialogs.dart"; import "package:inventree/widget/snacks.dart"; import "package:inventree/widget/location_display.dart"; @@ -183,16 +185,13 @@ class BarcodeScanHandler extends BarcodeHandler { @override Future onBarcodeMatched(Map data) async { - int pk = -1; // A stocklocation has been passed? if (data.containsKey("stocklocation")) { - pk = (data["stocklocation"]?["pk"] ?? -1) as int; if (pk > 0) { - barcodeSuccessTone(); InvenTreeStockLocation().get(pk).then((var loc) { @@ -203,25 +202,22 @@ class BarcodeScanHandler extends BarcodeHandler { icon: Icons.qr_code, ); OneContext().pop(); - OneContext().navigator.push(MaterialPageRoute(builder: (context) => LocationDisplayWidget(loc))); + OneContext().navigator.push(MaterialPageRoute( + builder: (context) => LocationDisplayWidget(loc))); } }); } else { - barcodeFailureTone(); showSnackIcon( - L10().invalidStockLocation, - success: false + L10().invalidStockLocation, + success: false ); } - } else if (data.containsKey("stockitem")) { - pk = (data["stockitem"]?["pk"] ?? -1) as int; if (pk > 0) { - barcodeSuccessTone(); InvenTreeStockItem().get(pk).then((var item) { @@ -232,11 +228,11 @@ class BarcodeScanHandler extends BarcodeHandler { ); OneContext().pop(); if (item is InvenTreeStockItem) { - OneContext().push(MaterialPageRoute(builder: (context) => StockDetailWidget(item))); + OneContext().push(MaterialPageRoute( + builder: (context) => StockDetailWidget(item))); } }); } else { - barcodeFailureTone(); showSnackIcon( @@ -244,12 +240,37 @@ class BarcodeScanHandler extends BarcodeHandler { success: false ); } + } else if (data.containsKey("supplierpart")) { + pk = (data["supplierpart"]?["pk"] ?? -1) as int; + + if (pk > 0) { + barcodeSuccessTone(); + + InvenTreeSupplierPart().get(pk).then((var supplierpart) { + showSnackIcon( + L10().supplierPart, + success: true, + icon: Icons.qr_code, + ); + + OneContext().pop(); + + if (supplierpart is InvenTreeSupplierPart) { + OneContext().push(MaterialPageRoute(builder: (context) => SupplierPartDetailWidget(supplierPart))); + } + }); + } else { + barcodeFailureTone(); + showSnackIcon( + L10().invalidSupplierPart, + success: false, + ); + } } else if (data.containsKey("part")) { pk = (data["part"]?["pk"] ?? -1) as int; if (pk > 0) { - barcodeSuccessTone(); InvenTreePart().get(pk).then((var part) { diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 73061de3..879b7147 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -467,6 +467,9 @@ "invalidStockItem": "Invalid Stock Item", "@invalidStockItem": {}, + "invalidSupplierPart": "Invalid Supplier Part", + "@invalidSupplierPart": {}, + "invalidUsernamePassword": "Invalid username / password combination", "@invalidUsernamePassword": {}, From b26f262f5b7ff6b1f3c027eb8c7cbfc38cbc5585 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 3 Feb 2023 14:57:30 +1100 Subject: [PATCH 15/23] Refactor barcode scanning code --- lib/barcode.dart | 207 +++++++++++++++++++++++++---------------------- 1 file changed, 109 insertions(+), 98 deletions(-) diff --git a/lib/barcode.dart b/lib/barcode.dart index 55427d9f..e9d02624 100644 --- a/lib/barcode.dart +++ b/lib/barcode.dart @@ -165,6 +165,7 @@ class BarcodeHandler { * - StockLocation * - StockItem * - Part + * - SupplierPart */ class BarcodeScanHandler extends BarcodeHandler { @@ -183,123 +184,134 @@ class BarcodeScanHandler extends BarcodeHandler { ); } - @override - Future onBarcodeMatched(Map data) async { - int pk = -1; - - // A stocklocation has been passed? - if (data.containsKey("stocklocation")) { - pk = (data["stocklocation"]?["pk"] ?? -1) as int; + /* + * Response when a "Part" instance is scanned + */ + Future handlePart(int pk) async { + InvenTreePart().get(pk).then((var part) { + showSnackIcon( + L10().part, + success: true, + icon: Icons.qr_code, + ); + // Dismiss the barcode scanner + OneContext().pop(); - if (pk > 0) { - barcodeSuccessTone(); + if (part is InvenTreePart) { + OneContext().push(MaterialPageRoute(builder: (context) => PartDetailWidget(part))); + } + }); + } - InvenTreeStockLocation().get(pk).then((var loc) { - if (loc is InvenTreeStockLocation) { - showSnackIcon( - L10().stockLocation, - success: true, - icon: Icons.qr_code, - ); - OneContext().pop(); - OneContext().navigator.push(MaterialPageRoute( - builder: (context) => LocationDisplayWidget(loc))); - } - }); - } else { - barcodeFailureTone(); + /* + * Response when a "StockItem" instance is scanned + */ + Future handleStockItem(int pk) async { + InvenTreeStockItem().get(pk).then((var item) { + showSnackIcon( + L10().stockItem, + success: true, + icon: Icons.qr_code, + ); + OneContext().pop(); + if (item is InvenTreeStockItem) { + OneContext().push(MaterialPageRoute( + builder: (context) => StockDetailWidget(item))); + } + }); + } + /* + * Response when a "StockLocation" instance is scanned + */ + Future handleStockLocation(int pk) async { + InvenTreeStockLocation().get(pk).then((var loc) { + if (loc is InvenTreeStockLocation) { showSnackIcon( - L10().invalidStockLocation, - success: false + L10().stockLocation, + success: true, + icon: Icons.qr_code, ); + OneContext().pop(); + OneContext().navigator.push(MaterialPageRoute( + builder: (context) => LocationDisplayWidget(loc))); } - } else if (data.containsKey("stockitem")) { - pk = (data["stockitem"]?["pk"] ?? -1) as int; + }); + } - if (pk > 0) { - barcodeSuccessTone(); + /* + * Response when a "SupplierPart" instance is scanned + */ + Future handleSupplierPart(int pk) async { + InvenTreeSupplierPart().get(pk).then((var supplierpart) { + showSnackIcon( + L10().supplierPart, + success: true, + icon: Icons.qr_code, + ); - InvenTreeStockItem().get(pk).then((var item) { - showSnackIcon( - L10().stockItem, - success: true, - icon: Icons.qr_code, - ); - OneContext().pop(); - if (item is InvenTreeStockItem) { - OneContext().push(MaterialPageRoute( - builder: (context) => StockDetailWidget(item))); - } - }); - } else { - barcodeFailureTone(); + OneContext().pop(); - showSnackIcon( - L10().invalidStockItem, - success: false - ); + if (supplierpart is InvenTreeSupplierPart) { + OneContext().push(MaterialPageRoute(builder: (context) => SupplierPartDetailWidget(supplierpart))); } - } else if (data.containsKey("supplierpart")) { - pk = (data["supplierpart"]?["pk"] ?? -1) as int; - - if (pk > 0) { - barcodeSuccessTone(); + }); + } - InvenTreeSupplierPart().get(pk).then((var supplierpart) { - showSnackIcon( - L10().supplierPart, - success: true, - icon: Icons.qr_code, - ); + @override + Future onBarcodeMatched(Map data) async { + int pk = -1; - OneContext().pop(); + String model = ""; - if (supplierpart is InvenTreeSupplierPart) { - OneContext().push(MaterialPageRoute(builder: (context) => SupplierPartDetailWidget(supplierPart))); - } - }); - } else { - barcodeFailureTone(); - showSnackIcon( - L10().invalidSupplierPart, - success: false, - ); - } - } else if (data.containsKey("part")) { + // The following model types can be matched with barcodes + final List validModels = [ + "part", + "stockitem", + "stocklocation", + "supplierpart" + ]; - pk = (data["part"]?["pk"] ?? -1) as int; + for (var key in validModels) { + if (data.containsKey(key)) { + pk = (data[key]?["pk"] ?? -1) as int; - if (pk > 0) { - barcodeSuccessTone(); - - InvenTreePart().get(pk).then((var part) { - showSnackIcon( - L10().part, - success: true, - icon: Icons.qr_code, - ); - // Dismiss the barcode scanner - OneContext().pop(); + // Break on the first valid match found + if (pk > 0) { + model = key; + break; + } + } + } - if (part is InvenTreePart) { - OneContext().push(MaterialPageRoute(builder: (context) => PartDetailWidget(part))); - } - }); - } else { + // A valid result has been found + if (pk > 0 && model.isNotEmpty) { - barcodeFailureTone(); + barcodeSuccessTone(); - showSnackIcon( - L10().invalidPart, - success: false - ); + switch (model) { + case "part": + handlePart(pk); + return; + case "stockitem": + handleStockItem(pk); + return; + case "stocklocation": + handleStockLocation(pk); + return; + case "supplierpart": + handleSupplierPart(pk); + return; + default: + // Fall through to failure state + break; } - } else { + } - barcodeFailureTone(); + // If we get here, we have not found a valid barcode result! + barcodeFailureTone(); - showSnackIcon( + showSnackIcon( L10().barcodeUnknown, success: false, onAction: () { @@ -316,8 +328,7 @@ class BarcodeScanHandler extends BarcodeHandler { ) ); } - ); - } + ); } } From c22e9951b8bdd178c5ae775dbf2bd0cc3df91683 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 3 Feb 2023 15:00:37 +1100 Subject: [PATCH 16/23] Navigate to supplier part detail from stock item page --- lib/widget/stock_detail.dart | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/widget/stock_detail.dart b/lib/widget/stock_detail.dart index dcccbc1d..e29635bd 100644 --- a/lib/widget/stock_detail.dart +++ b/lib/widget/stock_detail.dart @@ -4,8 +4,16 @@ import "package:font_awesome_flutter/font_awesome_flutter.dart"; import "package:inventree/app_colors.dart"; import "package:inventree/barcode.dart"; +import "package:inventree/l10.dart"; +import "package:inventree/api.dart"; +import "package:inventree/api_form.dart"; +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/supplier_part_detail.dart"; import "package:inventree/widget/dialogs.dart"; import "package:inventree/widget/attachment_widget.dart"; import "package:inventree/widget/location_display.dart"; @@ -16,10 +24,6 @@ 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/api.dart"; -import "package:inventree/api_form.dart"; -import "package:inventree/preferences.dart"; class StockDetailWidget extends StatefulWidget { @@ -563,6 +567,18 @@ class _StockItemDisplayState extends RefreshableState { width: 40, height: 40, ), + onTap: () async { + showLoadingOverlay(context); + var sp = await InvenTreeSupplierPart().get( + widget.item.supplierPartId); + hideLoadingOverlay(); + if (sp is InvenTreeSupplierPart) { + Navigator.push( + context, MaterialPageRoute( + builder: (context) => SupplierPartDetailWidget(sp)) + ); + } + } ) ); } From 9d03b8aca96d27b858b2a77399e5a3bf310c9352 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 3 Feb 2023 15:06:48 +1100 Subject: [PATCH 17/23] Support custom barcode for SupplierPart via app --- lib/widget/supplier_part_detail.dart | 59 ++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/lib/widget/supplier_part_detail.dart b/lib/widget/supplier_part_detail.dart index 281b0c57..a559615f 100644 --- a/lib/widget/supplier_part_detail.dart +++ b/lib/widget/supplier_part_detail.dart @@ -2,6 +2,7 @@ import "package:flutter/material.dart"; import "package:font_awesome_flutter/font_awesome_flutter.dart"; import "package:inventree/api.dart"; +import 'package:inventree/barcode.dart'; import "package:inventree/l10.dart"; import "package:inventree/inventree/part.dart"; @@ -178,16 +179,60 @@ class _SupplierPartDisplayState extends RefreshableState actionTiles(BuildContext context) { + List tiles = []; + + tiles.add( + customBarcodeActionTile(context, this, widget.supplierPart.customBarcode, "supplierpart", widget.supplierPart.pk) + ); + + return tiles; + } + + @override + Widget getSelectedWidget(int index) { + switch (index) { + case 0: + return ListView( + children: ListTile.divideTiles( + context: context, + tiles: detailTiles(context), + ).toList() + ); + case 1: + return ListView( + children: ListTile.divideTiles( + context: context, + tiles: actionTiles(context) + ).toList() + ); + default: + return ListView(); + } + } + @override Widget getBody(BuildContext context) { - return ListView( - children: ListTile.divideTiles( - context: context, - tiles: detailTiles(context), - ).toList() - ); + return getSelectedWidget(tabIndex); } + @override + Widget getBottomNavBar(BuildContext context) { + return BottomNavigationBar( + currentIndex: tabIndex, + onTap: onTabSelectionChanged, + items: [ + BottomNavigationBarItem( + icon: FaIcon(FontAwesomeIcons.infoCircle), + label: L10().details, + ), + BottomNavigationBarItem( + icon: FaIcon(FontAwesomeIcons.wrench), + label: L10().actions + ) + ] + ); + } } \ No newline at end of file From d8f14cff1c630bfbb7441428d95b62dd5dc8d401 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 3 Feb 2023 15:07:59 +1100 Subject: [PATCH 18/23] Cleanup comment --- lib/widget/supplier_part_detail.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/widget/supplier_part_detail.dart b/lib/widget/supplier_part_detail.dart index a559615f..a8730946 100644 --- a/lib/widget/supplier_part_detail.dart +++ b/lib/widget/supplier_part_detail.dart @@ -111,7 +111,7 @@ class _SupplierPartDisplayState extends RefreshableState Date: Fri, 3 Feb 2023 15:13:08 +1100 Subject: [PATCH 19/23] linting --- lib/widget/supplier_part_detail.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/widget/supplier_part_detail.dart b/lib/widget/supplier_part_detail.dart index a8730946..b6f048e2 100644 --- a/lib/widget/supplier_part_detail.dart +++ b/lib/widget/supplier_part_detail.dart @@ -2,7 +2,7 @@ import "package:flutter/material.dart"; import "package:font_awesome_flutter/font_awesome_flutter.dart"; import "package:inventree/api.dart"; -import 'package:inventree/barcode.dart'; +import "package:inventree/barcode.dart"; import "package:inventree/l10.dart"; import "package:inventree/inventree/part.dart"; @@ -12,8 +12,8 @@ import "package:inventree/widget/company_detail.dart"; 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:url_launcher/url_launcher.dart'; +import "package:inventree/widget/snacks.dart"; +import "package:url_launcher/url_launcher.dart"; /* From c10867a7adba604eb8e9fb7db83fe0e59eda4e72 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 3 Feb 2023 15:15:57 +1100 Subject: [PATCH 20/23] Fix override --- lib/widget/supplier_part_detail.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/widget/supplier_part_detail.dart b/lib/widget/supplier_part_detail.dart index b6f048e2..4f307403 100644 --- a/lib/widget/supplier_part_detail.dart +++ b/lib/widget/supplier_part_detail.dart @@ -190,8 +190,7 @@ class _SupplierPartDisplayState extends RefreshableState Date: Fri, 3 Feb 2023 15:18:41 +1100 Subject: [PATCH 21/23] Enable display of supplier list on home screen --- lib/settings/home_settings.dart | 6 +++--- lib/widget/home.dart | 9 ++++++--- lib/widget/supplier_part_detail.dart | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/settings/home_settings.dart b/lib/settings/home_settings.dart index 13e19cd2..071f96f1 100644 --- a/lib/settings/home_settings.dart +++ b/lib/settings/home_settings.dart @@ -85,9 +85,6 @@ class _HomeScreenSettingsState extends State { }, ), ), - // TODO: When these features are improved, add them back in! - // Currently, the company display does not provide any value - /* ListTile( title: Text(L10().homeShowSuppliers), subtitle: Text(L10().homeShowSuppliersDescription), @@ -102,6 +99,9 @@ class _HomeScreenSettingsState extends State { }, ), ), + // TODO: When these features are improved, add them back in! + // Currently, the company display does not provide any value + /* ListTile( title: Text(L10().homeShowManufacturers), subtitle: Text(L10().homeShowManufacturersDescription), diff --git a/lib/widget/home.dart b/lib/widget/home.dart index 84fef290..a731caf8 100644 --- a/lib/widget/home.dart +++ b/lib/widget/home.dart @@ -24,6 +24,7 @@ import "package:inventree/widget/purchase_order_list.dart"; import "package:inventree/widget/search.dart"; import "package:inventree/widget/snacks.dart"; import "package:inventree/widget/spinner.dart"; +import "package:inventree/widget/company_list.dart"; class InvenTreeHomePage extends StatefulWidget { @@ -126,13 +127,13 @@ class _InvenTreeHomePageState extends State { ); } - /* void _showSuppliers(BuildContext context) { if (!InvenTreeAPI().checkConnection()) return; Navigator.push(context, MaterialPageRoute(builder: (context) => CompanyListWidget(L10().suppliers, {"is_supplier": "true"}))); } + /* void _showManufacturers(BuildContext context) { if (!InvenTreeAPI().checkConnection()) return; @@ -312,8 +313,6 @@ class _InvenTreeHomePageState extends State { )); } - // TODO: Add these tiles back in once the features are fleshed out - /* // Suppliers if (homeShowSuppliers) { tiles.add(_listTile( @@ -326,6 +325,10 @@ class _InvenTreeHomePageState extends State { )); } + // TODO: Add these tiles back in once the features are fleshed out + /* + + // Manufacturers if (homeShowManufacturers) { tiles.add(_listTile( diff --git a/lib/widget/supplier_part_detail.dart b/lib/widget/supplier_part_detail.dart index 4f307403..ee45e26e 100644 --- a/lib/widget/supplier_part_detail.dart +++ b/lib/widget/supplier_part_detail.dart @@ -190,7 +190,7 @@ class _SupplierPartDisplayState extends RefreshableState Date: Sat, 4 Feb 2023 08:56:46 +1100 Subject: [PATCH 22/23] Code cleanup --- lib/widget/attachment_widget.dart | 5 ++++- lib/widget/bom_list.dart | 2 +- lib/widget/category_display.dart | 37 ++++++++++++++----------------- lib/widget/category_list.dart | 8 +++---- lib/widget/company_detail.dart | 3 +++ lib/widget/company_list.dart | 17 +++++++------- lib/widget/dialogs.dart | 3 ++- lib/widget/drawer.dart | 18 +++++++++++---- lib/widget/home.dart | 2 +- 9 files changed, 54 insertions(+), 41 deletions(-) diff --git a/lib/widget/attachment_widget.dart b/lib/widget/attachment_widget.dart index b137d9ab..6d54bdd6 100644 --- a/lib/widget/attachment_widget.dart +++ b/lib/widget/attachment_widget.dart @@ -6,13 +6,16 @@ import "package:font_awesome_flutter/font_awesome_flutter.dart"; import "package:one_context/one_context.dart"; import "package:url_launcher/url_launcher.dart"; +import "package:inventree/l10.dart"; import "package:inventree/app_colors.dart"; + import "package:inventree/inventree/model.dart"; + import "package:inventree/widget/fields.dart"; import "package:inventree/widget/progress.dart"; import "package:inventree/widget/snacks.dart"; import "package:inventree/widget/refreshable_state.dart"; -import "package:inventree/l10.dart"; + /* * A generic widget for displaying a list of attachments. diff --git a/lib/widget/bom_list.dart b/lib/widget/bom_list.dart index aeed5519..022fc38c 100644 --- a/lib/widget/bom_list.dart +++ b/lib/widget/bom_list.dart @@ -4,9 +4,9 @@ import "package:font_awesome_flutter/font_awesome_flutter.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/bom.dart"; import "package:inventree/inventree/model.dart"; import "package:inventree/inventree/part.dart"; diff --git a/lib/widget/category_display.dart b/lib/widget/category_display.dart index 14516c63..a432e3a1 100644 --- a/lib/widget/category_display.dart +++ b/lib/widget/category_display.dart @@ -22,13 +22,13 @@ class CategoryDisplayWidget extends StatefulWidget { final InvenTreePartCategory? category; @override - _CategoryDisplayState createState() => _CategoryDisplayState(category); + _CategoryDisplayState createState() => _CategoryDisplayState(); } class _CategoryDisplayState extends RefreshableState { - _CategoryDisplayState(this.category); + _CategoryDisplayState(); bool showFilterOptions = false; @@ -40,7 +40,7 @@ class _CategoryDisplayState extends RefreshableState { List actions = []; - if ((category != null) && InvenTreeAPI().checkPermission("part_category", "change")) { + if ((widget.category != null) && InvenTreeAPI().checkPermission("part_category", "change")) { actions.add( IconButton( icon: FaIcon(FontAwesomeIcons.edit), @@ -57,7 +57,7 @@ class _CategoryDisplayState extends RefreshableState { } void _editCategoryDialog(BuildContext context) { - final _cat = category; + final _cat = widget.category; // Cannot edit top-level category if (_cat == null) { @@ -74,9 +74,6 @@ class _CategoryDisplayState extends RefreshableState { ); } - // The local InvenTreePartCategory object - final InvenTreePartCategory? category; - @override Future onBuild(BuildContext context) async { refresh(context); @@ -86,8 +83,8 @@ class _CategoryDisplayState extends RefreshableState { Future request(BuildContext context) async { // Update the category - if (category != null) { - final bool result = await category?.reload() ?? false; + if (widget.category != null) { + final bool result = await widget.category?.reload() ?? false; if (!result) { Navigator.of(context).pop(); @@ -96,7 +93,7 @@ class _CategoryDisplayState extends RefreshableState { } Widget getCategoryDescriptionCard({bool extra = true}) { - if (category == null) { + if (widget.category == null) { return Card( child: ListTile( leading: FaIcon(FontAwesomeIcons.shapes), @@ -110,11 +107,11 @@ class _CategoryDisplayState extends RefreshableState { List children = [ ListTile( - title: Text("${category?.name}", + title: Text("${widget.category?.name}", style: TextStyle(fontWeight: FontWeight.bold) ), - subtitle: Text("${category?.description}"), - leading: category!.customIcon ?? FaIcon(FontAwesomeIcons.sitemap), + subtitle: Text("${widget.category?.description}"), + leading: widget.category!.customIcon ?? FaIcon(FontAwesomeIcons.sitemap), ), ]; @@ -122,14 +119,14 @@ class _CategoryDisplayState extends RefreshableState { children.add( ListTile( title: Text(L10().parentCategory), - subtitle: Text("${category?.parentPathString}"), + subtitle: Text("${widget.category?.parentPathString}"), leading: FaIcon( FontAwesomeIcons.levelUpAlt, color: COLOR_CLICK, ), onTap: () async { - int parentId = category?.parentId ?? -1; + int parentId = widget.category?.parentId ?? -1; if (parentId < 0) { Navigator.push(context, MaterialPageRoute(builder: (context) => CategoryDisplayWidget(null))); @@ -200,7 +197,7 @@ class _CategoryDisplayState extends RefreshableState { Expanded( child: PaginatedPartCategoryList( { - "parent": category?.pk.toString() ?? "null" + "parent": widget.category?.pk.toString() ?? "null" }, showFilterOptions, ), @@ -215,7 +212,7 @@ class _CategoryDisplayState extends RefreshableState { List partsTiles() { Map filters = { - "category": category?.pk.toString() ?? "null", + "category": widget.category?.pk.toString() ?? "null", }; return [ @@ -246,7 +243,7 @@ class _CategoryDisplayState extends RefreshableState { Future _newCategory(BuildContext context) async { - int pk = category?.pk ?? -1; + int pk = widget.category?.pk ?? -1; InvenTreePartCategory().createForm( context, @@ -276,7 +273,7 @@ class _CategoryDisplayState extends RefreshableState { Future _newPart() async { - int pk = category?.pk ?? -1; + int pk = widget.category?.pk ?? -1; InvenTreePart().createForm( context, @@ -320,7 +317,7 @@ class _CategoryDisplayState extends RefreshableState { ) ); - if (category != null) { + if (widget.category != null) { tiles.add( ListTile( title: Text(L10().partCreate), diff --git a/lib/widget/category_list.dart b/lib/widget/category_list.dart index 37f4b4bb..3e6f5be3 100644 --- a/lib/widget/category_list.dart +++ b/lib/widget/category_list.dart @@ -17,16 +17,14 @@ class PartCategoryList extends StatefulWidget { final Map filters; @override - _PartCategoryListState createState() => _PartCategoryListState(filters); + _PartCategoryListState createState() => _PartCategoryListState(); } class _PartCategoryListState extends RefreshableState { - _PartCategoryListState(this.filters); - - final Map filters; + _PartCategoryListState(); bool showFilterOptions = false; @@ -47,7 +45,7 @@ class _PartCategoryListState extends RefreshableState { @override Widget getBody(BuildContext context) { - return PaginatedPartCategoryList(filters, showFilterOptions); + return PaginatedPartCategoryList(widget.filters, showFilterOptions); } } diff --git a/lib/widget/company_detail.dart b/lib/widget/company_detail.dart index 8e7791f8..e5f05469 100644 --- a/lib/widget/company_detail.dart +++ b/lib/widget/company_detail.dart @@ -14,6 +14,9 @@ import "package:inventree/widget/snacks.dart"; import "package:inventree/widget/supplier_part_list.dart"; +/* + * Widget for displaying detail view of a single Company instance + */ class CompanyDetailWidget extends StatefulWidget { const CompanyDetailWidget(this.company, {Key? key}) : super(key: key); diff --git a/lib/widget/company_list.dart b/lib/widget/company_list.dart index ed8dd4df..033423de 100644 --- a/lib/widget/company_list.dart +++ b/lib/widget/company_list.dart @@ -2,13 +2,18 @@ import "package:flutter/material.dart"; import "package:inventree/api.dart"; + import "package:inventree/inventree/company.dart"; import "package:inventree/inventree/model.dart"; + import "package:inventree/widget/paginator.dart"; import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/widget/company_detail.dart"; +/* + * Widget for displaying a filterable list of Company instances + */ class CompanyListWidget extends StatefulWidget { const CompanyListWidget(this.title, this.filters, {Key? key}) : super(key: key); @@ -18,24 +23,20 @@ class CompanyListWidget extends StatefulWidget { final Map filters; @override - _CompanyListWidgetState createState() => _CompanyListWidgetState(title, filters); + _CompanyListWidgetState createState() => _CompanyListWidgetState(); } class _CompanyListWidgetState extends RefreshableState { - _CompanyListWidgetState(this.title, this.filters); - - final String title; - - final Map filters; + _CompanyListWidgetState(); @override - String getAppBarTitle(BuildContext context) => title; + String getAppBarTitle(BuildContext context) => widget.title; @override Widget getBody(BuildContext context) { - return PaginatedCompanyList(filters, true); + return PaginatedCompanyList(widget.filters, true); } } diff --git a/lib/widget/dialogs.dart b/lib/widget/dialogs.dart index 2b6d511b..322e20c3 100644 --- a/lib/widget/dialogs.dart +++ b/lib/widget/dialogs.dart @@ -1,10 +1,11 @@ import "package:flutter/material.dart"; import "package:font_awesome_flutter/font_awesome_flutter.dart"; -import "package:inventree/helpers.dart"; import "package:one_context/one_context.dart"; import "package:inventree/api.dart"; +import "package:inventree/helpers.dart"; import "package:inventree/l10.dart"; + import "package:inventree/preferences.dart"; import "package:inventree/widget/snacks.dart"; diff --git a/lib/widget/drawer.dart b/lib/widget/drawer.dart index cff24de8..0aecc5f4 100644 --- a/lib/widget/drawer.dart +++ b/lib/widget/drawer.dart @@ -1,14 +1,24 @@ +import "package:flutter/material.dart"; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; +import "package:package_info_plus/package_info_plus.dart"; + import "package:inventree/api.dart"; import "package:inventree/barcode.dart"; -import "package:flutter/material.dart"; import "package:inventree/l10.dart"; -import "package:inventree/settings/about.dart"; +import "package:inventree/settings/about.dart"; import "package:inventree/settings/settings.dart"; -import "package:font_awesome_flutter/font_awesome_flutter.dart"; + import "package:inventree/widget/search.dart"; -import "package:package_info_plus/package_info_plus.dart"; + +/* + * Custom "drawer" widget for the InvenTree app. + * + * - Provides a "home" button which completely unwinds the widget stack + * - Global search + * - Barcoed scan + */ class InvenTreeDrawer extends StatelessWidget { const InvenTreeDrawer(this.context); diff --git a/lib/widget/home.dart b/lib/widget/home.dart index a731caf8..0d15546f 100644 --- a/lib/widget/home.dart +++ b/lib/widget/home.dart @@ -301,7 +301,7 @@ class _InvenTreeHomePageState extends State { } )); - // Purchase orderes + // Purchase orders if (homeShowPo) { tiles.add(_listTile( context, From 6a1e8759865c55a3c07468f3d2e66fb5a4abc600 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sat, 4 Feb 2023 08:57:44 +1100 Subject: [PATCH 23/23] Update release noets --- assets/release_notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/assets/release_notes.md b/assets/release_notes.md index d65e939a..dab31188 100644 --- a/assets/release_notes.md +++ b/assets/release_notes.md @@ -5,6 +5,7 @@ --- - Add support for Supplier Parts +- Updated translations ### 0.9.3 - February 2023 ---