mirror of
				https://github.com/inventree/inventree-app.git
				synced 2025-10-31 13:25:40 +00:00 
			
		
		
		
	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
This commit is contained in:
		| @@ -147,19 +147,7 @@ class _AttachmentWidgetState extends RefreshableState<AttachmentWidget> { | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget getBody(BuildContext context) { | ||||
|     return Center( | ||||
|         child: ListView( | ||||
|           children: ListTile.divideTiles( | ||||
|               context: context, | ||||
|               tiles: attachmentTiles(context) | ||||
|           ).toList(), | ||||
|         ) | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|  | ||||
|   List<Widget> attachmentTiles(BuildContext context) { | ||||
|   List<Widget> getTiles(BuildContext context) { | ||||
|  | ||||
|     List<Widget> tiles = []; | ||||
|  | ||||
|   | ||||
| @@ -71,11 +71,7 @@ class _BillOfMaterialsState extends RefreshableState<BillOfMaterialsWidget> { | ||||
|     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<PaginatedBomList> { | ||||
|         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); | ||||
|   | ||||
| @@ -130,7 +130,8 @@ class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> { | ||||
|   /* | ||||
|    * Construct a list of tiles to display for this Company instance | ||||
|    */ | ||||
|   List<Widget> _companyTiles() { | ||||
|   @override | ||||
|   List<Widget> getTiles(BuildContext context) { | ||||
|  | ||||
|     List<Widget> tiles = []; | ||||
|  | ||||
| @@ -140,7 +141,7 @@ class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> { | ||||
|       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<CompanyDetailWidget> { | ||||
|     return tiles; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget getBody(BuildContext context) { | ||||
|  | ||||
|     return Center( | ||||
|       child: ListView( | ||||
|         children: _companyTiles(), | ||||
|       ) | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -69,14 +69,10 @@ class _CompanyListState extends PaginatedSearchState<PaginatedCompanyList> { | ||||
|     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))); | ||||
|       }, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -54,7 +54,8 @@ class _NotificationState extends RefreshableState<NotificationWidget> { | ||||
|   /* | ||||
|    * Display an individual notification message | ||||
|    */ | ||||
|   List<Widget> renderNotifications(BuildContext context) { | ||||
|   @override | ||||
|   List<Widget> getTiles(BuildContext context) { | ||||
|  | ||||
|     List<Widget> tiles = []; | ||||
|  | ||||
| @@ -87,17 +88,4 @@ class _NotificationState extends RefreshableState<NotificationWidget> { | ||||
|     return tiles; | ||||
|  | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget getBody(BuildContext context) { | ||||
|     return Center( | ||||
|       child: ListView( | ||||
|         children: ListTile.divideTiles( | ||||
|           context: context, | ||||
|           tiles: renderNotifications(context), | ||||
|         ).toList() | ||||
|       ) | ||||
|     ); | ||||
|   } | ||||
|  | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -133,11 +133,7 @@ class _PaginatedPartListState extends PaginatedSearchState<PaginatedPartList> { | ||||
|       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))); | ||||
|       }, | ||||
|   | ||||
| @@ -51,11 +51,7 @@ class _PartSupplierState extends RefreshableState<PartSupplierWidget> { | ||||
|     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 { | ||||
|   | ||||
							
								
								
									
										249
									
								
								lib/widget/po_line_detail.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										249
									
								
								lib/widget/po_line_detail.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -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<POLineDetailWidget> { | ||||
|  | ||||
|   _POLineDetailWidgetState(); | ||||
|  | ||||
|   @override | ||||
|   String getAppBarTitle() => L10().lineItem; | ||||
|  | ||||
|   @override | ||||
|   List<Widget> appBarActions(BuildContext context) { | ||||
|     List<Widget> actions = []; | ||||
|  | ||||
|     if (widget.item.canEdit) { | ||||
|       actions.add( | ||||
|         IconButton( | ||||
|           icon: Icon(Icons.edit_square), | ||||
|           onPressed: () { | ||||
|             _editLineItem(context); | ||||
|           }, | ||||
|         ) | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return actions; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   List<SpeedDialChild> actionButtons(BuildContext context) { | ||||
|     List<SpeedDialChild> 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<void> request(BuildContext context) async { | ||||
|     await widget.item.reload(); | ||||
|   } | ||||
|  | ||||
|   // Callback to edit this line item | ||||
|   Future<void> _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<void> receiveLineItem(BuildContext context) async { | ||||
|  | ||||
|     // Construct fields to receive | ||||
|     Map<String, dynamic> 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<Widget> getTiles(BuildContext context) { | ||||
|     List<Widget> 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; | ||||
|   } | ||||
|  | ||||
| } | ||||
							
								
								
									
										91
									
								
								lib/widget/po_line_list.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								lib/widget/po_line_list.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -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<String, String> filters, bool showSearch) : super(filters: filters, showSearch: showSearch); | ||||
|  | ||||
|   @override | ||||
|   _PaginatedPOLineListState createState() => _PaginatedPOLineListState(); | ||||
|  | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * State class for PaginatedPOLineList | ||||
| */ | ||||
| class _PaginatedPOLineListState extends PaginatedSearchState<PaginatedPOLineList> { | ||||
|  | ||||
|   _PaginatedPOLineListState() : super(); | ||||
|  | ||||
|   @override | ||||
|   String get prefix => "po_line_"; | ||||
|  | ||||
|   @override | ||||
|   Map<String, String> get orderingOptions => { | ||||
|     "part": L10().part, | ||||
|     "SKU": L10().sku, | ||||
|     "quantity": L10().quantity, | ||||
|   }; | ||||
|  | ||||
|   @override | ||||
|   Map<String, Map<String, dynamic>> get filterOptions => { | ||||
|     "pending": { | ||||
|       "label": L10().outstanding, | ||||
|       "help_text": L10().outstandingOrderDetail, | ||||
|       "tristate": true, | ||||
|     }, | ||||
|     "received": { | ||||
|       "label": L10().received, | ||||
|       "help_text": L10().receivedFilterDetail, | ||||
|       "tristate": true, | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   @override | ||||
|   Future<InvenTreePageResponse?> requestPage(int limit, int offset, Map<String, String> 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)), | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -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<PurchaseOrderDetailWidg | ||||
|         child: ListTile( | ||||
|           title: Text(order.reference), | ||||
|           subtitle: Text(order.description), | ||||
|           leading: supplier == null ? null : InvenTreeAPI().getImage(supplier.thumbnail, width: 40, height: 40), | ||||
|           leading: supplier == null ? null : InvenTreeAPI().getThumbnail(supplier.thumbnail), | ||||
|           trailing: Text( | ||||
|             api.PurchaseOrderStatus.label(order.status), | ||||
|             style: TextStyle( | ||||
| @@ -246,10 +245,12 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg | ||||
|       )); | ||||
|     } | ||||
|  | ||||
|     Color lineColor = completedLines < order.lineItemCount ? COLOR_WARNING : COLOR_SUCCESS; | ||||
|  | ||||
|     tiles.add(ListTile( | ||||
|       title: Text(L10().lineItems), | ||||
|       leading: FaIcon(FontAwesomeIcons.clipboardCheck), | ||||
|       trailing: Text("${completedLines} /  ${order.lineItemCount}"), | ||||
|       trailing: Text("${completedLines} /  ${order.lineItemCount}", style: TextStyle(color: lineColor)), | ||||
|     )); | ||||
|  | ||||
|     tiles.add(ListTile( | ||||
| @@ -317,160 +318,6 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg | ||||
|  | ||||
|   } | ||||
|  | ||||
|   /* | ||||
|    * Receive a specified PurchaseOrderLineItem into stock | ||||
|    */ | ||||
|   void receiveLine(BuildContext context, InvenTreePOLineItem lineItem) { | ||||
|  | ||||
|     Map<String, dynamic> 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<Widget> 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<Widget> lineTiles(BuildContext context) { | ||||
|  | ||||
|     List<Widget> 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<Widget> getTabIcons(BuildContext context) { | ||||
|     return [ | ||||
| @@ -484,7 +331,8 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg | ||||
|   List<Widget> 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), | ||||
|     ]; | ||||
|   } | ||||
|   | ||||
| @@ -154,11 +154,7 @@ class _PaginatedPurchaseOrderListState extends PaginatedSearchState<PaginatedPur | ||||
|     return ListTile( | ||||
|       title: Text(order.reference), | ||||
|       subtitle: Text(order.description), | ||||
|       leading: supplier == null ? null : InvenTreeAPI().getImage( | ||||
|         supplier.thumbnail, | ||||
|         width: 40, | ||||
|         height: 40, | ||||
|       ), | ||||
|       leading: supplier == null ? null : InvenTreeAPI().getThumbnail(supplier.thumbnail), | ||||
|       trailing: Text( | ||||
|         InvenTreeAPI().PurchaseOrderStatus.label(order.status), | ||||
|         style: TextStyle( | ||||
|   | ||||
| @@ -29,13 +29,19 @@ mixin BaseWidgetProperties { | ||||
|     return InvenTreeDrawer(context); | ||||
|   } | ||||
|  | ||||
|   // Function to construct a set of tabs for this widget (override if needed) | ||||
|   List<Widget> 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<Widget> 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<T extends StatefulWidget> extends State<T> 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), | ||||
|     ); | ||||
|   | ||||
| @@ -333,7 +333,8 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   List<Widget> _tiles(BuildContext context) { | ||||
|   @override | ||||
|   List<Widget> getTiles(BuildContext context) { | ||||
|  | ||||
|     List<Widget> tiles = []; | ||||
|  | ||||
| @@ -542,15 +543,4 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> { | ||||
|     return tiles; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget getBody(BuildContext context) { | ||||
|     return Center( | ||||
|       child: ListView( | ||||
|         children: ListTile.divideTiles( | ||||
|           context: context, | ||||
|           tiles: _tiles(context), | ||||
|         ).toList() | ||||
|       ) | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -567,7 +567,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> { | ||||
|       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<StockDetailWidget> { | ||||
|    * Construct a list of detail elements about this StockItem. | ||||
|    * The number of elements may vary depending on the StockItem details | ||||
|    */ | ||||
|   List<Widget> detailTiles() { | ||||
|   @override | ||||
|   List<Widget> getTiles(BuildContext context) { | ||||
|     List<Widget> tiles = []; | ||||
|  | ||||
|     // Image / name / description | ||||
| @@ -667,11 +668,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> { | ||||
|           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<StockDetailWidget> { | ||||
|     return tiles; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget getBody(BuildContext context) { | ||||
|     return ListView( | ||||
|       children: ListTile.divideTiles( | ||||
|           context: context, | ||||
|           tiles: detailTiles() | ||||
|       ).toList() | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -113,7 +113,8 @@ class _StockItemTestResultDisplayState extends RefreshableState<StockItemTestRes | ||||
|     return outputs; | ||||
|   } | ||||
|  | ||||
|   List<Widget> resultsList() { | ||||
|   @override | ||||
|   List<Widget> getTiles(BuildContext context) { | ||||
|     List<Widget> tiles = []; | ||||
|  | ||||
|     tiles.add( | ||||
| @@ -121,7 +122,7 @@ class _StockItemTestResultDisplayState extends RefreshableState<StockItemTestRes | ||||
|         child: ListTile( | ||||
|           title: Text(item.partName), | ||||
|           subtitle: Text(item.partDescription), | ||||
|           leading: InvenTreeAPI().getImage(item.partImage), | ||||
|           leading: InvenTreeAPI().getThumbnail(item.partImage), | ||||
|         ) | ||||
|       ) | ||||
|     ); | ||||
| @@ -213,15 +214,4 @@ class _StockItemTestResultDisplayState extends RefreshableState<StockItemTestRes | ||||
|  | ||||
|     return tiles; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget getBody(BuildContext context) { | ||||
|  | ||||
|     return ListView( | ||||
|       children: ListTile.divideTiles( | ||||
|         context: context, | ||||
|         tiles: resultsList() | ||||
|       ).toList() | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -115,11 +115,7 @@ class _PaginatedStockItemListState extends PaginatedSearchState<PaginatedStockIt | ||||
|     return ListTile( | ||||
|       title: Text("${item.partName}"), | ||||
|       subtitle: Text("${item.locationPathString}"), | ||||
|       leading: InvenTreeAPI().getImage( | ||||
|         item.partThumbnail, | ||||
|         width: 40, | ||||
|         height: 40, | ||||
|       ), | ||||
|       leading: InvenTreeAPI().getThumbnail(item.partThumbnail), | ||||
|       trailing: Text("${item.displayQuantity}", | ||||
|         style: TextStyle( | ||||
|           fontWeight: FontWeight.bold, | ||||
|   | ||||
| @@ -102,7 +102,8 @@ class _SupplierPartDisplayState extends RefreshableState<SupplierPartDetailWidge | ||||
|   /* | ||||
|    * Build a set of tiles to display for this SupplierPart | ||||
|    */ | ||||
|   List<Widget> detailTiles(BuildContext context) { | ||||
|   @override | ||||
|   List<Widget> getTiles(BuildContext context) { | ||||
|     List<Widget> tiles = []; | ||||
|  | ||||
|     if (loading) { | ||||
| @@ -116,11 +117,7 @@ class _SupplierPartDisplayState extends RefreshableState<SupplierPartDetailWidge | ||||
|           title: Text(L10().internalPart), | ||||
|           subtitle: Text(widget.supplierPart.partName), | ||||
|           leading: FaIcon(FontAwesomeIcons.shapes, color: COLOR_ACTION), | ||||
|           trailing: InvenTreeAPI().getImage( | ||||
|             widget.supplierPart.partImage, | ||||
|             width: 40, | ||||
|             height: 40, | ||||
|           ), | ||||
|           trailing: InvenTreeAPI().getThumbnail(widget.supplierPart.partImage), | ||||
|           onTap: () async { | ||||
|             showLoadingOverlay(context); | ||||
|             final part = await InvenTreePart().get(widget.supplierPart.partId); | ||||
| @@ -140,11 +137,7 @@ class _SupplierPartDisplayState extends RefreshableState<SupplierPartDetailWidge | ||||
|         title: Text(L10().supplier), | ||||
|         subtitle: Text(widget.supplierPart.supplierName), | ||||
|         leading: FaIcon(FontAwesomeIcons.building, color: COLOR_ACTION), | ||||
|         trailing: InvenTreeAPI().getImage( | ||||
|           widget.supplierPart.supplierImage, | ||||
|           width: 40, | ||||
|           height: 40, | ||||
|         ), | ||||
|         trailing: InvenTreeAPI().getThumbnail(widget.supplierPart.supplierImage), | ||||
|         onTap: () async { | ||||
|           showLoadingOverlay(context); | ||||
|           var supplier = await InvenTreeCompany().get(widget.supplierPart.supplierId); | ||||
| @@ -175,11 +168,7 @@ class _SupplierPartDisplayState extends RefreshableState<SupplierPartDetailWidge | ||||
|           title: Text(L10().manufacturer), | ||||
|           subtitle: Text(widget.supplierPart.manufacturerName), | ||||
|           leading: FaIcon(FontAwesomeIcons.industry, color: COLOR_ACTION), | ||||
|           trailing: InvenTreeAPI().getImage( | ||||
|             widget.supplierPart.manufacturerImage, | ||||
|             width: 40, | ||||
|             height: 40, | ||||
|           ), | ||||
|           trailing: InvenTreeAPI().getThumbnail(widget.supplierPart.manufacturerImage), | ||||
|           onTap: () async { | ||||
|             showLoadingOverlay(context); | ||||
|             var supplier = await InvenTreeCompany().get(widget.supplierPart.manufacturerId); | ||||
| @@ -230,14 +219,4 @@ class _SupplierPartDisplayState extends RefreshableState<SupplierPartDetailWidge | ||||
|     return tiles; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget getBody(BuildContext context) { | ||||
|     return ListView( | ||||
|         children: ListTile.divideTiles( | ||||
|           context: context, | ||||
|           tiles: detailTiles(context), | ||||
|         ).toList() | ||||
|     ); | ||||
|   } | ||||
|  | ||||
| } | ||||
| @@ -89,16 +89,8 @@ class _PaginatedSupplierPartListState extends PaginatedSearchState<PaginatedSupp | ||||
|     return ListTile( | ||||
|       title: Text(supplierPart.SKU), | ||||
|       subtitle: Text(supplierPart.partName), | ||||
|       leading: InvenTreeAPI().getImage( | ||||
|         supplierPart.supplierImage, | ||||
|         width: 40, | ||||
|         height: 40 | ||||
|       ), | ||||
|       trailing: InvenTreeAPI().getImage( | ||||
|         supplierPart.partImage, | ||||
|         width: 40, | ||||
|         height: 40, | ||||
|       ), | ||||
|       leading: InvenTreeAPI().getThumbnail(supplierPart.supplierImage), | ||||
|       trailing: InvenTreeAPI().getThumbnail(supplierPart.partImage), | ||||
|       onTap: () { | ||||
|         Navigator.push( | ||||
|           context, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user