2
0
mirror of https://github.com/inventree/inventree-app.git synced 2025-05-16 05:53:10 +00:00

Merge pull request #152 from inventree/more-attachments

More attachments
This commit is contained in:
Oliver 2022-06-04 00:40:58 +10:00 committed by GitHub
commit 62e6009aeb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 182 additions and 51 deletions

View File

@ -5,6 +5,8 @@
--- ---
- Add "quarantined" status flag for stock items - Add "quarantined" status flag for stock items
- Extends attachment support to stock items
- Extends attachment support to purchase orders
### 0.7.1 - May 2022 ### 0.7.1 - May 2022
--- ---

View File

@ -636,6 +636,9 @@ class InvenTreeAttachment extends InvenTreeModel {
InvenTreeAttachment.fromJson(Map<String, dynamic> json) : super.fromJson(json); InvenTreeAttachment.fromJson(Map<String, dynamic> json) : super.fromJson(json);
// Override this reference field for any subclasses
String get REFERENCE_FIELD => "";
String get attachment => (jsondata["attachment"] ?? "") as String; String get attachment => (jsondata["attachment"] ?? "") as String;
// Return the filename of the attachment // Return the filename of the attachment
@ -684,19 +687,27 @@ class InvenTreeAttachment extends InvenTreeModel {
} }
} }
Future<bool> uploadAttachment(File attachment, {String comment = "", Map<String, String> fields = const {}}) async { Future<bool> uploadAttachment(File attachment, int parentId, {String comment = "", Map<String, String> fields = const {}}) async {
// Ensure that the correct reference field is set
Map<String, String> data = Map<String, String>.from(fields);
data[REFERENCE_FIELD] = parentId.toString();
final APIResponse response = await InvenTreeAPI().uploadFile( final APIResponse response = await InvenTreeAPI().uploadFile(
URL, URL,
attachment, attachment,
method: "POST", method: "POST",
name: "attachment", name: "attachment",
fields: fields fields: data
); );
return response.successful(); return response.successful();
} }
/*
* Download this attachment file
*/
Future<void> downloadAttachment() async { Future<void> downloadAttachment() async {
await InvenTreeAPI().downloadFile(attachment); await InvenTreeAPI().downloadFile(attachment);

View File

@ -415,13 +415,18 @@ class InvenTreePart extends InvenTreeModel {
} }
} }
/*
* Class representing an attachment file against a Part object
*/
class InvenTreePartAttachment extends InvenTreeAttachment { class InvenTreePartAttachment extends InvenTreeAttachment {
InvenTreePartAttachment() : super(); InvenTreePartAttachment() : super();
InvenTreePartAttachment.fromJson(Map<String, dynamic> json) : super.fromJson(json); InvenTreePartAttachment.fromJson(Map<String, dynamic> json) : super.fromJson(json);
@override
String get REFERENCE_FIELD => "part";
@override @override
String get URL => "part/attachment/"; String get URL => "part/attachment/";

View File

@ -203,3 +203,24 @@ class InvenTreePOLineItem extends InvenTreeModel {
return InvenTreePOLineItem.fromJson(json); return InvenTreePOLineItem.fromJson(json);
} }
} }
/*
* Class representing an attachment file against a StockItem object
*/
class InvenTreePurchaseOrderAttachment extends InvenTreeAttachment {
InvenTreePurchaseOrderAttachment() : super();
InvenTreePurchaseOrderAttachment.fromJson(Map<String, dynamic> json) : super.fromJson(json);
@override
String get REFERENCE_FIELD => "order";
@override
String get URL => "order/po/attachment/";
@override
InvenTreeModel createFromJson(Map<String, dynamic> json) {
return InvenTreePurchaseOrderAttachment.fromJson(json);
}
}

View File

