2
0
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:
Oliver
2025-11-28 23:53:10 +11:00
committed by GitHub
parent bb10117f01
commit 346b1a150f
15 changed files with 381 additions and 519 deletions

View File

@@ -1,3 +1,8 @@
### x.xx.x - Month Year
---
- Fixes bug which launched camera twice when uploading an attachment
### 0.21.1 - November 2025 ### 0.21.1 - November 2025
--- ---

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

View File

@@ -111,31 +111,6 @@ class InvenTreeCompany extends InvenTreeModel {
InvenTreeCompany.fromJson(json); 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 * The InvenTreeSupplierPart class represents the SupplierPart model in the InvenTree database
*/ */

View File

@@ -1,5 +1,4 @@
import "dart:async"; import "dart:async";
import "dart:io";
import "package:flutter_tabler_icons/flutter_tabler_icons.dart"; import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
import "package:flutter/material.dart"; import "package:flutter/material.dart";
@@ -15,7 +14,6 @@ import "package:inventree/helpers.dart";
import "package:inventree/inventree/sentry.dart"; import "package:inventree/inventree/sentry.dart";
import "package:inventree/widget/dialogs.dart"; import "package:inventree/widget/dialogs.dart";
import "package:inventree/widget/fields.dart";
// Paginated response object // Paginated response object
class InvenTreePageResponse { class InvenTreePageResponse {
@@ -934,171 +932,3 @@ class InvenTreeUserSetting extends InvenTreeGlobalSetting {
@override @override
String get URL => "settings/user/"; 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);
}
}

View File

@@ -547,28 +547,3 @@ class InvenTreePartPricing extends InvenTreeModel {
double? get saleHistoryMin => getDoubleOrNull("sale_history_min"); double? get saleHistoryMin => getDoubleOrNull("sale_history_min");
double? get saleHistoryMax => getDoubleOrNull("sale_history_max"); 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);
}

View File

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

View File

