2
0
mirror of https://github.com/inventree/inventree-app.git synced 2025-04-28 05:26:47 +00:00

Label print updates (#399)

* Allow download of printed label

* Add setting for controlling label printing

* Control display of label printing via setting

* Refactor label printing functionality

- Move to helpers.dart
- Will be used for other label types also

* Factor out request for label templates

* Add label printing support for part

* Support label printing for stock location

* update release notes
This commit is contained in:
Oliver 2023-07-16 00:51:11 +10:00 committed by GitHub
parent d78affc1cb
commit 443e6e856c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 284 additions and 134 deletions

View File

@ -1,3 +1,9 @@
### 0.12.6 - July 2023
---
- Enable label printing for stock locations
- Enable label printing for parts
### 0.12.5 - July 2023
---

View File

@ -9,6 +9,7 @@
import "dart:io";
import "package:currency_formatter/currency_formatter.dart";
import "package:one_context/one_context.dart";
import "package:url_launcher/url_launcher.dart";
import "package:audioplayers/audioplayers.dart";
@ -133,3 +134,4 @@ String renderCurrency(double? amount, String currency, {int decimals = 2}) {
return value;
}

View File

@ -533,6 +533,12 @@
"keywords": "Keywords",
"@keywords": {},
"labelPrinting": "Label Printing",
"@labelPrinting": {},
"labelPrintingDetail": "Enable label printing",
"@labelPrintingDetail": {},
"labelTemplate": "Label Template",
"@labelTemplate": {},

155
lib/labels.dart Normal file
View File

@ -0,0 +1,155 @@
import "package:flutter/cupertino.dart";
import "package:font_awesome_flutter/font_awesome_flutter.dart";
import "package:inventree/api.dart";
import "package:inventree/widget/progress.dart";
import "package:inventree/api_form.dart";
import "package:inventree/l10.dart";
import "package:inventree/widget/snacks.dart";
/*
* Discover which label templates are available for a given item
*/
Future<List<Map<String, dynamic>>> getLabelTemplates(
String labelType,
Map<String, String> data,
) async {
if (!InvenTreeAPI().isConnected() || !InvenTreeAPI().supportsMixin("labels")) {
return [];
}
// Filter by active plugins
data["enabled"] = "true";
List<Map<String, dynamic>> labels = [];
await InvenTreeAPI().get(
"/label/${labelType}/",
params: data,
).then((APIResponse response) {
if (response.isValid() && response.statusCode == 200) {
for (var label in response.resultsList()) {
if (label is Map<String, dynamic>) {
labels.add(label);
}
}
}
});
return labels;
}
/*
* Select a particular label, from a provided list of options,
* and print against the selected instances.
*/
Future<void> selectAndPrintLabel(
BuildContext context,
List<Map<String, dynamic>> labels,
String labelType,
String labelQuery,
) async {
if (!InvenTreeAPI().isConnected()) {
return;
}
// Find a list of available plugins which support label printing
var plugins = InvenTreeAPI().getPlugins(mixin: "labels");
dynamic initial_label;
dynamic initial_plugin;
List<Map<String, dynamic>> label_options = [];
List<Map<String, dynamic>> plugin_options = [];
// Construct list of available label templates
for (var label in labels) {
String display_name = (label["description"] ?? "").toString();
int pk = (label["pk"] ?? -1) as int;
if (display_name.isNotEmpty && pk > 0) {
label_options.add({
"display_name": display_name,
"value": pk,
});
}
}
if (label_options.length == 1) {
initial_label = label_options.first["value"];
}
// Construct list of available plugins
for (var plugin in plugins) {
plugin_options.add({
"display_name": plugin.humanName,
"value": plugin.key
});
}
if (plugin_options.length == 1) {
initial_plugin = plugin_options.first["value"];
}
Map<String, dynamic> fields = {
"label": {
"label": L10().labelTemplate,
"type": "choice",
"value": initial_label,
"choices": label_options,
"required": true,
},
"plugin": {
"label": L10().pluginPrinter,
"type": "choice",
"value": initial_plugin,
"choices": plugin_options,
"required": true,
}
};
launchApiForm(
context,
L10().printLabel,
"",
fields,
icon: FontAwesomeIcons.print,
onSuccess: (Map<String, dynamic> data) async {
int labelId = (data["label"] ?? -1) as int;
String pluginKey = (data["plugin"] ?? "") as String;
if (labelId != -1 && pluginKey.isNotEmpty) {
String url = "/label/${labelType}/${labelId}/print/?${labelQuery}&plugin=${pluginKey}";
showLoadingOverlay(context);
InvenTreeAPI().get(url).then((APIResponse response) {
hideLoadingOverlay();
if (response.isValid() && response.statusCode == 200) {
var data = response.asMap();
if (data.containsKey("file")) {
var label_file = (data["file"] ?? "") as String;
// Attempt to open remote file
InvenTreeAPI().downloadFile(label_file);
} else {
showSnackIcon(
L10().printLabelSuccess,
success: true
);
}
} else {
showSnackIcon(
L10().printLabelFailure,
success: false,
);
}
});
}
},
);
}

View File

@ -25,6 +25,8 @@ const int SCREEN_ORIENTATION_LANDSCAPE = 2;
const String INV_SOUNDS_BARCODE = "barcodeSounds";
const String INV_SOUNDS_SERVER = "serverSounds";
const String INV_ENABLE_LABEL_PRINTING = "enableLabelPrinting";
// Part settings
const String INV_PART_SHOW_PARAMETERS = "partShowParameters";
const String INV_PART_SHOW_BOM = "partShowBom";

View File

@ -1,18 +1,18 @@
import "package:flutter/material.dart";
import "package:one_context/one_context.dart";
import "package:adaptive_theme/adaptive_theme.dart";
import "package:font_awesome_flutter/font_awesome_flutter.dart";
import "package:flutter_localized_locales/flutter_localized_locales.dart";
import "package:inventree/app_colors.dart";
import "package:inventree/widget/dialogs.dart";
import "package:one_context/one_context.dart";
import "package:inventree/app_colors.dart";
import "package:inventree/api_form.dart";
import "package:inventree/l10.dart";
import "package:inventree/l10n/supported_locales.dart";
import "package:inventree/main.dart";
import "package:inventree/preferences.dart";
import "package:inventree/widget/dialogs.dart";
import "package:inventree/widget/progress.dart";
@ -33,7 +33,7 @@ class _InvenTreeAppSettingsState extends State<InvenTreeAppSettingsWidget> {
bool reportErrors = true;
bool strictHttps = false;
bool enableLabelPrinting = true;
bool darkMode = false;
int screenOrientation = SCREEN_ORIENTATION_SYSTEM;
@ -56,6 +56,7 @@ class _InvenTreeAppSettingsState extends State<InvenTreeAppSettingsWidget> {
reportErrors = await InvenTreeSettingsManager().getValue(INV_REPORT_ERRORS, true) as bool;
strictHttps = await InvenTreeSettingsManager().getValue(INV_STRICT_HTTPS, false) as bool;
screenOrientation = await InvenTreeSettingsManager().getValue(INV_SCREEN_ORIENTATION, SCREEN_ORIENTATION_SYSTEM) as int;
enableLabelPrinting = await InvenTreeSettingsManager().getValue(INV_ENABLE_LABEL_PRINTING, true) as bool;
darkMode = AdaptiveTheme.of(context).mode.isDark;
@ -218,6 +219,20 @@ class _InvenTreeAppSettingsState extends State<InvenTreeAppSettingsWidget> {
);
},
),
ListTile(
title: Text(L10().labelPrinting),
subtitle: Text(L10().labelPrintingDetail),
leading: FaIcon(FontAwesomeIcons.print),
trailing: Switch(
value: enableLabelPrinting,
onChanged: (bool value) {
InvenTreeSettingsManager().setValue(INV_ENABLE_LABEL_PRINTING, value);
setState(() {
enableLabelPrinting = value;
});
}
),
),
ListTile(
title: Text(L10().strictHttps),
subtitle: Text(L10().strictHttpsDetails),

View File

@ -13,8 +13,8 @@ import "package:inventree/settings/login.dart";
import "package:inventree/settings/part_settings.dart";
class InvenTreeSettingsWidget extends StatefulWidget {
// InvenTree settings view
class InvenTreeSettingsWidget extends StatefulWidget {
@override
_InvenTreeSettingsState createState() => _InvenTreeSettingsState();

View File

@ -8,6 +8,7 @@ import "package:inventree/barcode/barcode.dart";
import "package:inventree/l10.dart";
import "package:inventree/inventree/stock.dart";
import "package:inventree/preferences.dart";
import "package:inventree/widget/location_list.dart";
import "package:inventree/widget/progress.dart";
@ -15,6 +16,7 @@ import "package:inventree/widget/refreshable_state.dart";
import "package:inventree/widget/snacks.dart";
import "package:inventree/widget/stock_detail.dart";
import "package:inventree/widget/stock_list.dart";
import "package:inventree/labels.dart";
/*
@ -38,6 +40,10 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
final InvenTreeStockLocation? location;
bool allowLabelPrinting = true;
List<Map<String, dynamic>> labels = [];
@override
String getAppBarTitle() {
return L10().stockLocation;
@ -163,6 +169,23 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
);
}
if (widget.location != null && allowLabelPrinting && labels.isNotEmpty) {
actions.add(
SpeedDialChild(
child: FaIcon(FontAwesomeIcons.print),
label: L10().printLabel,
onTap: () async {
selectAndPrintLabel(
context,
labels,
"location",
"location=${widget.location!.pk}"
);
}
)
);
}
return actions;
}
@ -202,6 +225,19 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
}
}
allowLabelPrinting = await InvenTreeSettingsManager().getBool(INV_ENABLE_LABEL_PRINTING, true);
allowLabelPrinting &= api.getPlugins(mixin: "labels").isNotEmpty;
if (allowLabelPrinting) {
labels.clear();
if (widget.location != null) {
labels = await getLabelTemplates("location", {
"location": widget.location!.pk.toString()
});
}
}
if (mounted) {
setState(() {});
}

View File

@ -11,6 +11,7 @@ import "package:inventree/helpers.dart";
import "package:inventree/inventree/bom.dart";
import "package:inventree/inventree/part.dart";
import "package:inventree/inventree/stock.dart";
import "package:inventree/labels.dart";
import "package:inventree/preferences.dart";
import "package:inventree/widget/attachment_widget.dart";
@ -54,17 +55,16 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
int parameterCount = 0;
bool showParameters = false;
bool showBom = false;
bool allowLabelPrinting = true;
int attachmentCount = 0;
int bomCount = 0;
int usedInCount = 0;
int variantCount = 0;
List<Map<String, dynamic>> labels = [];
@override
String getAppBarTitle() => L10().partDetails;
@ -121,6 +121,23 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
);
}
if (allowLabelPrinting && labels.isNotEmpty) {
actions.add(
SpeedDialChild(
child: FaIcon(FontAwesomeIcons.print),
label: L10().printLabel,
onTap: () async {
selectAndPrintLabel(
context,
labels,
"part",
"part=${widget.part.pk}"
);
}
)
);
}
return actions;
}
@ -226,6 +243,16 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
});
}
});
allowLabelPrinting = await InvenTreeSettingsManager().getBool(INV_ENABLE_LABEL_PRINTING, true);
allowLabelPrinting &= api.getPlugins(mixin: "labels").isNotEmpty;
if (allowLabelPrinting) {
labels.clear();
labels = await getLabelTemplates("part", {
"part": widget.part.pk.toString(),
});
}
}
void _editPartDialog(BuildContext context) {

View File

@ -9,6 +9,7 @@ import "package:inventree/helpers.dart";
import "package:inventree/l10.dart";
import "package:inventree/api.dart";
import "package:inventree/api_form.dart";
import "package:inventree/labels.dart";
import "package:inventree/preferences.dart";
import "package:inventree/inventree/company.dart";
@ -127,13 +128,18 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
);
}
if (labels.isNotEmpty) {
if (allowLabelPrinting && labels.isNotEmpty) {
actions.add(
SpeedDialChild(
child: FaIcon(FontAwesomeIcons.print),
label: L10().printLabel,
onTap: () {
_printLabel(context);
onTap: () async {
selectAndPrintLabel(
context,
labels,
"stock",
"item=${widget.item.pk}"
);
}
)
);
@ -198,9 +204,10 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
int attachmentCount = 0;
bool allowLabelPrinting = true;
@override
Future<void> onBuild(BuildContext context) async {
// Load part data if not already loaded
if (part == null) {
refresh(context);
@ -209,9 +216,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
@override
Future<void> request(BuildContext context) async {
await api.StockStatus.load();
stockShowHistory = await InvenTreeSettingsManager().getValue(INV_STOCK_SHOW_HISTORY, false) as bool;
stockShowTests = await InvenTreeSettingsManager().getValue(INV_STOCK_SHOW_TESTS, true) as bool;
@ -254,42 +259,19 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
}
});
// Request information on labels available for this stock item
if (InvenTreeAPI().pluginsEnabled()) {
_getLabels();
}
}
// Determine if label printing is supported
allowLabelPrinting = await InvenTreeSettingsManager().getBool(INV_ENABLE_LABEL_PRINTING, true);
allowLabelPrinting &= api.getPlugins(mixin: "labels").isNotEmpty;
Future <void> _getLabels() async {
// Request information on labels available for this stock item
if (allowLabelPrinting) {
// Clear the existing labels list
labels.clear();
// If the server does not support label printing, don't bother!
if (!InvenTreeAPI().supportsMixin("labels")) {
return;
}
InvenTreeAPI().get(
"/label/stock/",
params: {
"enabled": "true",
"item": "${widget.item.pk}",
},
).then((APIResponse response) {
if (response.isValid() && response.statusCode == 200) {
for (var label in response.resultsList()) {
if (label is Map<String, dynamic>) {
labels.add(label);
}
}
if (mounted) {
setState(() {});
}
}
labels = await getLabelTemplates("stock", {
"item": widget.item.pk.toString()
});
}
}
/// Delete the stock item from the database
Future<void> _deleteItem(BuildContext context) async {
@ -314,87 +296,6 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
}
/// Opens a popup dialog allowing user to select a label for printing
Future <void> _printLabel(BuildContext context) async {
var plugins = InvenTreeAPI().getPlugins(mixin: "labels");
dynamic initial_label;
dynamic initial_plugin;
List<Map<String, dynamic>> label_options = [];
List<Map<String, dynamic>> plugin_options = [];
for (var label in labels) {
label_options.add({
"display_name": label["description"],
"value": label["pk"],
});
}
for (var plugin in plugins) {
plugin_options.add({
"display_name": plugin.humanName,
"value": plugin.key,
});
}
if (labels.length == 1) {
initial_label = labels.first["pk"];
}
if (plugins.length == 1) {
initial_plugin = plugins.first.key;
}
Map<String, dynamic> fields = {
"label": {
"label": L10().labelTemplate,
"type": "choice",
"value": initial_label,
"choices": label_options,
"required": true,
},
"plugin": {
"label": L10().pluginPrinter,
"type": "choice",
"value": initial_plugin,
"choices": plugin_options,
"required": true,
}
};
launchApiForm(
context,
L10().printLabel,
"",
fields,
icon: FontAwesomeIcons.print,
onSuccess: (Map<String, dynamic> data) async {
int labelId = (data["label"] ?? -1) as int;
String pluginKey = (data["plugin"] ?? "") as String;
if (labelId != -1 && pluginKey.isNotEmpty) {
String url = "/label/stock/${labelId}/print/?item=${widget.item.pk}&plugin=${pluginKey}";
InvenTreeAPI().get(url).then((APIResponse response) {
if (response.isValid() && response.statusCode == 200) {
showSnackIcon(
L10().printLabelSuccess,
success: true
);
} else {
showSnackIcon(
L10().printLabelFailure,
success: false,
);
}
});
}
},
);
}
Future <void> _editStockItem(BuildContext context) async {
var fields = InvenTreeStockItem().formFields();