@ -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<String, dynamic> json) : super.fromJson(json);
@override
String get REFERENCE_FIELD => "stock_item";
@override
String get URL => "stock/attachment/";
@override
InvenTreeModel createFromJson(Map<String, dynamic> json) {
return InvenTreeStockItemAttachment.fromJson(json);
}
}
class InvenTreeStockLocation extends InvenTreeModel { class InvenTreeStockLocation extends InvenTreeModel {
InvenTreeStockLocation() : super(); InvenTreeStockLocation() : super();

View File

@ -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 "dart:io";
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:font_awesome_flutter/font_awesome_flutter.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/fields.dart";
import "package:inventree/widget/refreshable_state.dart";
import "package:inventree/widget/snacks.dart"; import "package:inventree/widget/snacks.dart";
import "package:inventree/widget/refreshable_state.dart";
import "package:inventree/api.dart";
import "package:inventree/l10.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 @override
_PartAttachmentDisplayState createState() => _PartAttachmentDisplayState(part); _AttachmentWidgetState createState() => _AttachmentWidgetState(attachment, referenceId, hasUploadPermission);
} }
class _PartAttachmentDisplayState extends RefreshableState<PartAttachmentsWidget> { class _AttachmentWidgetState extends RefreshableState<AttachmentWidget> {
_PartAttachmentDisplayState(this.part); _AttachmentWidgetState(this.attachment, this.referenceId, this.hasUploadPermission);
final InvenTreePart part; final InvenTreeAttachment attachment;
final int referenceId;
final bool hasUploadPermission;
List<InvenTreePartAttachment> attachments = []; List<InvenTreeAttachment> attachments = [];
@override @override
String getAppBarTitle(BuildContext context) => L10().attachments; String getAppBarTitle(BuildContext context) => L10().attachments;
@ -37,20 +46,19 @@ class _PartAttachmentDisplayState extends RefreshableState<PartAttachmentsWidget
List<Widget> actions = []; List<Widget> actions = [];
if (InvenTreeAPI().checkPermission("part", "change")) { if (hasUploadPermission) {
// File upload // File upload
actions.add( actions.add(
IconButton( IconButton(
icon: FaIcon(FontAwesomeIcons.plusCircle), icon: FaIcon(FontAwesomeIcons.plusCircle),
onPressed: () async { onPressed: () async {
FilePickerDialog.pickFile( FilePickerDialog.pickFile(
onPicked: (File file) { onPicked: (File file) {
upload(file); upload(file);
} }
); );
}, },
) )
); );
} }
@ -58,12 +66,8 @@ class _PartAttachmentDisplayState extends RefreshableState<PartAttachmentsWidget
} }
Future<void> upload(File file) async { Future<void> upload(File file) async {
final bool result = await InvenTreePartAttachment().uploadAttachment(
file, final bool result = await attachment.uploadAttachment(file, referenceId);
fields: {
"part": "${part.pk}"
}
);
if (result) { if (result) {
showSnackIcon(L10().uploadSuccess, success: true); showSnackIcon(L10().uploadSuccess, success: true);
@ -77,32 +81,31 @@ class _PartAttachmentDisplayState extends RefreshableState<PartAttachmentsWidget
@override @override
Future<void> request(BuildContext context) async { Future<void> request(BuildContext context) async {
await InvenTreePartAttachment().list( await attachment.list(
filters: { filters: {
"part": "${part.pk}" attachment.REFERENCE_FIELD: referenceId.toString()
} }
).then((var results) { ).then((var results) {
attachments.clear(); attachments.clear();
for (var result in results) { for (var result in results) {
if (result is InvenTreePartAttachment) { if (result is InvenTreeAttachment) {
attachments.add(result); attachments.add(result);
} }
} }
}); });
} }
@override @override
Widget getBody(BuildContext context) { Widget getBody(BuildContext context) {
return Center( return Center(
child: ListView( child: ListView(
children: ListTile.divideTiles( children: ListTile.divideTiles(
context: context, context: context,
tiles: attachmentTiles(context) tiles: attachmentTiles(context)
).toList(), ).toList(),
) )
); );
} }
@ -126,14 +129,12 @@ class _PartAttachmentDisplayState extends RefreshableState<PartAttachmentsWidget
tiles.add(ListTile( tiles.add(ListTile(
title: Text(L10().attachmentNone), title: Text(L10().attachmentNone),
subtitle: Text( subtitle: Text(
L10().attachmentNonePartDetail, L10().attachmentNonePartDetail,
style: TextStyle(fontStyle: FontStyle.italic), style: TextStyle(fontStyle: FontStyle.italic),
), ),
)); ));
} }
return tiles; return tiles;
} }
} }

View File