@@ -334,7 +334,7 @@ class InvenTreeSalesOrderShipment extends InvenTreeModel {
/* /*
* Class representing an allocation of stock against a SalesOrderShipment * Class representing an allocation of stock against a SalesOrderShipment
*/ */
class InvenTreeSalesOrderAllocation extends InvenTreeAttachment { class InvenTreeSalesOrderAllocation extends InvenTreeModel {
InvenTreeSalesOrderAllocation() : super(); InvenTreeSalesOrderAllocation() : super();
InvenTreeSalesOrderAllocation.fromJson(Map<String, dynamic> json) 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/";
}

View File

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

View File

@@ -2,14 +2,13 @@ import "dart:io";
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter_tabler_icons/flutter_tabler_icons.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:one_context/one_context.dart";
import "package:inventree/api.dart";
import "package:inventree/l10.dart"; import "package:inventree/l10.dart";
import "package:inventree/app_colors.dart"; import "package:inventree/app_colors.dart";
import "package:inventree/inventree/model.dart";
import "package:inventree/widget/fields.dart"; import "package:inventree/widget/fields.dart";
import "package:inventree/widget/progress.dart"; import "package:inventree/widget/progress.dart";
import "package:inventree/widget/snacks.dart"; import "package:inventree/widget/snacks.dart";
@@ -23,13 +22,13 @@ import "package:inventree/widget/refreshable_state.dart";
*/ */
class AttachmentWidget extends StatefulWidget { class AttachmentWidget extends StatefulWidget {
const AttachmentWidget( const AttachmentWidget(
this.attachmentClass, this.modelType,
this.modelId, this.modelId,
this.imagePrefix, this.imagePrefix,
this.hasUploadPermission, this.hasUploadPermission,
) : super(); ) : super();
final InvenTreeAttachment attachmentClass; final String modelType;
final int modelId; final int modelId;
final bool hasUploadPermission; final bool hasUploadPermission;
final String imagePrefix; final String imagePrefix;
@@ -54,15 +53,15 @@ class _AttachmentWidgetState extends RefreshableState<AttachmentWidget> {
IconButton( IconButton(
icon: Icon(TablerIcons.camera), icon: Icon(TablerIcons.camera),
onPressed: () async { onPressed: () async {
widget.attachmentClass.uploadImage( InvenTreeAttachment()
widget.modelId, .uploadImage(
prefix: widget.imagePrefix, widget.modelType,
); widget.modelId,
FilePickerDialog.pickImageFromCamera().then((File? file) { prefix: widget.imagePrefix,
upload(context, file).then((_) { )
refresh(context); .then((_) {
}); refresh(context);
}); });
}, },
), ),
IconButton( IconButton(
@@ -83,8 +82,9 @@ class _AttachmentWidgetState extends RefreshableState<AttachmentWidget> {
showLoadingOverlay(); showLoadingOverlay();
final bool result = await widget.attachmentClass.uploadAttachment( final bool result = await InvenTreeAttachment().uploadAttachment(
file, file,
widget.modelType,
widget.modelId, widget.modelId,
); );
@@ -168,25 +168,24 @@ class _AttachmentWidgetState extends RefreshableState<AttachmentWidget> {
Future<void> request(BuildContext context) async { Future<void> request(BuildContext context) async {
Map<String, String> filters = {}; Map<String, String> filters = {};
if (InvenTreeAPI().supportsModernAttachments) { filters["model_type"] = widget.modelType;
filters["model_type"] = widget.attachmentClass.REF_MODEL_TYPE; filters["model_id"] = widget.modelId.toString();
filters["model_id"] = widget.modelId.toString();
} else {
filters[widget.attachmentClass.REFERENCE_FIELD] = widget.modelId
.toString();
}
await widget.attachmentClass.list(filters: filters).then((var results) { List<InvenTreeAttachment> _attachments = [];
attachments.clear();
InvenTreeAttachment().list(filters: filters).then((var results) {
for (var result in results) { for (var result in results) {
if (result is InvenTreeAttachment) { if (result is InvenTreeAttachment) {
attachments.add(result); _attachments.add(result);
} }
} }
});
setState(() {}); if (mounted) {
setState(() {
attachments = _attachments;
});
}
});
} }
@override @override
@@ -240,3 +239,40 @@ class _AttachmentWidgetState extends RefreshableState<AttachmentWidget> {
return tiles; 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,
),
),
);
},
);
}

View File

@@ -1,6 +1,7 @@
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter_speed_dial/flutter_speed_dial.dart"; import "package:flutter_speed_dial/flutter_speed_dial.dart";
import "package:flutter_tabler_icons/flutter_tabler_icons.dart"; import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
import "package:inventree/inventree/attachment.dart";
import "package:inventree/l10.dart"; import "package:inventree/l10.dart";
import "package:inventree/api.dart"; import "package:inventree/api.dart";
@@ -184,15 +185,15 @@ class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> {
} }
}); });
InvenTreeCompanyAttachment().countAttachments(widget.company.pk).then(( InvenTreeAttachment()
value, .countAttachments(InvenTreeCompany.MODEL_TYPE, widget.company.pk)
) { .then((value) {
if (mounted) { if (mounted) {
setState(() { setState(() {
attachmentCount = value; attachmentCount = value;
});
}
}); });
}
});
} }
Future<void> editCompany(BuildContext context) async { Future<void> editCompany(BuildContext context) async {
@@ -393,29 +394,19 @@ class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> {
); );
} }
tiles.add( ListTile? attachmentTile = ShowAttachmentsItem(
ListTile( context,
title: Text(L10().attachments), InvenTreeCompany.MODEL_TYPE,
leading: Icon(TablerIcons.file, color: COLOR_ACTION), widget.company.pk,
trailing: LinkIcon( widget.company.name,
text: attachmentCount > 0 ? attachmentCount.toString() : null, attachmentCount,
), widget.company.canEdit,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AttachmentWidget(
InvenTreeCompanyAttachment(),
widget.company.pk,
widget.company.name,
InvenTreeCompany().canEdit,
),
),
);
},
),
); );
if (attachmentTile != null) {
tiles.add(attachmentTile);
}
return tiles; return tiles;
} }
} }

