From 10783cd1c44a9ae777fe656827564b2351143945 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sat, 4 Jun 2022 00:12:44 +1000 Subject: [PATCH 1/5] Refactor PartAttachmentWidget - Now displays generic attachments - Augment the InvenTreeAttachment class to "know" about its model reference --- lib/inventree/model.dart | 15 ++- lib/inventree/part.dart | 3 + ...nts_widget.dart => attachment_widget.dart} | 91 ++++++++++--------- lib/widget/part_detail.dart | 7 +- 4 files changed, 67 insertions(+), 49 deletions(-) rename lib/widget/{part_attachments_widget.dart => attachment_widget.dart} (50%) diff --git a/lib/inventree/model.dart b/lib/inventree/model.dart index 5d55e9f1..75d31a4d 100644 --- a/lib/inventree/model.dart +++ b/lib/inventree/model.dart @@ -636,6 +636,9 @@ class InvenTreeAttachment extends InvenTreeModel { InvenTreeAttachment.fromJson(Map json) : super.fromJson(json); + // Override this reference field for any subclasses + String get REFERENCE_FIELD => ""; + String get attachment => (jsondata["attachment"] ?? "") as String; // Return the filename of the attachment @@ -684,19 +687,27 @@ class InvenTreeAttachment extends InvenTreeModel { } } - Future uploadAttachment(File attachment, {String comment = "", Map fields = const {}}) async { + Future uploadAttachment(File attachment, int parentId, {String comment = "", Map fields = const {}}) async { + + // Ensure that the correct reference field is set + Map data = Map.from(fields); + + data[REFERENCE_FIELD] = parentId.toString(); final APIResponse response = await InvenTreeAPI().uploadFile( URL, attachment, method: "POST", name: "attachment", - fields: fields + fields: data ); return response.successful(); } + /* + * Download this attachment file + */ Future downloadAttachment() async { await InvenTreeAPI().downloadFile(attachment); diff --git a/lib/inventree/part.dart b/lib/inventree/part.dart index 74670120..c528a583 100644 --- a/lib/inventree/part.dart +++ b/lib/inventree/part.dart @@ -422,6 +422,9 @@ class InvenTreePartAttachment extends InvenTreeAttachment { InvenTreePartAttachment.fromJson(Map json) : super.fromJson(json); + @override + String get REFERENCE_FIELD => "part"; + @override String get URL => "part/attachment/"; diff --git a/lib/widget/part_attachments_widget.dart b/lib/widget/attachment_widget.dart similarity index 50% rename from lib/widget/part_attachments_widget.dart rename to lib/widget/attachment_widget.dart index 5fada013..e5903258 100644 --- a/lib/widget/part_attachments_widget.dart +++ b/lib/widget/attachment_widget.dart @@ -1,33 +1,42 @@ +/* + * A generic widget for displaying a list of attachments. + * + * To allow use with different "types" of attachments, + * we pass a subclassed instance of the InvenTreeAttachment model. + */ + import "dart:io"; import "package:flutter/material.dart"; import "package:font_awesome_flutter/font_awesome_flutter.dart"; -import "package:inventree/inventree/part.dart"; +import "package:inventree/inventree/model.dart"; import "package:inventree/widget/fields.dart"; -import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/widget/snacks.dart"; - -import "package:inventree/api.dart"; +import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/l10.dart"; -class PartAttachmentsWidget extends StatefulWidget { +class AttachmentWidget extends StatefulWidget { - const PartAttachmentsWidget(this.part, {Key? key}) : super(key: key); + const AttachmentWidget(this.attachment, this.referenceId, this.hasUploadPermission) : super(); - final InvenTreePart part; + final InvenTreeAttachment attachment; + final int referenceId; + final bool hasUploadPermission; @override - _PartAttachmentDisplayState createState() => _PartAttachmentDisplayState(part); + _AttachmentWidgetState createState() => _AttachmentWidgetState(attachment, referenceId, hasUploadPermission); } -class _PartAttachmentDisplayState extends RefreshableState { +class _AttachmentWidgetState extends RefreshableState { - _PartAttachmentDisplayState(this.part); + _AttachmentWidgetState(this.attachment, this.referenceId, this.hasUploadPermission); - final InvenTreePart part; + final InvenTreeAttachment attachment; + final int referenceId; + final bool hasUploadPermission; - List attachments = []; + List attachments = []; @override String getAppBarTitle(BuildContext context) => L10().attachments; @@ -37,20 +46,19 @@ class _PartAttachmentDisplayState extends RefreshableState actions = []; - if (InvenTreeAPI().checkPermission("part", "change")) { - + if (hasUploadPermission) { // File upload actions.add( - IconButton( - icon: FaIcon(FontAwesomeIcons.plusCircle), - onPressed: () async { - FilePickerDialog.pickFile( - onPicked: (File file) { - upload(file); - } - ); - }, - ) + IconButton( + icon: FaIcon(FontAwesomeIcons.plusCircle), + onPressed: () async { + FilePickerDialog.pickFile( + onPicked: (File file) { + upload(file); + } + ); + }, + ) ); } @@ -58,12 +66,8 @@ class _PartAttachmentDisplayState extends RefreshableState upload(File file) async { - final bool result = await InvenTreePartAttachment().uploadAttachment( - file, - fields: { - "part": "${part.pk}" - } - ); + + final bool result = await attachment.uploadAttachment(file, referenceId); if (result) { showSnackIcon(L10().uploadSuccess, success: true); @@ -77,32 +81,31 @@ class _PartAttachmentDisplayState extends RefreshableState request(BuildContext context) async { - await InvenTreePartAttachment().list( + await attachment.list( filters: { - "part": "${part.pk}" + attachment.REFERENCE_FIELD: referenceId.toString() } ).then((var results) { attachments.clear(); for (var result in results) { - if (result is InvenTreePartAttachment) { + if (result is InvenTreeAttachment) { attachments.add(result); } } }); - } @override Widget getBody(BuildContext context) { return Center( - child: ListView( - children: ListTile.divideTiles( - context: context, - tiles: attachmentTiles(context) - ).toList(), - ) + child: ListView( + children: ListTile.divideTiles( + context: context, + tiles: attachmentTiles(context) + ).toList(), + ) ); } @@ -126,14 +129,12 @@ class _PartAttachmentDisplayState extends RefreshableState { Navigator.push( context, MaterialPageRoute( - builder: (context) => PartAttachmentsWidget(part) + builder: (context) => AttachmentWidget( + InvenTreePartAttachment(), + part.pk, + InvenTreeAPI().checkPermission("part", "change")) ) ); }, From fcfda4ebff784464fdbf0c6b1c4552c2fe0e1c4c Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sat, 4 Jun 2022 00:15:34 +1000 Subject: [PATCH 2/5] Display number of attachments on part view --- lib/widget/part_detail.dart | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/widget/part_detail.dart b/lib/widget/part_detail.dart index b260739e..376d5480 100644 --- a/lib/widget/part_detail.dart +++ b/lib/widget/part_detail.dart @@ -39,6 +39,8 @@ class _PartDisplayState extends RefreshableState { InvenTreePart? parentPart; + int attachmentCount = 0; + @override String getAppBarTitle(BuildContext context) => L10().partDetails; @@ -110,6 +112,12 @@ class _PartDisplayState extends RefreshableState { } await part.getTestTemplates(); + + attachmentCount = await InvenTreePartAttachment().count( + filters: { + "part": part.pk.toString() + } + ); } Future _toggleStar() async { @@ -405,7 +413,7 @@ class _PartDisplayState extends RefreshableState { ListTile( title: Text(L10().attachments), leading: FaIcon(FontAwesomeIcons.fileAlt, color: COLOR_CLICK), - trailing: Text(""), + trailing: attachmentCount > 0 ? Text(attachmentCount.toString()) : null, onTap: () { Navigator.push( context, From ada64f397143c65700650ebd426077343046ed4c Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sat, 4 Jun 2022 00:23:10 +1000 Subject: [PATCH 3/5] Adds attachment support for the StockItem model --- lib/inventree/part.dart | 4 +++- lib/inventree/stock.dart | 23 +++++++++++++++++++++++ lib/widget/stock_detail.dart | 28 ++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/lib/inventree/part.dart b/lib/inventree/part.dart index c528a583..85ee6d86 100644 --- a/lib/inventree/part.dart +++ b/lib/inventree/part.dart @@ -415,7 +415,9 @@ class InvenTreePart extends InvenTreeModel { } } - +/* + * Class representing an attachment file against a Part object + */ class InvenTreePartAttachment extends InvenTreeAttachment { InvenTreePartAttachment() : super(); diff --git a/lib/inventree/stock.dart b/lib/inventree/stock.dart index b60eb957..46b0c75a 100644 --- a/lib/inventree/stock.dart +++ b/lib/inventree/stock.dart @@ -601,6 +601,29 @@ class InvenTreeStockItem extends InvenTreeModel { } +/* + * Class representing an attachment file against a StockItem object + */ +class InvenTreeStockItemAttachment extends InvenTreeAttachment { + + InvenTreeStockItemAttachment() : super(); + + InvenTreeStockItemAttachment.fromJson(Map json) : super.fromJson(json); + + @override + String get REFERENCE_FIELD => "stock_item"; + + @override + String get URL => "stock/attachment/"; + + @override + InvenTreeModel createFromJson(Map json) { + return InvenTreeStockItemAttachment.fromJson(json); + } + +} + + class InvenTreeStockLocation extends InvenTreeModel { InvenTreeStockLocation() : super(); diff --git a/lib/widget/stock_detail.dart b/lib/widget/stock_detail.dart index 8c1a376d..d8c43adb 100644 --- a/lib/widget/stock_detail.dart +++ b/lib/widget/stock_detail.dart @@ -7,6 +7,7 @@ import "package:inventree/barcode.dart"; import "package:inventree/inventree/stock.dart"; import "package:inventree/inventree/part.dart"; import "package:inventree/widget/dialogs.dart"; +import "package:inventree/widget/attachment_widget.dart"; import "package:inventree/widget/location_display.dart"; import "package:inventree/widget/part_detail.dart"; import "package:inventree/widget/progress.dart"; @@ -94,6 +95,8 @@ class _StockItemDisplayState extends RefreshableState { // Part object InvenTreePart? part; + int attachmentCount = 0; + @override Future onBuild(BuildContext context) async { @@ -126,6 +129,12 @@ class _StockItemDisplayState extends RefreshableState { }); }); + attachmentCount = await InvenTreeStockItemAttachment().count( + filters: { + "stock_item": item.pk.toString() + } + ); + // Request information on labels available for this stock item if (InvenTreeAPI().pluginsEnabled()) { _getLabels(); @@ -694,6 +703,25 @@ class _StockItemDisplayState extends RefreshableState { ) ); + tiles.add( + ListTile( + title: Text(L10().attachments), + leading: FaIcon(FontAwesomeIcons.fileAlt, color: COLOR_CLICK), + trailing: attachmentCount > 0 ? Text(attachmentCount.toString()) : null, + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AttachmentWidget( + InvenTreeStockItemAttachment(), + item.pk, + InvenTreeAPI().checkPermission("stock", "change")) + ) + ); + }, + ) + ); + return tiles; } From 0237e9c819d42973d478ce15948df8c766defed5 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sat, 4 Jun 2022 00:28:39 +1000 Subject: [PATCH 4/5] Adds attachment support for purchase orders --- lib/inventree/purchase_order.dart | 21 +++++++++++++++++++ lib/widget/purchase_order_detail.dart | 29 +++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/lib/inventree/purchase_order.dart b/lib/inventree/purchase_order.dart index 6b8595cf..59dff7e4 100644 --- a/lib/inventree/purchase_order.dart +++ b/lib/inventree/purchase_order.dart @@ -203,3 +203,24 @@ class InvenTreePOLineItem extends InvenTreeModel { return InvenTreePOLineItem.fromJson(json); } } + +/* + * Class representing an attachment file against a StockItem object + */ +class InvenTreePurchaseOrderAttachment extends InvenTreeAttachment { + + InvenTreePurchaseOrderAttachment() : super(); + + InvenTreePurchaseOrderAttachment.fromJson(Map json) : super.fromJson(json); + + @override + String get REFERENCE_FIELD => "order"; + + @override + String get URL => "order/po/attachment/"; + + @override + InvenTreeModel createFromJson(Map json) { + return InvenTreePurchaseOrderAttachment.fromJson(json); + } +} diff --git a/lib/widget/purchase_order_detail.dart b/lib/widget/purchase_order_detail.dart index 2abe56c6..2b511446 100644 --- a/lib/widget/purchase_order_detail.dart +++ b/lib/widget/purchase_order_detail.dart @@ -9,6 +9,7 @@ import "package:inventree/app_colors.dart"; import "package:inventree/helpers.dart"; import "package:inventree/inventree/company.dart"; import "package:inventree/inventree/purchase_order.dart"; +import "package:inventree/widget/attachment_widget.dart"; import "package:inventree/widget/company_detail.dart"; import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/l10.dart"; @@ -37,6 +38,8 @@ class _PurchaseOrderDetailState extends RefreshableState editOrder(BuildContext context) async { @@ -177,6 +186,26 @@ class _PurchaseOrderDetailState extends RefreshableState 0 ? Text(attachmentCount.toString()) : null, + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AttachmentWidget( + InvenTreePurchaseOrderAttachment(), + order.pk, + InvenTreeAPI().checkPermission("purchase_order", "change")) + ) + ); + }, + ) + ); + return tiles; } From 302532f5a4bc6094b776cbabcb74c9d54e381dd3 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sat, 4 Jun 2022 00:34:50 +1000 Subject: [PATCH 5/5] Update release notes --- assets/release_notes.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/assets/release_notes.md b/assets/release_notes.md index a330e6f0..1754b7cc 100644 --- a/assets/release_notes.md +++ b/assets/release_notes.md @@ -5,6 +5,8 @@ --- - Add "quarantined" status flag for stock items +- Extends attachment support to stock items +- Extends attachment support to purchase orders ### 0.7.1 - May 2022 ---