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

Merge pull request 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

@ -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
---

@ -636,6 +636,9 @@ class InvenTreeAttachment extends InvenTreeModel {
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;
// 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(
URL,
attachment,
method: "POST",
name: "attachment",
fields: fields
fields: data
);
return response.successful();
}
/*
* Download this attachment file
*/
Future<void> downloadAttachment() async {
await InvenTreeAPI().downloadFile(attachment);

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

@ -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<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);
}
}

@ -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 {
InvenTreeStockLocation() : super();

@ -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<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
String getAppBarTitle(BuildContext context) => L10().attachments;
@ -37,20 +46,19 @@ class _PartAttachmentDisplayState extends RefreshableState<PartAttachmentsWidget
List<Widget> 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<PartAttachmentsWidget
}
Future<void> 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<PartAttachmentsWidget
@override
Future<void> 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<PartAttachmentsWidget
tiles.add(ListTile(
title: Text(L10().attachmentNone),
subtitle: Text(
L10().attachmentNonePartDetail,
style: TextStyle(fontStyle: FontStyle.italic),
L10().attachmentNonePartDetail,
style: TextStyle(fontStyle: FontStyle.italic),
),
));
}
return tiles;
}
}
}

@ -6,7 +6,7 @@ import "package:inventree/app_colors.dart";
import "package:inventree/inventree/stock.dart";
import "package:inventree/l10.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/progress.dart";
import "package:inventree/inventree/part.dart";
@ -39,6 +39,8 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
InvenTreePart? parentPart;
int attachmentCount = 0;
@override
String getAppBarTitle(BuildContext context) => L10().partDetails;
@ -110,6 +112,12 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
}
await part.getTestTemplates();
attachmentCount = await InvenTreePartAttachment().count(
filters: {
"part": part.pk.toString()
}
);
}
Future <void> _toggleStar() async {
@ -405,12 +413,15 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
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,
MaterialPageRoute(
builder: (context) => PartAttachmentsWidget(part)
builder: (context) => AttachmentWidget(
InvenTreePartAttachment(),
part.pk,
InvenTreeAPI().checkPermission("part", "change"))
)
);
},

@ -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<PurchaseOrderDetailWidg
int completedLines = 0;
int attachmentCount = 0;
String _poPrefix = "";
@override
@ -77,6 +80,12 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
completedLines += 1;
}
}
attachmentCount = await InvenTreePurchaseOrderAttachment().count(
filters: {
"order": order.pk.toString()
}
);
}
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;
}

@ -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<StockDetailWidget> {
// Part object
InvenTreePart? part;
int attachmentCount = 0;
@override
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
if (InvenTreeAPI().pluginsEnabled()) {
_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;
}