View File

@@ -7,6 +7,7 @@ import "package:inventree/app_colors.dart";
import "package:inventree/barcode/barcode.dart"; import "package:inventree/barcode/barcode.dart";
import "package:inventree/barcode/purchase_order.dart"; import "package:inventree/barcode/purchase_order.dart";
import "package:inventree/helpers.dart"; import "package:inventree/helpers.dart";
import "package:inventree/inventree/attachment.dart";
import "package:inventree/l10.dart"; import "package:inventree/l10.dart";
import "package:inventree/inventree/model.dart"; import "package:inventree/inventree/model.dart";
@@ -174,8 +175,12 @@ class _PurchaseOrderDetailState
/// Upload an image against the current PurchaseOrder /// Upload an image against the current PurchaseOrder
Future<void> _uploadImage(BuildContext context) async { Future<void> _uploadImage(BuildContext context) async {
InvenTreePurchaseOrderAttachment() InvenTreeAttachment()
.uploadImage(widget.order.pk, prefix: widget.order.reference) .uploadImage(
InvenTreePurchaseOrder.MODEL_TYPE,
widget.order.pk,
prefix: widget.order.reference,
)
.then((result) => refresh(context)); .then((result) => refresh(context));
} }
@@ -295,15 +300,15 @@ class _PurchaseOrderDetailState
} }
} }
InvenTreePurchaseOrderAttachment().countAttachments(widget.order.pk).then(( InvenTreeAttachment()
int value, .countAttachments(InvenTreePurchaseOrder.MODEL_TYPE, widget.order.pk)
) { .then((int value) {
if (mounted) { if (mounted) {
setState(() { setState(() {
attachmentCount = value; attachmentCount = value;
});
}
}); });
}
});
if (api.supportsPurchaseOrderDestination && if (api.supportsPurchaseOrderDestination &&
widget.order.destinationId > 0) { widget.order.destinationId > 0) {
@@ -565,30 +570,19 @@ class _PurchaseOrderDetailState
), ),
); );
// Attachments ListTile? attachmentTile = ShowAttachmentsItem(
tiles.add( context,
ListTile( InvenTreePurchaseOrder.MODEL_TYPE,
title: Text(L10().attachments), widget.order.pk,
leading: Icon(TablerIcons.file, color: COLOR_ACTION), widget.order.reference,
trailing: LinkIcon( attachmentCount,
text: attachmentCount > 0 ? attachmentCount.toString() : null, widget.order.canEdit,
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AttachmentWidget(
InvenTreePurchaseOrderAttachment(),
widget.order.pk,
widget.order.reference,
widget.order.canEdit,
),
),
);
},
),
); );
if (attachmentTile != null) {
tiles.add(attachmentTile);
}
return tiles; return tiles;
} }

View File

