Skip to content

Commit

Permalink
Order picture action (#557)
Browse files Browse the repository at this point in the history
* Add "take picture" to purchase order detail

* Rename uploaded images

* Provide prefix when uploading images

* Add similar functionality for "sales order" detail

* Add new settings screens

* Control camera shortcut

* Bump release notes
  • Loading branch information
SchrodingersGat authored Dec 5, 2024
1 parent 4698e7e commit 2e798b1
Show file tree
Hide file tree
Showing 14 changed files with 321 additions and 17 deletions.
3 changes: 3 additions & 0 deletions assets/release_notes.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
### 0.17.0 - November 2024
---

- Improvements for image uploading
- Provide "upload image" shortcut on Purchase Order detail view
- Provide "upload image" shortcut on Sales Order detail view
- Clearly indicate if a StockItem is unavailable

### 0.16.5 - September 2024
Expand Down
46 changes: 39 additions & 7 deletions lib/inventree/model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import "package:inventree/api_form.dart";
import "package:inventree/l10.dart";
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
Expand Down Expand Up @@ -993,7 +995,7 @@ class InvenTreeAttachment extends InvenTreeModel {
return count(filters: filters);
}

Future<bool> uploadAttachment(File attachment, String modelType, int modelId, {String comment = "", Map<String, String> fields = const {}}) async {
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);
Expand All @@ -1002,14 +1004,9 @@ class InvenTreeAttachment extends InvenTreeModel {

if (InvenTreeAPI().supportsModernAttachments) {

if (modelType.isEmpty) {
sentryReportMessage("uploadAttachment called with empty 'modelType'");
return false;
}

url = "attachment/";
data["model_id"] = modelId.toString();
data["model_type"] = modelType;
data["model_type"] = REF_MODEL_TYPE;

} else {

Expand All @@ -1032,6 +1029,41 @@ class InvenTreeAttachment extends InvenTreeModel {
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
*/
Expand Down
30 changes: 30 additions & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,18 @@
"projectCode": "Project Code",
"@projectCode": {},

"purchaseOrderEnable": "Enable Purchase Orders",
"@purchaseOrderEnable": {},

"purchaseOrderEnableDetail": "Enable purchase order functionality",
"@purchaseOrderEnableDetail": {},

"purchaseOrderShowCamera": "Camera Shortcut",
"@purchaseOrderShowCamera": {},

"purchaseOrderShowCameraDetail": "Enable image upload shortcut on purchase order screen",
"@purchaseOrderShowCameraDetail": {},

"purchaseOrder": "Purchase Order",
"@purchaseOrder": {},

Expand All @@ -906,6 +918,9 @@
"purchaseOrderEdit": "Edit Purchase Order",
"@purchaseOrderEdit": {},

"purchaseOrderSettings": "Purchase order settings",
"@purchaseOrderSettings": {},

"purchaseOrders": "Purchase Orders",
"@purchaseOrders": {},

Expand Down Expand Up @@ -1060,6 +1075,21 @@
"salesOrders": "Sales Orders",
"@salesOrders": {},

"salesOrderEnable": "Enable Sales Orders",
"@salesOrderEnable": {},

"salesOrderEnableDetail": "Enable sales order functionality",
"@salesOrderEnableDetail": {},

"salesOrderShowCamera": "Camera Shortcut",
"@salesOrderShowCamera": {},

"salesOrderShowCameraDetail": "Enable image upload shortcut on sales order screen",
"@salesOrderShowCameraDetail": {},

"salesOrderSettings": "Sales order settings",
"@salesOrderSettings": {},

"salesOrderCreate": "New Sales Order",
"@saleOrderCreate": {},

Expand Down
8 changes: 8 additions & 0 deletions lib/preferences.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ const String INV_STOCK_SHOW_HISTORY = "stockShowHistory";
const String INV_STOCK_SHOW_TESTS = "stockShowTests";
const String INV_STOCK_CONFIRM_SCAN = "stockConfirmScan";

// Purchase order settings
const String INV_PO_ENABLE = "poEnable";
const String INV_PO_SHOW_CAMERA = "poShowCamera";

// Sales order settings
const String INV_SO_ENABLE = "soEnable";
const String INV_SO_SHOW_CAMERA = "soShowCamera";

const String INV_REPORT_ERRORS = "reportErrors";
const String INV_STRICT_HTTPS = "strictHttps";

Expand Down
10 changes: 5 additions & 5 deletions lib/settings/part_settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ class _InvenTreePartSettingsState extends State<InvenTreePartSettingsWidget> {
}

Future<void> loadSettings() async {
partShowParameters = await InvenTreeSettingsManager().getValue(INV_PART_SHOW_PARAMETERS, true) as bool;
partShowBom = await InvenTreeSettingsManager().getValue(INV_PART_SHOW_BOM, true) as bool;
stockShowHistory = await InvenTreeSettingsManager().getValue(INV_STOCK_SHOW_HISTORY, false) as bool;
stockShowTests = await InvenTreeSettingsManager().getValue(INV_STOCK_SHOW_TESTS, true) as bool;
stockConfirmScan = await InvenTreeSettingsManager().getValue(INV_STOCK_CONFIRM_SCAN, false) as bool;
partShowParameters = await InvenTreeSettingsManager().getBool(INV_PART_SHOW_PARAMETERS, true);
partShowBom = await InvenTreeSettingsManager().getBool(INV_PART_SHOW_BOM, true);
stockShowHistory = await InvenTreeSettingsManager().getBool(INV_STOCK_SHOW_HISTORY, false);
stockShowTests = await InvenTreeSettingsManager().getBool(INV_STOCK_SHOW_TESTS, true);
stockConfirmScan = await InvenTreeSettingsManager().getBool(INV_STOCK_CONFIRM_SCAN, false);

if (mounted) {
setState(() {
Expand Down
79 changes: 79 additions & 0 deletions lib/settings/purchase_order_settings.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@

import "package:flutter/material.dart";
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";

import "package:inventree/l10.dart";
import "package:inventree/preferences.dart";


class InvenTreePurchaseOrderSettingsWidget extends StatefulWidget {
@override
_InvenTreePurchaseOrderSettingsState createState() => _InvenTreePurchaseOrderSettingsState();
}


class _InvenTreePurchaseOrderSettingsState extends State<InvenTreePurchaseOrderSettingsWidget> {

_InvenTreePurchaseOrderSettingsState();

bool poEnable = true;
bool poShowCamera = true;

@override
void initState() {
super.initState();

loadSettings();
}

Future<void> loadSettings() async {
poEnable = await InvenTreeSettingsManager().getBool(INV_PO_ENABLE, true);
poShowCamera = await InvenTreeSettingsManager().getBool(INV_PO_SHOW_CAMERA, true);

if (mounted) {
setState(() {
});
}
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(L10().purchaseOrderSettings)),
body: Container(
child: ListView(
children: [
ListTile(
title: Text(L10().purchaseOrderEnable),
subtitle: Text(L10().purchaseOrderEnableDetail),
leading: Icon(TablerIcons.shopping_cart),
trailing: Switch(
value: poEnable,
onChanged: (bool value) {
InvenTreeSettingsManager().setValue(INV_PO_ENABLE, value);
setState(() {
poEnable = value;
});
},
),
),
ListTile(
title: Text(L10().purchaseOrderShowCamera),
subtitle: Text(L10().purchaseOrderShowCameraDetail),
leading: Icon(TablerIcons.camera),
trailing: Switch(
value: poShowCamera,
onChanged: (bool value) {
InvenTreeSettingsManager().setValue(INV_PO_SHOW_CAMERA, value);
setState(() {
poShowCamera = value;
});
},
),
),
]
)
)
);
}
}
79 changes: 79 additions & 0 deletions lib/settings/sales_order_settings.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@

import "package:flutter/material.dart";
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";

import "package:inventree/l10.dart";
import "package:inventree/preferences.dart";


class InvenTreeSalesOrderSettingsWidget extends StatefulWidget {
@override
_InvenTreeSalesOrderSettingsState createState() => _InvenTreeSalesOrderSettingsState();
}


class _InvenTreeSalesOrderSettingsState extends State<InvenTreeSalesOrderSettingsWidget> {

_InvenTreeSalesOrderSettingsState();

bool soEnable = true;
bool soShowCamera = true;

@override
void initState() {
super.initState();

loadSettings();
}

Future<void> loadSettings() async {
soEnable = await InvenTreeSettingsManager().getBool(INV_SO_ENABLE, true);
soShowCamera = await InvenTreeSettingsManager().getBool(INV_SO_SHOW_CAMERA, true);

if (mounted) {
setState(() {
});
}
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(L10().salesOrderSettings)),
body: Container(
child: ListView(
children: [
ListTile(
title: Text(L10().salesOrderEnable),
subtitle: Text(L10().salesOrderEnableDetail),
leading: Icon(TablerIcons.shopping_cart),
trailing: Switch(
value: soEnable,
onChanged: (bool value) {
InvenTreeSettingsManager().setValue(INV_SO_ENABLE, value);
setState(() {
soEnable = value;
});
},
),
),
ListTile(
title: Text(L10().salesOrderShowCamera),
subtitle: Text(L10().salesOrderShowCameraDetail),
leading: Icon(TablerIcons.camera),
trailing: Switch(
value: soShowCamera,
onChanged: (bool value) {
InvenTreeSettingsManager().setValue(INV_SO_SHOW_CAMERA, value);
setState(() {
soShowCamera = value;
});
},
),
),
]
)
)
);
}
}
19 changes: 18 additions & 1 deletion lib/settings/settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import "package:inventree/settings/barcode_settings.dart";
import "package:inventree/settings/home_settings.dart";
import "package:inventree/settings/select_server.dart";
import "package:inventree/settings/part_settings.dart";

import "package:inventree/settings/purchase_order_settings.dart";
import "package:inventree/settings/sales_order_settings.dart";

// InvenTree settings view
class InvenTreeSettingsWidget extends StatefulWidget {
Expand Down Expand Up @@ -86,6 +87,22 @@ class _InvenTreeSettingsState extends State<InvenTreeSettingsWidget> {
Navigator.push(context, MaterialPageRoute(builder: (context) => InvenTreePartSettingsWidget()));
}
),
ListTile(
title: Text(L10().purchaseOrder),
subtitle: Text(L10().purchaseOrderSettings),
leading: Icon(TablerIcons.shopping_cart, color: COLOR_ACTION),
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => InvenTreePurchaseOrderSettingsWidget()));
},
),
ListTile(
title: Text(L10().salesOrder),
subtitle: Text(L10().salesOrderSettings),
leading: Icon(TablerIcons.truck, color: COLOR_ACTION),
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => InvenTreeSalesOrderSettingsWidget()));
},
),
Divider(),
ListTile(
title: Text(L10().about),
Expand Down
8 changes: 6 additions & 2 deletions lib/widget/attachment_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ import "package:inventree/widget/refreshable_state.dart";
*/
class AttachmentWidget extends StatefulWidget {

const AttachmentWidget(this.attachmentClass, this.modelId, this.hasUploadPermission) : super();
const AttachmentWidget(this.attachmentClass, this.modelId, this.imagePrefix, this.hasUploadPermission) : super();

final InvenTreeAttachment attachmentClass;
final int modelId;
final bool hasUploadPermission;
final String imagePrefix;

@override
_AttachmentWidgetState createState() => _AttachmentWidgetState();
Expand All @@ -54,6 +55,10 @@ 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);
});
Expand All @@ -78,7 +83,6 @@ class _AttachmentWidgetState extends RefreshableState<AttachmentWidget> {

final bool result = await widget.attachmentClass.uploadAttachment(
file,
widget.attachmentClass.REF_MODEL_TYPE,
widget.modelId
);

Expand Down
Loading

0 comments on commit 2e798b1

Please sign in to comment.