From b7e806efeee5e4941be620b811cbbf962c428426 Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 21 Apr 2023 23:15:00 +1000 Subject: [PATCH] PO Line Item Improvements (#340) * Refactor thumbnail image * Add paginated list of purchase order line items * Refactor getBody() function - No longer "have" to specify - Can use getTiles for a simpler interface * Add detail widget for polineitem * add pricing info * Receive line items via action button * tweak color * update release notes * linting fixes --- assets/release_notes.md | 1 + lib/api.dart | 2 + lib/api_form.dart | 8 +- lib/inventree/purchase_order.dart | 39 +++- lib/l10n/app_en.arb | 20 +- lib/widget/attachment_widget.dart | 14 +- lib/widget/bom_list.dart | 12 +- lib/widget/company_detail.dart | 14 +- lib/widget/company_list.dart | 8 +- lib/widget/notifications.dart | 18 +- lib/widget/part_list.dart | 6 +- lib/widget/part_suppliers.dart | 6 +- lib/widget/po_line_detail.dart | 249 ++++++++++++++++++++++++ lib/widget/po_line_list.dart | 91 +++++++++ lib/widget/purchase_order_detail.dart | 166 +--------------- lib/widget/purchase_order_list.dart | 6 +- lib/widget/refreshable_state.dart | 26 +-- lib/widget/search.dart | 14 +- lib/widget/stock_detail.dart | 20 +- lib/widget/stock_item_test_results.dart | 16 +- lib/widget/stock_list.dart | 6 +- lib/widget/supplier_part_detail.dart | 31 +-- lib/widget/supplier_part_list.dart | 12 +- 23 files changed, 442 insertions(+), 343 deletions(-) create mode 100644 lib/widget/po_line_detail.dart create mode 100644 lib/widget/po_line_list.dart diff --git a/assets/release_notes.md b/assets/release_notes.md index 1d67fe51..39384b79 100644 --- a/assets/release_notes.md +++ b/assets/release_notes.md @@ -2,6 +2,7 @@ --- - Add support for Project Codes +- Improve purchase order support - Fix action button colors - Added Norwegian translations - Fix serial number field when creating stock item diff --git a/lib/api.dart b/lib/api.dart index be2ba808..fdb1fbcd 100644 --- a/lib/api.dart +++ b/lib/api.dart @@ -1312,6 +1312,8 @@ class InvenTreeAPI { static String get staticThumb => "/static/img/blank_image.thumbnail.png"; + CachedNetworkImage getThumbnail(String imageUrl, {double size = 40}) => getImage(imageUrl, width: size, height: size); + /* * Load image from the InvenTree server, * or from local cache (if it has been cached!) diff --git a/lib/api_form.dart b/lib/api_form.dart index d08d29a3..83cf3e8c 100644 --- a/lib/api_form.dart +++ b/lib/api_form.dart @@ -612,7 +612,7 @@ class APIFormField { part.description, style: TextStyle(fontWeight: selected ? FontWeight.bold : FontWeight.normal), ) : null, - leading: extended ? InvenTreeAPI().getImage(part.thumbnail, width: 40, height: 40) : null, + leading: extended ? InvenTreeAPI().getThumbnail(part.thumbnail) : null, ); case "partcategory": @@ -662,11 +662,7 @@ class APIFormField { return ListTile( title: Text(company.name), subtitle: extended ? Text(company.description) : null, - leading: InvenTreeAPI().getImage( - company.thumbnail, - width: 40, - height: 40 - ) + leading: InvenTreeAPI().getThumbnail(company.thumbnail) ); case "projectcode": var project_code = InvenTreeProjectCode.fromJson(data); diff --git a/lib/inventree/purchase_order.dart b/lib/inventree/purchase_order.dart index e22d009f..5a4ac720 100644 --- a/lib/inventree/purchase_order.dart +++ b/lib/inventree/purchase_order.dart @@ -1,3 +1,4 @@ +import "package:inventree/helpers.dart"; import "package:inventree/inventree/company.dart"; import "package:inventree/inventree/part.dart"; import "package:inventree/inventree/model.dart"; @@ -175,19 +176,27 @@ class InvenTreePOLineItem extends InvenTreeModel { @override String get URL => "order/po-line/"; + @override + List get rolesRequired => ["purchase_order"]; + @override Map formFields() { return { - // TODO: @Guusggg Not sure what will come here. - // "quantity": {}, - // "reference": {}, - // "notes": {}, - // "order": {}, - // "part": {}, - "received": {}, - // "purchase_price": {}, - // "purchase_price_currency": {}, - // "destination": {} + "part": { + // We cannot edit the supplier part field here + "hidden": true, + }, + "order": { + // We cannot edit the order field here + "hidden": true, + }, + "reference": {}, + "quantity": {}, + "purchase_price": {}, + "purchase_price_currency": {}, + "destination": {}, + "notes": {}, + "link": {}, }; } @@ -211,6 +220,8 @@ class InvenTreePOLineItem extends InvenTreeModel { double get received => getDouble("received"); + String get progressString => simpleNumberString(received) + " / " + simpleNumberString(quantity); + double get outstanding => quantity - received; String get reference => getString("reference"); @@ -229,6 +240,12 @@ class InvenTreePOLineItem extends InvenTreeModel { } } + int get partId => getInt("pk", subKey: "part_detail"); + + String get partName => getString("name", subKey: "part_detail"); + + String get partImage => getString("thumbnail", subKey: "part_detail"); + InvenTreeSupplierPart? get supplierPart { dynamic detail = jsondata["supplier_part_detail"]; @@ -240,6 +257,8 @@ class InvenTreePOLineItem extends InvenTreeModel { } } + String get SKU => getString("SKU", subKey: "supplier_part_detail"); + double get purchasePrice => getDouble("purchase_price"); String get purchasePriceCurrency => getString("purchase_price_currency"); diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 9802d72b..89edcc9a 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -274,6 +274,9 @@ "editItem": "Edit Stock Item", "@editItem": {}, + "editLineItem": "Edit Line Item", + "@editLineItem": {}, + "enterPassword": "Enter password", "@enterPassword": {}, @@ -534,6 +537,9 @@ "lineItems": "Line Items", "@lineItems": {}, + "lineItemUpdated": "Line item updated", + "@lineItemUpdated": {}, + "locateItem": "Locate stock item", "@locateItem": {}, @@ -612,7 +618,7 @@ "outstanding": "Outstanding", "@outstanding": {}, - "outstandingOrderDetail": "Show outstanding orders", + "outstandingOrderDetail": "Show outstanding items", "@outstandingOrderDetail": {}, "packaging": "Packaging", @@ -797,9 +803,6 @@ "quantityPositive": "Quantity must be positive", "@quantityPositive": {}, - "quarantined": "Quarantined", - "@quarantined": {}, - "queryEmpty": "Enter search query", "@queryEmpty": {}, @@ -809,6 +812,9 @@ "received": "Received", "@received": {}, + "receivedFilterDetail": "Show received items", + "@receivedFilterDetail": {}, + "receiveItem": "Receive Item", "@receiveItem": {}, @@ -1038,6 +1044,9 @@ "serverNotSelected": "Server not selected", "@serverNotSelected": {}, + "sku": "SKU", + "@sku": {}, + "sounds": "Sounds", "@sounds": {}, @@ -1256,6 +1265,9 @@ "translateHelp": "Help translate the InvenTree app", "@translateHelp": {}, + "unitPrice": "Unit Price", + "@unitPrice": {}, + "units": "Units", "@units": {}, diff --git a/lib/widget/attachment_widget.dart b/lib/widget/attachment_widget.dart index bdfb4155..1a31993c 100644 --- a/lib/widget/attachment_widget.dart +++ b/lib/widget/attachment_widget.dart @@ -147,19 +147,7 @@ class _AttachmentWidgetState extends RefreshableState { } @override - Widget getBody(BuildContext context) { - return Center( - child: ListView( - children: ListTile.divideTiles( - context: context, - tiles: attachmentTiles(context) - ).toList(), - ) - ); - } - - - List attachmentTiles(BuildContext context) { + List getTiles(BuildContext context) { List tiles = []; diff --git a/lib/widget/bom_list.dart b/lib/widget/bom_list.dart index 8504c75d..f1d867dd 100644 --- a/lib/widget/bom_list.dart +++ b/lib/widget/bom_list.dart @@ -71,11 +71,7 @@ class _BillOfMaterialsState extends RefreshableState { return Column( children: [ ListTile( - leading: InvenTreeAPI().getImage( - widget.part.thumbnail, - width: 32, - height: 32, - ), + leading: InvenTreeAPI().getThumbnail(widget.part.thumbnail), title: Text(widget.part.fullname), subtitle: Text(widget.isParentComponent ? L10().billOfMaterials : L10().usedInDetails), trailing: Text(L10().quantity), @@ -153,11 +149,7 @@ class _PaginatedBomListState extends PaginatedSearchState { simpleNumberString(bomItem.quantity), style: TextStyle(fontWeight: FontWeight.bold), ), - leading: InvenTreeAPI().getImage( - subPart?.thumbnail ?? "", - width: 40, - height: 40, - ), + leading: InvenTreeAPI().getThumbnail(subPart?.thumbnail ?? ""), onTap: subPart == null ? null : () async { showLoadingOverlay(context); diff --git a/lib/widget/company_detail.dart b/lib/widget/company_detail.dart index c1d98a5c..15684a16 100644 --- a/lib/widget/company_detail.dart +++ b/lib/widget/company_detail.dart @@ -130,7 +130,8 @@ class _CompanyDetailState extends RefreshableState { /* * Construct a list of tiles to display for this Company instance */ - List _companyTiles() { + @override + List getTiles(BuildContext context) { List tiles = []; @@ -140,7 +141,7 @@ class _CompanyDetailState extends RefreshableState { child: ListTile( title: Text("${widget.company.name}"), subtitle: Text("${widget.company.description}"), - leading: InvenTreeAPI().getImage(widget.company.image, width: 40, height: 40), + leading: InvenTreeAPI().getThumbnail(widget.company.image), ), )); @@ -290,13 +291,4 @@ class _CompanyDetailState extends RefreshableState { return tiles; } - @override - Widget getBody(BuildContext context) { - - return Center( - child: ListView( - children: _companyTiles(), - ) - ); - } } \ No newline at end of file diff --git a/lib/widget/company_list.dart b/lib/widget/company_list.dart index de9a5395..fe6ecca7 100644 --- a/lib/widget/company_list.dart +++ b/lib/widget/company_list.dart @@ -69,14 +69,10 @@ class _CompanyListState extends PaginatedSearchState { return ListTile( title: Text(company.name), subtitle: Text(company.description), - leading: InvenTreeAPI().getImage( - company.image, - width: 40, - height: 40 - ), + leading: InvenTreeAPI().getThumbnail(company.image), onTap: () async { Navigator.push(context, MaterialPageRoute(builder: (context) => CompanyDetailWidget(company))); }, ); } -} \ No newline at end of file +} diff --git a/lib/widget/notifications.dart b/lib/widget/notifications.dart index 6b37d831..966890cc 100644 --- a/lib/widget/notifications.dart +++ b/lib/widget/notifications.dart @@ -54,7 +54,8 @@ class _NotificationState extends RefreshableState { /* * Display an individual notification message */ - List renderNotifications(BuildContext context) { + @override + List getTiles(BuildContext context) { List tiles = []; @@ -87,17 +88,4 @@ class _NotificationState extends RefreshableState { return tiles; } - - @override - Widget getBody(BuildContext context) { - return Center( - child: ListView( - children: ListTile.divideTiles( - context: context, - tiles: renderNotifications(context), - ).toList() - ) - ); - } - -} \ No newline at end of file +} diff --git a/lib/widget/part_list.dart b/lib/widget/part_list.dart index b6b7bc52..0b00bb90 100644 --- a/lib/widget/part_list.dart +++ b/lib/widget/part_list.dart @@ -133,11 +133,7 @@ class _PaginatedPartListState extends PaginatedSearchState { title: Text(part.fullname), subtitle: Text(part.description), trailing: Text(part.stockString()), - leading: InvenTreeAPI().getImage( - part.thumbnail, - width: 40, - height: 40, - ), + leading: InvenTreeAPI().getThumbnail(part.thumbnail), onTap: () { Navigator.push(context, MaterialPageRoute(builder: (context) => PartDetailWidget(part))); }, diff --git a/lib/widget/part_suppliers.dart b/lib/widget/part_suppliers.dart index 36b3900b..b9fb05ac 100644 --- a/lib/widget/part_suppliers.dart +++ b/lib/widget/part_suppliers.dart @@ -51,11 +51,7 @@ class _PartSupplierState extends RefreshableState { InvenTreeSupplierPart _part = _supplierParts[index]; return ListTile( - leading: InvenTreeAPI().getImage( - _part.supplierImage, - width: 40, - height: 40, - ), + leading: InvenTreeAPI().getThumbnail(_part.supplierImage), title: Text("${_part.SKU}"), subtitle: Text("${_part.manufacturerName}: ${_part.MPN}"), onTap: () async { diff --git a/lib/widget/po_line_detail.dart b/lib/widget/po_line_detail.dart new file mode 100644 index 00000000..9391cdfc --- /dev/null +++ b/lib/widget/po_line_detail.dart @@ -0,0 +1,249 @@ +import "package:flutter/material.dart"; +import "package:flutter_speed_dial/flutter_speed_dial.dart"; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; + +import "package:inventree/api_form.dart"; +import "package:inventree/app_colors.dart"; +import "package:inventree/helpers.dart"; +import "package:inventree/l10.dart"; +import "package:inventree/widget/progress.dart"; +import "package:inventree/widget/part_detail.dart"; + +import "package:inventree/widget/refreshable_state.dart"; +import "package:inventree/inventree/company.dart"; +import "package:inventree/inventree/part.dart"; +import "package:inventree/inventree/purchase_order.dart"; +import "package:inventree/widget/snacks.dart"; +import "package:inventree/widget/supplier_part_detail.dart"; + +/* + * Widget for displaying detail view of a purchase order line item +*/ +class POLineDetailWidget extends StatefulWidget { + + const POLineDetailWidget(this.item, {Key? key}) : super(key: key); + + final InvenTreePOLineItem item; + + @override + _POLineDetailWidgetState createState() => _POLineDetailWidgetState(); + +} + + +/* + * State for the POLineDetailWidget + */ +class _POLineDetailWidgetState extends RefreshableState { + + _POLineDetailWidgetState(); + + @override + String getAppBarTitle() => L10().lineItem; + + @override + List appBarActions(BuildContext context) { + List actions = []; + + if (widget.item.canEdit) { + actions.add( + IconButton( + icon: Icon(Icons.edit_square), + onPressed: () { + _editLineItem(context); + }, + ) + ); + } + + return actions; + } + + @override + List actionButtons(BuildContext context) { + List buttons = []; + + if (widget.item.canCreate) { + // Receive items + if (!widget.item.isComplete) { + buttons.add( + SpeedDialChild( + child: FaIcon(FontAwesomeIcons.rightToBracket, color: Colors.blue), + label: L10().receiveItem, + onTap: () async { + receiveLineItem(context); + } + ) + ); + } + } + + return buttons; + } + + @override + Future request(BuildContext context) async { + await widget.item.reload(); + } + + // Callback to edit this line item + Future _editLineItem(BuildContext context) async { + var fields = widget.item.formFields(); + + widget.item.editForm( + context, + L10().editLineItem, + fields: fields, + onSuccess: (data) async { + refresh(context); + showSnackIcon(L10().lineItemUpdated, success: true); + } + ); + } + + // Launch a form to 'receive' this line item + Future receiveLineItem(BuildContext context) async { + + // Construct fields to receive + Map fields = { + "line_item": { + "parent": "items", + "nested": true, + "hidden": true, + "value": widget.item.pk, + }, + "quantity": { + "parent": "items", + "nested": true, + "value": widget.item.outstanding, + }, + "status": { + "parent": "items", + "nested": true, + }, + "location": { + }, + "barcode": { + "parent": "items", + "nested": true, + "type": "barcode", + "label": L10().barcodeAssign, + "required": false, + } + }; + + showLoadingOverlay(context); + var order = await InvenTreePurchaseOrder().get(widget.item.orderId); + hideLoadingOverlay(); + + if (order is InvenTreePurchaseOrder) { + launchApiForm( + context, + L10().receiveItem, + order.receive_url, + fields, + method: "POST", + icon: FontAwesomeIcons.rightToBracket, + onSuccess: (data) async { + showSnackIcon(L10().receivedItem, success: true); + refresh(context); + } + ); + } else { + showSnackIcon(L10().error); + return; + } + } + + @override + List getTiles(BuildContext context) { + List tiles = []; + + // Reference to the part + tiles.add( + ListTile( + title: Text(L10().internalPart), + subtitle: Text(widget.item.partName), + leading: FaIcon(FontAwesomeIcons.shapes, color: COLOR_ACTION), + trailing: api.getThumbnail(widget.item.partImage), + onTap: () async { + showLoadingOverlay(context); + print("part id: ${widget.item.partId}"); + var part = await InvenTreePart().get(widget.item.partId); + hideLoadingOverlay(); + + if (part is InvenTreePart) { + Navigator.push(context, MaterialPageRoute(builder: (context) => PartDetailWidget(part))); + } + }, + ) + ); + + // Reference to the supplier part + tiles.add( + ListTile( + title: Text(L10().supplierPart), + subtitle: Text(widget.item.SKU), + leading: FaIcon(FontAwesomeIcons.building, color: COLOR_ACTION), + onTap: () async { + showLoadingOverlay(context); + var part = await InvenTreeSupplierPart().get(widget.item.supplierPartId); + hideLoadingOverlay(); + + if (part is InvenTreeSupplierPart) { + Navigator.push(context, MaterialPageRoute(builder: (context) => SupplierPartDetailWidget(part))); + } + }, + ) + ); + + // Recevied + tiles.add( + ListTile( + title: Text(L10().received), + subtitle: Text(widget.item.received.toString()), + trailing: Text(widget.item.progressString, style: TextStyle(color: widget.item.isComplete ? COLOR_SUCCESS: COLOR_WARNING)), + leading: FaIcon(FontAwesomeIcons.boxOpen), + ) + ); + + // Pricing information + tiles.add( + ListTile( + title: Text(L10().unitPrice), + leading: FaIcon(FontAwesomeIcons.dollarSign), + trailing: Text( + renderCurrency(widget.item.purchasePrice, widget.item.purchasePriceCurrency) + ), + ) + ); + + // Note + if (widget.item.notes.isNotEmpty) { + tiles.add( + ListTile( + title: Text(L10().notes), + subtitle: Text(widget.item.notes), + leading: FaIcon(FontAwesomeIcons.noteSticky), + ) + ); + } + + // External link + if (widget.item.link.isNotEmpty) { + tiles.add( + ListTile( + title: Text(L10().link), + subtitle: Text(widget.item.link), + leading: FaIcon(FontAwesomeIcons.link, color: COLOR_ACTION), + onTap: () async { + await openLink(widget.item.link); + }, + ) + ); + } + + return tiles; + } + +} \ No newline at end of file diff --git a/lib/widget/po_line_list.dart b/lib/widget/po_line_list.dart new file mode 100644 index 00000000..13946a14 --- /dev/null +++ b/lib/widget/po_line_list.dart @@ -0,0 +1,91 @@ +import "package:flutter/material.dart"; + +import "package:inventree/api.dart"; +import "package:inventree/app_colors.dart"; +import "package:inventree/l10.dart"; + +import "package:inventree/inventree/company.dart"; +import "package:inventree/inventree/model.dart"; +import "package:inventree/inventree/purchase_order.dart"; + +import "package:inventree/widget/paginator.dart"; +import "package:inventree/widget/po_line_detail.dart"; +import "package:inventree/widget/progress.dart"; + +/* + * Paginated widget class for displaying a list of purchase order line items + */ +class PaginatedPOLineList extends PaginatedSearchWidget { + + const PaginatedPOLineList(Map filters, bool showSearch) : super(filters: filters, showSearch: showSearch); + + @override + _PaginatedPOLineListState createState() => _PaginatedPOLineListState(); + +} + +/* + * State class for PaginatedPOLineList +*/ +class _PaginatedPOLineListState extends PaginatedSearchState { + + _PaginatedPOLineListState() : super(); + + @override + String get prefix => "po_line_"; + + @override + Map get orderingOptions => { + "part": L10().part, + "SKU": L10().sku, + "quantity": L10().quantity, + }; + + @override + Map> get filterOptions => { + "pending": { + "label": L10().outstanding, + "help_text": L10().outstandingOrderDetail, + "tristate": true, + }, + "received": { + "label": L10().received, + "help_text": L10().receivedFilterDetail, + "tristate": true, + } + }; + + @override + Future requestPage(int limit, int offset, Map params) async { + + final page = await InvenTreePOLineItem().listPaginated(limit, offset, filters: params); + return page; + } + + @override + Widget buildItem(BuildContext context, InvenTreeModel model) { + InvenTreePOLineItem item = model as InvenTreePOLineItem; + InvenTreeSupplierPart? supplierPart = item.supplierPart; + + if (supplierPart != null) { + return ListTile( + title: Text(supplierPart.SKU), + subtitle: Text(supplierPart.partName), + trailing: Text(item.progressString, style: TextStyle(color: item.isComplete ? COLOR_SUCCESS : COLOR_WARNING)), + leading: InvenTreeAPI().getThumbnail(supplierPart.partImage), + onTap: () async { + showLoadingOverlay(context); + await item.reload(); + hideLoadingOverlay(); + Navigator.push(context, MaterialPageRoute(builder: (context) => POLineDetailWidget(item))); + }, + ); + } else { + // Return an error tile + return ListTile( + title: Text(L10().error), + subtitle: Text("supplier part not defined", style: TextStyle(color: COLOR_DANGER)), + ); + } + } +} diff --git a/lib/widget/purchase_order_detail.dart b/lib/widget/purchase_order_detail.dart index e6017c57..8b2b6501 100644 --- a/lib/widget/purchase_order_detail.dart +++ b/lib/widget/purchase_order_detail.dart @@ -2,10 +2,9 @@ import "package:flutter/material.dart"; import "package:flutter_speed_dial/flutter_speed_dial.dart"; import "package:font_awesome_flutter/font_awesome_flutter.dart"; import "package:inventree/widget/dialogs.dart"; -import "package:one_context/one_context.dart"; +import "package:inventree/widget/po_line_list.dart"; import "package:inventree/api.dart"; -import "package:inventree/api_form.dart"; import "package:inventree/app_colors.dart"; import "package:inventree/helpers.dart"; import "package:inventree/l10.dart"; @@ -194,7 +193,7 @@ class _PurchaseOrderDetailState extends RefreshableState fields = { - "line_item": { - "parent": "items", - "nested": true, - "hidden": true, - "value": lineItem.pk, - }, - "quantity": { - "parent": "items", - "nested": true, - "value": lineItem.outstanding, - }, - "status": { - "parent": "items", - "nested": true, - }, - "location": { - }, - "barcode": { - "parent": "items", - "nested": true, - "type": "barcode", - "label": L10().barcodeAssign, - "required": false, - } - }; - - // TODO: Pre-fill the "location" value if the part has a default location specified - - launchApiForm( - context, - L10().receiveItem, - order.receive_url, - fields, - method: "POST", - icon: FontAwesomeIcons.rightToBracket, - onSuccess: (data) async { - showSnackIcon(L10().receivedItem, success: true); - refresh(context); - } - ); - } - - /* - * Display a context menu for a particular PurhaseOrderLineItem - */ - void lineItemMenu(BuildContext context, InvenTreePOLineItem lineItem) { - - List children = []; - - // TODO: Add in this option once the SupplierPart detail view is implemented - /* - children.add( - SimpleDialogOption( - onPressed: () { - OneContext().popDialog(); - - // TODO: Navigate to the "SupplierPart" display? - }, - child: ListTile( - title: Text(L10().viewSupplierPart), - leading: FaIcon(FontAwesomeIcons.eye), - ) - ) - ); - */ - - if (order.isPlaced && InvenTreeAPI().supportsPoReceive) { - children.add( - SimpleDialogOption( - onPressed: () { - // Hide the dialog option - OneContext().popDialog(); - - receiveLine(context, lineItem); - }, - child: ListTile( - title: Text(L10().receiveItem), - leading: FaIcon(FontAwesomeIcons.rightToBracket), - ) - ) - ); - } - - // No valid actions available - if (children.isEmpty) { - return; - } - - children.insert(0, Divider()); - - OneContext().showDialog( - builder: (BuildContext context) { - return SimpleDialog( - title: Text(L10().lineItem), - children: children, - ); - } - ); - - } - - List lineTiles(BuildContext context) { - - List tiles = []; - - for (var line in lines) { - - InvenTreeSupplierPart? supplierPart = line.supplierPart; - - if (supplierPart != null) { - - String q = simpleNumberString(line.quantity); - - Color c = Colors.black; - - if (order.isOpen) { - - q = simpleNumberString(line.received) + " / " + simpleNumberString(line.quantity); - - if (line.isComplete) { - c = COLOR_SUCCESS; - } else { - c = COLOR_DANGER; - } - } - - tiles.add( - ListTile( - title: Text(supplierPart.SKU), - subtitle: Text(supplierPart.partName), - leading: InvenTreeAPI().getImage(supplierPart.partImage, width: 40, height: 40), - trailing: Text( - q, - style: TextStyle( - color: c, - ), - ), - onTap: () { - lineItemMenu(context, line); - }, - ) - ); - } - } - - return tiles; - } - @override List getTabIcons(BuildContext context) { return [ @@ -484,7 +331,8 @@ class _PurchaseOrderDetailState extends RefreshableState getTabs(BuildContext context) { return [ ListView(children: orderTiles(context)), - ListView(children: lineTiles(context)), + PaginatedPOLineList({"order": order.pk.toString()}, true), + // ListView(children: lineTiles(context)), PaginatedStockItemList({"purchase_order": order.pk.toString()}, true), ]; } diff --git a/lib/widget/purchase_order_list.dart b/lib/widget/purchase_order_list.dart index a2281985..913a7e2a 100644 --- a/lib/widget/purchase_order_list.dart +++ b/lib/widget/purchase_order_list.dart @@ -154,11 +154,7 @@ class _PaginatedPurchaseOrderListState extends PaginatedSearchState getTabs(BuildContext context) => []; - // Function to construct a body (MUST BE PROVIDED) + // Function to construct a set of tiles for this widget (override if needed) + List getTiles(BuildContext context) => []; + + // Function to construct a body Widget getBody(BuildContext context) { - // Default return is an empty ListView - return ListView(); + // Default body calls getTiles() + return Column( + children: getTiles(context) + ); } @@ -257,15 +263,11 @@ abstract class RefreshableState extends State with floatingActionButton: buildSpeedDial(context), floatingActionButtonLocation: FloatingActionButtonLocation .miniEndDocked, - body: Builder( - builder: (BuildContext context) { - return RefreshIndicator( - onRefresh: () async { - refresh(context); - }, - child: body - ); - } + body: RefreshIndicator( + onRefresh: () async { + refresh(context); + }, + child: body ), bottomNavigationBar: buildBottomAppBar(context, refreshableKey), ); diff --git a/lib/widget/search.dart b/lib/widget/search.dart index fd8fcabb..a719633d 100644 --- a/lib/widget/search.dart +++ b/lib/widget/search.dart @@ -333,7 +333,8 @@ class _SearchDisplayState extends RefreshableState { } } - List _tiles(BuildContext context) { + @override + List getTiles(BuildContext context) { List tiles = []; @@ -542,15 +543,4 @@ class _SearchDisplayState extends RefreshableState { return tiles; } - @override - Widget getBody(BuildContext context) { - return Center( - child: ListView( - children: ListTile.divideTiles( - context: context, - tiles: _tiles(context), - ).toList() - ) - ); - } } diff --git a/lib/widget/stock_detail.dart b/lib/widget/stock_detail.dart index b2dcc2c8..7dc3c11c 100644 --- a/lib/widget/stock_detail.dart +++ b/lib/widget/stock_detail.dart @@ -567,7 +567,7 @@ class _StockItemDisplayState extends RefreshableState { child: ListTile( title: Text("${widget.item.partName}"), subtitle: Text("${widget.item.partDescription}"), - leading: InvenTreeAPI().getImage(widget.item.partImage), + leading: InvenTreeAPI().getThumbnail(widget.item.partImage), trailing: Text( api.StockStatus.label(widget.item.status), style: TextStyle( @@ -595,7 +595,8 @@ class _StockItemDisplayState extends RefreshableState { * Construct a list of detail elements about this StockItem. * The number of elements may vary depending on the StockItem details */ - List detailTiles() { + @override + List getTiles(BuildContext context) { List tiles = []; // Image / name / description @@ -667,11 +668,7 @@ class _StockItemDisplayState extends RefreshableState { title: Text(L10().supplierPart), subtitle: Text(widget.item.supplierSKU), leading: FaIcon(FontAwesomeIcons.building, color: COLOR_ACTION), - trailing: InvenTreeAPI().getImage( - widget.item.supplierImage, - width: 40, - height: 40, - ), + trailing: InvenTreeAPI().getThumbnail(widget.item.supplierImage), onTap: () async { showLoadingOverlay(context); var sp = await InvenTreeSupplierPart().get( @@ -845,13 +842,4 @@ class _StockItemDisplayState extends RefreshableState { return tiles; } - @override - Widget getBody(BuildContext context) { - return ListView( - children: ListTile.divideTiles( - context: context, - tiles: detailTiles() - ).toList() - ); - } } \ No newline at end of file diff --git a/lib/widget/stock_item_test_results.dart b/lib/widget/stock_item_test_results.dart index fc0ef4b5..b89a01b6 100644 --- a/lib/widget/stock_item_test_results.dart +++ b/lib/widget/stock_item_test_results.dart @@ -113,7 +113,8 @@ class _StockItemTestResultDisplayState extends RefreshableState resultsList() { + @override + List getTiles(BuildContext context) { List tiles = []; tiles.add( @@ -121,7 +122,7 @@ class _StockItemTestResultDisplayState extends RefreshableState detailTiles(BuildContext context) { + @override + List getTiles(BuildContext context) { List tiles = []; if (loading) { @@ -116,11 +117,7 @@ class _SupplierPartDisplayState extends RefreshableState