@@ -3,6 +3,7 @@ import "package:flutter_speed_dial/flutter_speed_dial.dart";
import "package:flutter_tabler_icons/flutter_tabler_icons.dart"; import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
import "package:inventree/barcode/barcode.dart"; import "package:inventree/barcode/barcode.dart";
import "package:inventree/barcode/sales_order.dart"; import "package:inventree/barcode/sales_order.dart";
import "package:inventree/inventree/attachment.dart";
import "package:inventree/inventree/company.dart"; import "package:inventree/inventree/company.dart";
import "package:inventree/inventree/sales_order.dart"; import "package:inventree/inventree/sales_order.dart";
import "package:inventree/preferences.dart"; import "package:inventree/preferences.dart";
@@ -108,8 +109,12 @@ class _SalesOrderDetailState extends RefreshableState<SalesOrderDetailWidget> {
/// Upload an image for this order /// Upload an image for this order
Future<void> _uploadImage(BuildContext context) async { Future<void> _uploadImage(BuildContext context) async {
InvenTreeSalesOrderAttachment() InvenTreeAttachment()
.uploadImage(widget.order.pk, prefix: widget.order.reference) .uploadImage(
InvenTreeSalesOrder.MODEL_TYPE,
widget.order.pk,
prefix: widget.order.reference,
)
.then((result) => refresh(context)); .then((result) => refresh(context));
} }
@@ -266,15 +271,15 @@ class _SalesOrderDetailState extends RefreshableState<SalesOrderDetailWidget> {
true, true,
); );
InvenTreeSalesOrderAttachment().countAttachments(widget.order.pk).then(( InvenTreeAttachment()
int value, .countAttachments(InvenTreeSalesOrder.MODEL_TYPE, widget.order.pk)
) { .then((int value) {
if (mounted) { if (mounted) {
setState(() { setState(() {
attachmentCount = value; attachmentCount = value;
});
}
}); });
}
});
// Count number of "extra line items" against this order // Count number of "extra line items" against this order
InvenTreeSOExtraLineItem() InvenTreeSOExtraLineItem()
@@ -492,30 +497,19 @@ class _SalesOrderDetailState extends RefreshableState<SalesOrderDetailWidget> {
), ),
); );
// Attachments ListTile? attachmentTile = ShowAttachmentsItem(
tiles.add( context,
ListTile( InvenTreeSalesOrder.MODEL_TYPE,
title: Text(L10().attachments), widget.order.pk,
leading: Icon(TablerIcons.file, color: COLOR_ACTION), widget.order.reference,
trailing: LinkIcon( attachmentCount,
text: attachmentCount > 0 ? attachmentCount.toString() : null, widget.order.canEdit,
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AttachmentWidget(
InvenTreeSalesOrderAttachment(),
widget.order.pk,
widget.order.reference,
widget.order.canEdit,
),
),
);
},
),
); );
if (attachmentTile != null) {
tiles.add(attachmentTile);
}
return tiles; return tiles;
} }

View File

@@ -8,6 +8,7 @@ import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
import "package:inventree/api.dart"; import "package:inventree/api.dart";
import "package:inventree/api_form.dart"; import "package:inventree/api_form.dart";
import "package:inventree/app_colors.dart"; import "package:inventree/app_colors.dart";
import "package:inventree/inventree/attachment.dart";
import "package:inventree/inventree/sales_order.dart"; import "package:inventree/inventree/sales_order.dart";
import "package:inventree/l10.dart"; import "package:inventree/l10.dart";
import "package:inventree/preferences.dart"; import "package:inventree/preferences.dart";
@@ -91,8 +92,11 @@ class _SOShipmentDetailWidgetState
}); });
} }
InvenTreeSalesOrderShipmentAttachment() InvenTreeAttachment()
.countAttachments(widget.shipment.pk) .countAttachments(
InvenTreeSalesOrderShipment.MODEL_TYPE,
widget.shipment.pk,
)
.then((int value) { .then((int value) {
if (mounted) { if (mounted) {
setState(() { setState(() {
@@ -104,8 +108,12 @@ class _SOShipmentDetailWidgetState
/// Upload an image for this shipment /// Upload an image for this shipment
Future<void> _uploadImage(BuildContext context) async { Future<void> _uploadImage(BuildContext context) async {
InvenTreeSalesOrderShipmentAttachment() InvenTreeAttachment()
.uploadImage(widget.shipment.pk, prefix: widget.shipment.reference) .uploadImage(
InvenTreeSalesOrderShipment.MODEL_TYPE,
widget.shipment.pk,
prefix: widget.shipment.reference,
)
.then((result) => refresh(context)); .then((result) => refresh(context));
} }
@@ -339,30 +347,19 @@ class _SOShipmentDetailWidgetState
), ),
); );
// Attachments ListTile? attachmentTile = ShowAttachmentsItem(
tiles.add( context,
ListTile( InvenTreeSalesOrderShipment.MODEL_TYPE,
title: Text(L10().attachments), widget.shipment.pk,
leading: Icon(TablerIcons.file, color: COLOR_ACTION), widget.shipment.reference,
trailing: LinkIcon( attachmentCount,
text: attachmentCount > 0 ? attachmentCount.toString() : null, widget.shipment.canEdit,
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AttachmentWidget(
InvenTreeSalesOrderShipmentAttachment(),
widget.shipment.pk,
widget.shipment.reference,
widget.shipment.canEdit,
),
),
);
},
),
); );
if (attachmentTile != null) {
tiles.add(attachmentTile);
}
return tiles; return tiles;
} }

View File

@@ -4,6 +4,7 @@ import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
import "package:inventree/app_colors.dart"; import "package:inventree/app_colors.dart";
import "package:inventree/barcode/barcode.dart"; import "package:inventree/barcode/barcode.dart";
import "package:inventree/inventree/attachment.dart";
import "package:inventree/l10.dart"; import "package:inventree/l10.dart";
import "package:inventree/helpers.dart"; import "package:inventree/helpers.dart";
@@ -212,13 +213,15 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
} }
// Request the number of attachments // Request the number of attachments
InvenTreePartAttachment().countAttachments(part.pk).then((int value) { InvenTreeAttachment()
if (mounted) { .countAttachments(InvenTreePart.MODEL_TYPE, part.pk)
setState(() { .then((int value) {
attachmentCount = value; if (mounted) {
setState(() {
attachmentCount = value;
});
}
}); });
}
});
// If show pricing information? // If show pricing information?
if (showPricing) { if (showPricing) {
@@ -596,29 +599,19 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
), ),
); );
tiles.add( ListTile? attachmentTile = ShowAttachmentsItem(
ListTile( context,
title: Text(L10().attachments), InvenTreePart.MODEL_TYPE,
leading: Icon(TablerIcons.file, color: COLOR_ACTION), part.pk,
trailing: LinkIcon( L10().part,
text: attachmentCount > 0 ? attachmentCount.toString() : null, attachmentCount,
), part.canEdit,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AttachmentWidget(
InvenTreePartAttachment(),
part.pk,
L10().part,
part.canEdit,
),
),
);
},
),
); );
if (attachmentTile != null) {
tiles.add(attachmentTile);
}
return tiles; return tiles;
} }

