mirror of
https://github.com/inventree/inventree-app.git
synced 2025-12-03 18:59:50 +00:00
Attachments refactor (#737)
* refactor attachment code into its own file * Add getters * Remove custom models for each type of attachment * Refactor existing widgets * Fix double camera open bug * Remove dead code * Remove unused imports * Refactor common code * format * Update release notes
This commit is contained in:
@@ -1,3 +1,8 @@
|
||||
### x.xx.x - Month Year
|
||||
---
|
||||
|
||||
- Fixes bug which launched camera twice when uploading an attachment
|
||||
|
||||
### 0.21.1 - November 2025
|
||||
---
|
||||
|
||||
|
||||
176
lib/inventree/attachment.dart
Normal file
176
lib/inventree/attachment.dart
Normal file
@@ -0,0 +1,176 @@
|
||||
import "dart:io";
|
||||
|
||||
import "package:flutter/cupertino.dart";
|
||||
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
|
||||
import "package:inventree/api.dart";
|
||||
import "package:inventree/inventree/model.dart";
|
||||
import "package:inventree/inventree/sentry.dart";
|
||||
import "package:inventree/l10.dart";
|
||||
import "package:inventree/widget/fields.dart";
|
||||
import "package:inventree/widget/snacks.dart";
|
||||
import "package:path/path.dart" as path;
|
||||
|
||||
class InvenTreeAttachment extends InvenTreeModel {
|
||||
// Class representing an "attachment" file
|
||||
InvenTreeAttachment() : super();
|
||||
|
||||
InvenTreeAttachment.fromJson(Map<String, dynamic> json)
|
||||
: super.fromJson(json);
|
||||
|
||||
@override
|
||||
InvenTreeAttachment createFromJson(Map<String, dynamic> json) =>
|
||||
InvenTreeAttachment.fromJson(json);
|
||||
|
||||
@override
|
||||
String get URL => "attachment/";
|
||||
|
||||
@override
|
||||
Map<String, Map<String, dynamic>> formFields() {
|
||||
Map<String, Map<String, dynamic>> fields = {"link": {}, "comment": {}};
|
||||
|
||||
if (!hasLink) {
|
||||
fields.remove("link");
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
// The model type of the instance this attachment is associated with
|
||||
String get modelType => getString("model_type");
|
||||
|
||||
// The ID of the instance this attachment is associated with
|
||||
int get modelId => getInt("model_id");
|
||||
|
||||
String get attachment => getString("attachment");
|
||||
|
||||
bool get hasAttachment => attachment.isNotEmpty;
|
||||
|
||||
// Return the filename of the attachment
|
||||
String get filename {
|
||||
return attachment.split("/").last;
|
||||
}
|
||||
|
||||
IconData get icon {
|
||||
String fn = filename.toLowerCase();
|
||||
|
||||
if (fn.endsWith(".pdf")) {
|
||||
return TablerIcons.file_type_pdf;
|
||||
} else if (fn.endsWith(".csv")) {
|
||||
return TablerIcons.file_type_csv;
|
||||
} else if (fn.endsWith(".doc") || fn.endsWith(".docx")) {
|
||||
return TablerIcons.file_type_doc;
|
||||
} else if (fn.endsWith(".xls") || fn.endsWith(".xlsx")) {
|
||||
return TablerIcons.file_type_xls;
|
||||
}
|
||||
|
||||
// Image formats
|
||||
final List<String> img_formats = [".png", ".jpg", ".gif", ".bmp", ".svg"];
|
||||
|
||||
for (String fmt in img_formats) {
|
||||
if (fn.endsWith(fmt)) {
|
||||
return TablerIcons.file_type_jpg;
|
||||
}
|
||||
}
|
||||
|
||||
return TablerIcons.file;
|
||||
}
|
||||
|
||||
String get comment => getString("comment");
|
||||
|
||||
DateTime? get uploadDate {
|
||||
if (jsondata.containsKey("upload_date")) {
|
||||
return DateTime.tryParse((jsondata["upload_date"] ?? "") as String);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Return a count of how many attachments exist against the specified model ID
|
||||
Future<int> countAttachments(String modelType, int modelId) async {
|
||||
Map<String, String> filters = {};
|
||||
|
||||
if (!api.supportsModernAttachments) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
filters["model_type"] = modelType;
|
||||
filters["model_id"] = modelId.toString();
|
||||
|
||||
return count(filters: filters);
|
||||
}
|
||||
|
||||
Future<bool> uploadAttachment(
|
||||
File attachment,
|
||||
String modelType,
|
||||
int modelId, {
|
||||
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);
|
||||
|
||||
String url = URL;
|
||||
|
||||
if (comment.isNotEmpty) {
|
||||
data["comment"] = comment;
|
||||
}
|
||||
|
||||
data["model_type"] = modelType;
|
||||
data["model_id"] = modelId.toString();
|
||||
|
||||
final APIResponse response = await InvenTreeAPI().uploadFile(
|
||||
url,
|
||||
attachment,
|
||||
method: "POST",
|
||||
name: "attachment",
|
||||
fields: data,
|
||||
);
|
||||
|
||||
return response.successful();
|
||||
}
|
||||
|
||||
Future<bool> uploadImage(
|
||||
String modelType,
|
||||
int modelId, {
|
||||
String prefix = "InvenTree",
|
||||
}) async {
|
||||
bool result = false;
|
||||
|
||||
await FilePickerDialog.pickImageFromCamera().then((File? file) {
|
||||
if (file != null) {
|
||||
String dir = path.dirname(file.path);
|
||||
String ext = path.extension(file.path);
|
||||
String now = DateTime.now().toIso8601String().replaceAll(":", "-");
|
||||
|
||||
// Rename the file with a unique name
|
||||
String filename = "${dir}/${prefix}_image_${now}${ext}";
|
||||
|
||||
try {
|
||||
return file.rename(filename).then((File renamed) {
|
||||
return uploadAttachment(renamed, modelType, modelId).then((
|
||||
success,
|
||||
) {
|
||||
result = success;
|
||||
showSnackIcon(
|
||||
result ? L10().imageUploadSuccess : L10().imageUploadFailure,
|
||||
success: result,
|
||||
);
|
||||
});
|
||||
});
|
||||
} catch (error, stackTrace) {
|
||||
sentryReportError("uploadImage", error, stackTrace);
|
||||
showSnackIcon(L10().imageUploadFailure, success: false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Download this attachment file
|
||||
*/
|
||||
Future<void> downloadAttachment() async {
|
||||
await InvenTreeAPI().downloadFile(attachment);
|
||||
}
|
||||
}
|
||||
@@ -111,31 +111,6 @@ class InvenTreeCompany extends InvenTreeModel {
|
||||
InvenTreeCompany.fromJson(json);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class representing an attachment file against a Company object
|
||||
*/
|
||||
class InvenTreeCompanyAttachment extends InvenTreeAttachment {
|
||||
InvenTreeCompanyAttachment() : super();
|
||||
|
||||
InvenTreeCompanyAttachment.fromJson(Map<String, dynamic> json)
|
||||
: super.fromJson(json);
|
||||
|
||||
@override
|
||||
String get REFERENCE_FIELD => "company";
|
||||
|
||||
@override
|
||||
String get REF_MODEL_TYPE => "company";
|
||||
|
||||
@override
|
||||
String get URL => InvenTreeAPI().supportsModernAttachments
|
||||
? "attachment/"
|
||||
: "company/attachment/";
|
||||
|
||||
@override
|
||||
InvenTreeModel createFromJson(Map<String, dynamic> json) =>
|
||||
InvenTreeCompanyAttachment.fromJson(json);
|
||||
}
|
||||
|
||||
/*
|
||||
* The InvenTreeSupplierPart class represents the SupplierPart model in the InvenTree database
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import "dart:async";
|
||||
import "dart:io";
|
||||
|
||||
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
|
||||
import "package:flutter/material.dart";
|
||||
@@ -15,7 +14,6 @@ import "package:inventree/helpers.dart";
|
||||
import "package:inventree/inventree/sentry.dart";
|
||||
|
||||
import "package:inventree/widget/dialogs.dart";
|
||||
import "package:inventree/widget/fields.dart";
|
||||
|
||||
// Paginated response object
|
||||
class InvenTreePageResponse {
|
||||
@@ -934,171 +932,3 @@ class InvenTreeUserSetting extends InvenTreeGlobalSetting {
|
||||
@override
|
||||
String get URL => "settings/user/";
|
||||
}
|
||||
|
||||
class InvenTreeAttachment extends InvenTreeModel {
|
||||
// Class representing an "attachment" file
|
||||
InvenTreeAttachment() : super();
|
||||
|
||||
InvenTreeAttachment.fromJson(Map<String, dynamic> json)
|
||||
: super.fromJson(json);
|
||||
|
||||
@override
|
||||
String get URL => "attachment/";
|
||||
|
||||
@override
|
||||
Map<String, Map<String, dynamic>> formFields() {
|
||||
Map<String, Map<String, dynamic>> fields = {"link": {}, "comment": {}};
|
||||
|
||||
if (!hasLink) {
|
||||
fields.remove("link");
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
// Override this reference field for any subclasses
|
||||
// Note: This is used for the *legacy* attachment API
|
||||
String get REFERENCE_FIELD => "";
|
||||
|
||||
// Override this reference field for any subclasses
|
||||
// Note: This is used for the *modern* attachment API
|
||||
String get REF_MODEL_TYPE => "";
|
||||
|
||||
String get attachment => getString("attachment");
|
||||
|
||||
bool get hasAttachment => attachment.isNotEmpty;
|
||||
|
||||
// Return the filename of the attachment
|
||||
String get filename {
|
||||
return attachment.split("/").last;
|
||||
}
|
||||
|
||||
IconData get icon {
|
||||
String fn = filename.toLowerCase();
|
||||
|
||||
if (fn.endsWith(".pdf")) {
|
||||
return TablerIcons.file_type_pdf;
|
||||
} else if (fn.endsWith(".csv")) {
|
||||
return TablerIcons.file_type_csv;
|
||||
} else if (fn.endsWith(".doc") || fn.endsWith(".docx")) {
|
||||
return TablerIcons.file_type_doc;
|
||||
} else if (fn.endsWith(".xls") || fn.endsWith(".xlsx")) {
|
||||
return TablerIcons.file_type_xls;
|
||||
}
|
||||
|
||||
// Image formats
|
||||
final List<String> img_formats = [".png", ".jpg", ".gif", ".bmp", ".svg"];
|
||||
|
||||
for (String fmt in img_formats) {
|
||||
if (fn.endsWith(fmt)) {
|
||||
return TablerIcons.file_type_jpg;
|
||||
}
|
||||
}
|
||||
|
||||
return TablerIcons.file;
|
||||
}
|
||||
|
||||
String get comment => getString("comment");
|
||||
|
||||
DateTime? get uploadDate {
|
||||
if (jsondata.containsKey("upload_date")) {
|
||||
return DateTime.tryParse((jsondata["upload_date"] ?? "") as String);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Return a count of how many attachments exist against the specified model ID
|
||||
Future<int> countAttachments(int modelId) {
|
||||
Map<String, String> filters = {};
|
||||
|
||||
if (InvenTreeAPI().supportsModernAttachments) {
|
||||
filters["model_type"] = REF_MODEL_TYPE;
|
||||
filters["model_id"] = modelId.toString();
|
||||
} else {
|
||||
filters[REFERENCE_FIELD] = modelId.toString();
|
||||
}
|
||||
|
||||
return count(filters: filters);
|
||||
}
|
||||
|
||||
Future<bool> uploadAttachment(
|
||||
File attachment,
|
||||
int modelId, {
|
||||
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);
|
||||
|
||||
String url = URL;
|
||||
|
||||
if (comment.isNotEmpty) {
|
||||
data["comment"] = comment;
|
||||
}
|
||||
|
||||
if (InvenTreeAPI().supportsModernAttachments) {
|
||||
url = "attachment/";
|
||||
data["model_id"] = modelId.toString();
|
||||
data["model_type"] = REF_MODEL_TYPE;
|
||||
} else {
|
||||
if (REFERENCE_FIELD.isEmpty) {
|
||||
sentryReportMessage(
|
||||
"uploadAttachment called with empty 'REFERENCE_FIELD'",
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
data[REFERENCE_FIELD] = modelId.toString();
|
||||
}
|
||||
|
||||
final APIResponse response = await InvenTreeAPI().uploadFile(
|
||||
url,
|
||||
attachment,
|
||||
method: "POST",
|
||||
name: "attachment",
|
||||
fields: data,
|
||||
);
|
||||
|
||||
return response.successful();
|
||||
}
|
||||
|
||||
Future<bool> uploadImage(int modelId, {String prefix = "InvenTree"}) async {
|
||||
bool result = false;
|
||||
|
||||
await FilePickerDialog.pickImageFromCamera().then((File? file) {
|
||||
if (file != null) {
|
||||
String dir = path.dirname(file.path);
|
||||
String ext = path.extension(file.path);
|
||||
String now = DateTime.now().toIso8601String().replaceAll(":", "-");
|
||||
|
||||
// Rename the file with a unique name
|
||||
String filename = "${dir}/${prefix}_image_${now}${ext}";
|
||||
|
||||
try {
|
||||
file.rename(filename).then((File renamed) {
|
||||
uploadAttachment(renamed, modelId).then((success) {
|
||||
result = success;
|
||||
showSnackIcon(
|
||||
result ? L10().imageUploadSuccess : L10().imageUploadFailure,
|
||||
success: result,
|
||||
);
|
||||
});
|
||||
});
|
||||
} catch (error, stackTrace) {
|
||||
sentryReportError("uploadImage", error, stackTrace);
|
||||
showSnackIcon(L10().imageUploadFailure, success: false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Download this attachment file
|
||||
*/
|
||||
Future<void> downloadAttachment() async {
|
||||
await InvenTreeAPI().downloadFile(attachment);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -547,28 +547,3 @@ class InvenTreePartPricing extends InvenTreeModel {
|
||||
double? get saleHistoryMin => getDoubleOrNull("sale_history_min");
|
||||
double? get saleHistoryMax => getDoubleOrNull("sale_history_max");
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 REF_MODEL_TYPE => "part";
|
||||
|
||||
@override
|
||||
String get URL => InvenTreeAPI().supportsModernAttachments
|
||||
? "attachment/"
|
||||
: "part/attachment/";
|
||||
|
||||
@override
|
||||
InvenTreeModel createFromJson(Map<String, dynamic> json) =>
|
||||
InvenTreePartAttachment.fromJson(json);
|
||||
}
|
||||
|
||||
@@ -336,28 +336,3 @@ class InvenTreePOExtraLineItem extends InvenTreeExtraLineItem {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class representing an attachment file against a PurchaseOrder 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 REF_MODEL_TYPE => "purchaseorder";
|
||||
|
||||
@override
|
||||
String get URL => InvenTreeAPI().supportsModernAttachments
|
||||
? "attachment/"
|
||||
: "order/po/attachment/";
|
||||
|
||||
@override
|
||||
InvenTreeModel createFromJson(Map<String, dynamic> json) =>
|
||||
InvenTreePurchaseOrderAttachment.fromJson(json);
|
||||
}
|
||||
|
||||
@@ -334,7 +334,7 @@ class InvenTreeSalesOrderShipment extends InvenTreeModel {
|
||||
/*
|
||||
* Class representing an allocation of stock against a SalesOrderShipment
|
||||
*/
|
||||
class InvenTreeSalesOrderAllocation extends InvenTreeAttachment {
|
||||
class InvenTreeSalesOrderAllocation extends InvenTreeModel {
|
||||
InvenTreeSalesOrderAllocation() : super();
|
||||
|
||||
InvenTreeSalesOrderAllocation.fromJson(Map<String, dynamic> json)
|
||||
@@ -428,48 +428,3 @@ class InvenTreeSalesOrderAllocation extends InvenTreeAttachment {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class representing an attachment file against a SalesOrder object
|
||||
*/
|
||||
class InvenTreeSalesOrderAttachment extends InvenTreeAttachment {
|
||||
InvenTreeSalesOrderAttachment() : super();
|
||||
|
||||
InvenTreeSalesOrderAttachment.fromJson(Map<String, dynamic> json)
|
||||
: super.fromJson(json);
|
||||
|
||||
@override
|
||||
InvenTreeModel createFromJson(Map<String, dynamic> json) =>
|
||||
InvenTreeSalesOrderAttachment.fromJson(json);
|
||||
|
||||
@override
|
||||
String get REFERENCE_FIELD => "order";
|
||||
|
||||
@override
|
||||
String get REF_MODEL_TYPE => "salesorder";
|
||||
|
||||
@override
|
||||
String get URL => InvenTreeAPI().supportsModernAttachments
|
||||
? "attachment/"
|
||||
: "order/so/attachment/";
|
||||
}
|
||||
|
||||
class InvenTreeSalesOrderShipmentAttachment extends InvenTreeAttachment {
|
||||
InvenTreeSalesOrderShipmentAttachment() : super();
|
||||
|
||||
InvenTreeSalesOrderShipmentAttachment.fromJson(Map<String, dynamic> json)
|
||||
: super.fromJson(json);
|
||||
|
||||
@override
|
||||
InvenTreeModel createFromJson(Map<String, dynamic> json) =>
|
||||
InvenTreeSalesOrderShipmentAttachment.fromJson(json);
|
||||
|
||||
@override
|
||||
String get REFERENCE_FIELD => "shipment";
|
||||
|
||||
@override
|
||||
String get REF_MODEL_TYPE => "salesordershipment";
|
||||
|
||||
@override
|
||||
String get URL => "attachment/";
|
||||
}
|
||||
|
||||
@@ -575,31 +575,6 @@ 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 REF_MODEL_TYPE => "stockitem";
|
||||
|
||||
@override
|
||||
String get URL => InvenTreeAPI().supportsModernAttachments
|
||||
? "attachment/"
|
||||
: "stock/attachment/";
|
||||
|
||||
@override
|
||||
InvenTreeModel createFromJson(Map<String, dynamic> json) =>
|
||||
InvenTreeStockItemAttachment.fromJson(json);
|
||||
}
|
||||
|
||||
class InvenTreeStockLocation extends InvenTreeModel {
|
||||
InvenTreeStockLocation() : super();
|
||||
|
||||
|
||||
@@ -2,14 +2,13 @@ import "dart:io";
|
||||
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
|
||||
import "package:inventree/api.dart";
|
||||
import "package:inventree/inventree/attachment.dart";
|
||||
import "package:inventree/widget/link_icon.dart";
|
||||
import "package:one_context/one_context.dart";
|
||||
|
||||
import "package:inventree/api.dart";
|
||||
import "package:inventree/l10.dart";
|
||||
import "package:inventree/app_colors.dart";
|
||||
|
||||
import "package:inventree/inventree/model.dart";
|
||||
|
||||
import "package:inventree/widget/fields.dart";
|
||||
import "package:inventree/widget/progress.dart";
|
||||
import "package:inventree/widget/snacks.dart";
|
||||
@@ -23,13 +22,13 @@ import "package:inventree/widget/refreshable_state.dart";
|
||||
*/
|
||||
class AttachmentWidget extends StatefulWidget {
|
||||
const AttachmentWidget(
|
||||
this.attachmentClass,
|
||||
this.modelType,
|
||||
this.modelId,
|
||||
this.imagePrefix,
|
||||
this.hasUploadPermission,
|
||||
) : super();
|
||||
|
||||
final InvenTreeAttachment attachmentClass;
|
||||
final String modelType;
|
||||
final int modelId;
|
||||
final bool hasUploadPermission;
|
||||
final String imagePrefix;
|
||||
@@ -54,15 +53,15 @@ class _AttachmentWidgetState extends RefreshableState<AttachmentWidget> {
|
||||
IconButton(
|
||||
icon: Icon(TablerIcons.camera),
|
||||
onPressed: () async {
|
||||
widget.attachmentClass.uploadImage(
|
||||
widget.modelId,
|
||||
prefix: widget.imagePrefix,
|
||||
);
|
||||
FilePickerDialog.pickImageFromCamera().then((File? file) {
|
||||
upload(context, file).then((_) {
|
||||
refresh(context);
|
||||
});
|
||||
});
|
||||
InvenTreeAttachment()
|
||||
.uploadImage(
|
||||
widget.modelType,
|
||||
widget.modelId,
|
||||
prefix: widget.imagePrefix,
|
||||
)
|
||||
.then((_) {
|
||||
refresh(context);
|
||||
});
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
@@ -83,8 +82,9 @@ class _AttachmentWidgetState extends RefreshableState<AttachmentWidget> {
|
||||
|
||||
showLoadingOverlay();
|
||||
|
||||
final bool result = await widget.attachmentClass.uploadAttachment(
|
||||
final bool result = await InvenTreeAttachment().uploadAttachment(
|
||||
file,
|
||||
widget.modelType,
|
||||
widget.modelId,
|
||||
);
|
||||
|
||||
@@ -168,25 +168,24 @@ class _AttachmentWidgetState extends RefreshableState<AttachmentWidget> {
|
||||
Future<void> request(BuildContext context) async {
|
||||
Map<String, String> filters = {};
|
||||
|
||||
if (InvenTreeAPI().supportsModernAttachments) {
|
||||
filters["model_type"] = widget.attachmentClass.REF_MODEL_TYPE;
|
||||
filters["model_id"] = widget.modelId.toString();
|
||||
} else {
|
||||
filters[widget.attachmentClass.REFERENCE_FIELD] = widget.modelId
|
||||
.toString();
|
||||
}
|
||||
filters["model_type"] = widget.modelType;
|
||||
filters["model_id"] = widget.modelId.toString();
|
||||
|
||||
await widget.attachmentClass.list(filters: filters).then((var results) {
|
||||
attachments.clear();
|
||||
List<InvenTreeAttachment> _attachments = [];
|
||||
|
||||
InvenTreeAttachment().list(filters: filters).then((var results) {
|
||||
for (var result in results) {
|
||||
if (result is InvenTreeAttachment) {
|
||||
attachments.add(result);
|
||||
_attachments.add(result);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setState(() {});
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
attachments = _attachments;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -240,3 +239,40 @@ class _AttachmentWidgetState extends RefreshableState<AttachmentWidget> {
|
||||
return tiles;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Return a ListTile to display attachments for the specified model
|
||||
*/
|
||||
ListTile? ShowAttachmentsItem(
|
||||
BuildContext context,
|
||||
String modelType,
|
||||
int modelId,
|
||||
String imagePrefix,
|
||||
int attachmentCount,
|
||||
bool hasUploadPermission,
|
||||
) {
|
||||
if (!InvenTreeAPI().supportsModernAttachments) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
title: Text(L10().attachments),
|
||||
leading: Icon(TablerIcons.file, color: COLOR_ACTION),
|
||||
trailing: LinkIcon(
|
||||
text: attachmentCount > 0 ? attachmentCount.toString() : null,
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => AttachmentWidget(
|
||||
modelType,
|
||||
modelId,
|
||||
imagePrefix,
|
||||
hasUploadPermission,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_speed_dial/flutter_speed_dial.dart";
|
||||
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
|
||||
import "package:inventree/inventree/attachment.dart";
|
||||
|
||||
import "package:inventree/l10.dart";
|
||||
import "package:inventree/api.dart";
|
||||
@@ -184,15 +185,15 @@ class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> {
|
||||
}
|
||||
});
|
||||
|
||||
InvenTreeCompanyAttachment().countAttachments(widget.company.pk).then((
|
||||
value,
|
||||
) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
attachmentCount = value;
|
||||
InvenTreeAttachment()
|
||||
.countAttachments(InvenTreeCompany.MODEL_TYPE, widget.company.pk)
|
||||
.then((value) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
attachmentCount = value;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> editCompany(BuildContext context) async {
|
||||
@@ -393,29 +394,19 @@ class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> {
|
||||
);
|
||||
}
|
||||
|
||||
tiles.add(
|
||||
ListTile(
|
||||
title: Text(L10().attachments),
|
||||
leading: Icon(TablerIcons.file, color: COLOR_ACTION),
|
||||
trailing: LinkIcon(
|
||||
text: attachmentCount > 0 ? attachmentCount.toString() : null,
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => AttachmentWidget(
|
||||
InvenTreeCompanyAttachment(),
|
||||
widget.company.pk,
|
||||
widget.company.name,
|
||||
InvenTreeCompany().canEdit,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
ListTile? attachmentTile = ShowAttachmentsItem(
|
||||
context,
|
||||
InvenTreeCompany.MODEL_TYPE,
|
||||
widget.company.pk,
|
||||
widget.company.name,
|
||||
attachmentCount,
|
||||
widget.company.canEdit,
|
||||
);
|
||||
|
||||
if (attachmentTile != null) {
|
||||
tiles.add(attachmentTile);
|
||||
}
|
||||
|
||||
return tiles;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import "package:inventree/app_colors.dart";
|
||||
import "package:inventree/barcode/barcode.dart";
|
||||
import "package:inventree/barcode/purchase_order.dart";
|
||||
import "package:inventree/helpers.dart";
|
||||
import "package:inventree/inventree/attachment.dart";
|
||||
import "package:inventree/l10.dart";
|
||||
|
||||
import "package:inventree/inventree/model.dart";
|
||||
@@ -174,8 +175,12 @@ class _PurchaseOrderDetailState
|
||||
|
||||
/// Upload an image against the current PurchaseOrder
|
||||
Future<void> _uploadImage(BuildContext context) async {
|
||||
InvenTreePurchaseOrderAttachment()
|
||||
.uploadImage(widget.order.pk, prefix: widget.order.reference)
|
||||
InvenTreeAttachment()
|
||||
.uploadImage(
|
||||
InvenTreePurchaseOrder.MODEL_TYPE,
|
||||
widget.order.pk,
|
||||
prefix: widget.order.reference,
|
||||
)
|
||||
.then((result) => refresh(context));
|
||||
}
|
||||
|
||||
@@ -295,15 +300,15 @@ class _PurchaseOrderDetailState
|
||||
}
|
||||
}
|
||||
|
||||
InvenTreePurchaseOrderAttachment().countAttachments(widget.order.pk).then((
|
||||
int value,
|
||||
) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
attachmentCount = value;
|
||||
InvenTreeAttachment()
|
||||
.countAttachments(InvenTreePurchaseOrder.MODEL_TYPE, widget.order.pk)
|
||||
.then((int value) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
attachmentCount = value;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (api.supportsPurchaseOrderDestination &&
|
||||
widget.order.destinationId > 0) {
|
||||
@@ -565,30 +570,19 @@ class _PurchaseOrderDetailState
|
||||
),
|
||||
);
|
||||
|
||||
// Attachments
|
||||
tiles.add(
|
||||
ListTile(
|
||||
title: Text(L10().attachments),
|
||||
leading: Icon(TablerIcons.file, color: COLOR_ACTION),
|
||||
trailing: LinkIcon(
|
||||
text: attachmentCount > 0 ? attachmentCount.toString() : null,
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => AttachmentWidget(
|
||||
InvenTreePurchaseOrderAttachment(),
|
||||
widget.order.pk,
|
||||
widget.order.reference,
|
||||
widget.order.canEdit,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
ListTile? attachmentTile = ShowAttachmentsItem(
|
||||
context,
|
||||
InvenTreePurchaseOrder.MODEL_TYPE,
|
||||
widget.order.pk,
|
||||
widget.order.reference,
|
||||
attachmentCount,
|
||||
widget.order.canEdit,
|
||||
);
|
||||
|
||||
if (attachmentTile != null) {
|
||||
tiles.add(attachmentTile);
|
||||
}
|
||||
|
||||
return tiles;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import "package:flutter_speed_dial/flutter_speed_dial.dart";
|
||||
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
|
||||
import "package:inventree/barcode/barcode.dart";
|
||||
import "package:inventree/barcode/sales_order.dart";
|
||||
import "package:inventree/inventree/attachment.dart";
|
||||
import "package:inventree/inventree/company.dart";
|
||||
import "package:inventree/inventree/sales_order.dart";
|
||||
import "package:inventree/preferences.dart";
|
||||
@@ -108,8 +109,12 @@ class _SalesOrderDetailState extends RefreshableState<SalesOrderDetailWidget> {
|
||||
|
||||
/// Upload an image for this order
|
||||
Future<void> _uploadImage(BuildContext context) async {
|
||||
InvenTreeSalesOrderAttachment()
|
||||
.uploadImage(widget.order.pk, prefix: widget.order.reference)
|
||||
InvenTreeAttachment()
|
||||
.uploadImage(
|
||||
InvenTreeSalesOrder.MODEL_TYPE,
|
||||
widget.order.pk,
|
||||
prefix: widget.order.reference,
|
||||
)
|
||||
.then((result) => refresh(context));
|
||||
}
|
||||
|
||||
@@ -266,15 +271,15 @@ class _SalesOrderDetailState extends RefreshableState<SalesOrderDetailWidget> {
|
||||
true,
|
||||
);
|
||||
|
||||
InvenTreeSalesOrderAttachment().countAttachments(widget.order.pk).then((
|
||||
int value,
|
||||
) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
attachmentCount = value;
|
||||
InvenTreeAttachment()
|
||||
.countAttachments(InvenTreeSalesOrder.MODEL_TYPE, widget.order.pk)
|
||||
.then((int value) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
attachmentCount = value;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Count number of "extra line items" against this order
|
||||
InvenTreeSOExtraLineItem()
|
||||
@@ -492,30 +497,19 @@ class _SalesOrderDetailState extends RefreshableState<SalesOrderDetailWidget> {
|
||||
),
|
||||
);
|
||||
|
||||
// Attachments
|
||||
tiles.add(
|
||||
ListTile(
|
||||
title: Text(L10().attachments),
|
||||
leading: Icon(TablerIcons.file, color: COLOR_ACTION),
|
||||
trailing: LinkIcon(
|
||||
text: attachmentCount > 0 ? attachmentCount.toString() : null,
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => AttachmentWidget(
|
||||
InvenTreeSalesOrderAttachment(),
|
||||
widget.order.pk,
|
||||
widget.order.reference,
|
||||
widget.order.canEdit,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
ListTile? attachmentTile = ShowAttachmentsItem(
|
||||
context,
|
||||
InvenTreeSalesOrder.MODEL_TYPE,
|
||||
widget.order.pk,
|
||||
widget.order.reference,
|
||||
attachmentCount,
|
||||
widget.order.canEdit,
|
||||
);
|
||||
|
||||
if (attachmentTile != null) {
|
||||
tiles.add(attachmentTile);
|
||||
}
|
||||
|
||||
return tiles;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
|
||||
import "package:inventree/api.dart";
|
||||
import "package:inventree/api_form.dart";
|
||||
import "package:inventree/app_colors.dart";
|
||||
import "package:inventree/inventree/attachment.dart";
|
||||
import "package:inventree/inventree/sales_order.dart";
|
||||
import "package:inventree/l10.dart";
|
||||
import "package:inventree/preferences.dart";
|
||||
@@ -91,8 +92,11 @@ class _SOShipmentDetailWidgetState
|
||||
});
|
||||
}
|
||||
|
||||
InvenTreeSalesOrderShipmentAttachment()
|
||||
.countAttachments(widget.shipment.pk)
|
||||
InvenTreeAttachment()
|
||||
.countAttachments(
|
||||
InvenTreeSalesOrderShipment.MODEL_TYPE,
|
||||
widget.shipment.pk,
|
||||
)
|
||||
.then((int value) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
@@ -104,8 +108,12 @@ class _SOShipmentDetailWidgetState
|
||||
|
||||
/// Upload an image for this shipment
|
||||
Future<void> _uploadImage(BuildContext context) async {
|
||||
InvenTreeSalesOrderShipmentAttachment()
|
||||
.uploadImage(widget.shipment.pk, prefix: widget.shipment.reference)
|
||||
InvenTreeAttachment()
|
||||
.uploadImage(
|
||||
InvenTreeSalesOrderShipment.MODEL_TYPE,
|
||||
widget.shipment.pk,
|
||||
prefix: widget.shipment.reference,
|
||||
)
|
||||
.then((result) => refresh(context));
|
||||
}
|
||||
|
||||
@@ -339,30 +347,19 @@ class _SOShipmentDetailWidgetState
|
||||
),
|
||||
);
|
||||
|
||||
// Attachments
|
||||
tiles.add(
|
||||
ListTile(
|
||||
title: Text(L10().attachments),
|
||||
leading: Icon(TablerIcons.file, color: COLOR_ACTION),
|
||||
trailing: LinkIcon(
|
||||
text: attachmentCount > 0 ? attachmentCount.toString() : null,
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => AttachmentWidget(
|
||||
InvenTreeSalesOrderShipmentAttachment(),
|
||||
widget.shipment.pk,
|
||||
widget.shipment.reference,
|
||||
widget.shipment.canEdit,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
ListTile? attachmentTile = ShowAttachmentsItem(
|
||||
context,
|
||||
InvenTreeSalesOrderShipment.MODEL_TYPE,
|
||||
widget.shipment.pk,
|
||||
widget.shipment.reference,
|
||||
attachmentCount,
|
||||
widget.shipment.canEdit,
|
||||
);
|
||||
|
||||
if (attachmentTile != null) {
|
||||
tiles.add(attachmentTile);
|
||||
}
|
||||
|
||||
return tiles;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
|
||||
|
||||
import "package:inventree/app_colors.dart";
|
||||
import "package:inventree/barcode/barcode.dart";
|
||||
import "package:inventree/inventree/attachment.dart";
|
||||
import "package:inventree/l10.dart";
|
||||
import "package:inventree/helpers.dart";
|
||||
|
||||
@@ -212,13 +213,15 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
||||
}
|
||||
|
||||
// Request the number of attachments
|
||||
InvenTreePartAttachment().countAttachments(part.pk).then((int value) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
attachmentCount = value;
|
||||
InvenTreeAttachment()
|
||||
.countAttachments(InvenTreePart.MODEL_TYPE, part.pk)
|
||||
.then((int value) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
attachmentCount = value;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// If show pricing information?
|
||||
if (showPricing) {
|
||||
@@ -596,29 +599,19 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
||||
),
|
||||
);
|
||||
|
||||
tiles.add(
|
||||
ListTile(
|
||||
title: Text(L10().attachments),
|
||||
leading: Icon(TablerIcons.file, color: COLOR_ACTION),
|
||||
trailing: LinkIcon(
|
||||
text: attachmentCount > 0 ? attachmentCount.toString() : null,
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => AttachmentWidget(
|
||||
InvenTreePartAttachment(),
|
||||
part.pk,
|
||||
L10().part,
|
||||
part.canEdit,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
ListTile? attachmentTile = ShowAttachmentsItem(
|
||||
context,
|
||||
InvenTreePart.MODEL_TYPE,
|
||||
part.pk,
|
||||
L10().part,
|
||||
attachmentCount,
|
||||
part.canEdit,
|
||||
);
|
||||
|
||||
if (attachmentTile != null) {
|
||||
tiles.add(attachmentTile);
|
||||
}
|
||||
|
||||
return tiles;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import "package:inventree/app_colors.dart";
|
||||
import "package:inventree/barcode/barcode.dart";
|
||||
import "package:inventree/barcode/stock.dart";
|
||||
import "package:inventree/helpers.dart";
|
||||
import "package:inventree/inventree/attachment.dart";
|
||||
import "package:inventree/inventree/sales_order.dart";
|
||||
import "package:inventree/l10.dart";
|
||||
import "package:inventree/api.dart";
|
||||
@@ -255,15 +256,15 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
||||
}
|
||||
|
||||
// Request the number of attachments
|
||||
InvenTreeStockItemAttachment().countAttachments(widget.item.pk).then((
|
||||
int value,
|
||||
) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
attachmentCount = value;
|
||||
InvenTreeAttachment()
|
||||
.countAttachments(InvenTreeStockItem.MODEL_TYPE, widget.item.pk)
|
||||
.then((int value) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
attachmentCount = value;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Request SalesOrder information
|
||||
if (widget.item.hasSalesOrder) {
|
||||
@@ -837,29 +838,19 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
||||
),
|
||||
);
|
||||
|
||||
tiles.add(
|
||||
ListTile(
|
||||
title: Text(L10().attachments),
|
||||
leading: Icon(TablerIcons.file, color: COLOR_ACTION),
|
||||
trailing: LinkIcon(
|
||||
text: attachmentCount > 0 ? attachmentCount.toString() : null,
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => AttachmentWidget(
|
||||
InvenTreeStockItemAttachment(),
|
||||
widget.item.pk,
|
||||
L10().stockItem,
|
||||
widget.item.canEdit,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
ListTile? attachmentTile = ShowAttachmentsItem(
|
||||
context,
|
||||
InvenTreeStockItem.MODEL_TYPE,
|
||||
widget.item.pk,
|
||||
L10().stockItem,
|
||||
attachmentCount,
|
||||
widget.item.canEdit,
|
||||
);
|
||||
|
||||
if (attachmentTile != null) {
|
||||
tiles.add(attachmentTile);
|
||||
}
|
||||
|
||||
return tiles;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user