@ -6,7 +6,7 @@ import "package:inventree/app_colors.dart";
import "package:inventree/inventree/stock.dart"; import "package:inventree/inventree/stock.dart";
import "package:inventree/l10.dart"; import "package:inventree/l10.dart";
import "package:inventree/helpers.dart"; import "package:inventree/helpers.dart";
import "package:inventree/widget/part_attachments_widget.dart"; import "package:inventree/widget/attachment_widget.dart";
import "package:inventree/widget/part_notes.dart"; import "package:inventree/widget/part_notes.dart";
import "package:inventree/widget/progress.dart"; import "package:inventree/widget/progress.dart";
import "package:inventree/inventree/part.dart"; import "package:inventree/inventree/part.dart";
@ -39,6 +39,8 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
InvenTreePart? parentPart; InvenTreePart? parentPart;
int attachmentCount = 0;
@override @override
String getAppBarTitle(BuildContext context) => L10().partDetails; String getAppBarTitle(BuildContext context) => L10().partDetails;
@ -110,6 +112,12 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
} }
await part.getTestTemplates(); await part.getTestTemplates();
attachmentCount = await InvenTreePartAttachment().count(
filters: {
"part": part.pk.toString()
}
);
} }
Future <void> _toggleStar() async { Future <void> _toggleStar() async {
@ -405,12 +413,15 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
ListTile( ListTile(
title: Text(L10().attachments), title: Text(L10().attachments),
leading: FaIcon(FontAwesomeIcons.fileAlt, color: COLOR_CLICK), leading: FaIcon(FontAwesomeIcons.fileAlt, color: COLOR_CLICK),
trailing: Text(""), trailing: attachmentCount > 0 ? Text(attachmentCount.toString()) : null,
onTap: () { onTap: () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => PartAttachmentsWidget(part) builder: (context) => AttachmentWidget(
InvenTreePartAttachment(),
part.pk,
InvenTreeAPI().checkPermission("part", "change"))
) )
); );
}, },

View File

@ -9,6 +9,7 @@ import "package:inventree/app_colors.dart";
import "package:inventree/helpers.dart"; import "package:inventree/helpers.dart";
import "package:inventree/inventree/company.dart"; import "package:inventree/inventree/company.dart";
import "package:inventree/inventree/purchase_order.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/company_detail.dart";
import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/widget/refreshable_state.dart";
import "package:inventree/l10.dart"; import "package:inventree/l10.dart";
@ -37,6 +38,8 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
int completedLines = 0; int completedLines = 0;
int attachmentCount = 0;
String _poPrefix = ""; String _poPrefix = "";
@override @override
@ -77,6 +80,12 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
completedLines += 1; completedLines += 1;
} }
} }
attachmentCount = await InvenTreePurchaseOrderAttachment().count(
filters: {
"order": order.pk.toString()
}
);
} }
Future <void> editOrder(BuildContext context) async { Future <void> editOrder(BuildContext context) async {
@ -177,6 +186,26 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
)); ));
} }
// Attachments
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(
InvenTreePurchaseOrderAttachment(),
order.pk,
InvenTreeAPI().checkPermission("purchase_order", "change"))
)
);
},
)
);
return tiles; return tiles;
} }

View File

@ -7,6 +7,7 @@ import "package:inventree/barcode.dart";
import "package:inventree/inventree/stock.dart"; import "package:inventree/inventree/stock.dart";
import "package:inventree/inventree/part.dart"; import "package:inventree/inventree/part.dart";
import "package:inventree/widget/dialogs.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/location_display.dart";
import "package:inventree/widget/part_detail.dart"; import "package:inventree/widget/part_detail.dart";
import "package:inventree/widget/progress.dart"; import "package:inventree/widget/progress.dart";
@ -94,6 +95,8 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
// Part object // Part object
InvenTreePart? part; InvenTreePart? part;
int attachmentCount = 0;
@override @override
Future<void> onBuild(BuildContext context) async { Future<void> onBuild(BuildContext context) async {
@ -126,6 +129,12 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
}); });
}); });
attachmentCount = await InvenTreeStockItemAttachment().count(
filters: {
"stock_item": item.pk.toString()
}
);
// Request information on labels available for this stock item // Request information on labels available for this stock item
if (InvenTreeAPI().pluginsEnabled()) { if (InvenTreeAPI().pluginsEnabled()) {
_getLabels(); _getLabels();
@ -694,6 +703,25 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
) )
); );
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; return tiles;
} }