View File

@@ -7,6 +7,7 @@ import "package:inventree/app_colors.dart";
import "package:inventree/barcode/barcode.dart"; import "package:inventree/barcode/barcode.dart";
import "package:inventree/barcode/stock.dart"; import "package:inventree/barcode/stock.dart";
import "package:inventree/helpers.dart"; import "package:inventree/helpers.dart";
import "package:inventree/inventree/attachment.dart";
import "package:inventree/inventree/sales_order.dart"; import "package:inventree/inventree/sales_order.dart";
import "package:inventree/l10.dart"; import "package:inventree/l10.dart";
import "package:inventree/api.dart"; import "package:inventree/api.dart";
@@ -255,15 +256,15 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
} }
// Request the number of attachments // Request the number of attachments
InvenTreeStockItemAttachment().countAttachments(widget.item.pk).then(( InvenTreeAttachment()
int value, .countAttachments(InvenTreeStockItem.MODEL_TYPE, widget.item.pk)
) { .then((int value) {
if (mounted) { if (mounted) {
setState(() { setState(() {
attachmentCount = value; attachmentCount = value;
});
}
}); });
}
});
// Request SalesOrder information // Request SalesOrder information
if (widget.item.hasSalesOrder) { if (widget.item.hasSalesOrder) {
@@ -837,29 +838,19 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
), ),
); );
tiles.add( ListTile? attachmentTile = ShowAttachmentsItem(
ListTile( context,
title: Text(L10().attachments), InvenTreeStockItem.MODEL_TYPE,
leading: Icon(TablerIcons.file, color: COLOR_ACTION), widget.item.pk,
trailing: LinkIcon( L10().stockItem,
text: attachmentCount > 0 ? attachmentCount.toString() : null, attachmentCount,
), widget.item.canEdit,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AttachmentWidget(
InvenTreeStockItemAttachment(),
widget.item.pk,
L10().stockItem,
widget.item.canEdit,
),
),
);
},
),
); );
if (attachmentTile != null) {
tiles.add(attachmentTile);
}
return tiles; return tiles;
} }
} }