2
0
mirror of https://github.com/inventree/inventree-app.git synced 2025-04-27 21:16:48 +00:00

Label printing fix (#489)

* Add check for modern label printing interface

* Update getLabelTemplates

* Fix typo

* Refactor / simplify

* Revert parameter type

* Update version number and release notes

* Refactor label printing function

- Will require some cleanup in the future
- Still needs testing

* Fix for modern printing

* Typo fix
This commit is contained in:
Oliver 2024-05-12 20:41:02 +10:00 committed by GitHub
parent 91cb24c74c
commit 3c0bca276d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 171 additions and 77 deletions

View File

@ -1,3 +1,9 @@
### 0.15.0 - May 2024
---
- Support modern label printing API
- Updated translations
### 0.14.3 - April 2024
---

View File

@ -345,6 +345,9 @@ class InvenTreeAPI {
// Does the server support "active" status on Company and SupplierPart API endpoints?
bool get supportsCompanyActiveStatus => isConnected() && apiVersion >= 189;
// Does the server support the "modern" (consolidated) label printing API?
bool get supportsModenLabelPrinting => isConnected() && apiVersion >= 197;
// Cached list of plugins (refreshed when we connect to the server)
List<InvenTreePlugin> _plugins = [];

View File

@ -64,6 +64,9 @@ class InvenTreeModel {
// Note: If the WEB_URL is the same (except for /api/) as URL then just leave blank
String get WEB_URL => "";
// Return the "model type" of this model
String get MODEL_TYPE => "";
// Helper function to set a value in the JSON data
void setValue(String key, dynamic value) {
jsondata[key] = value;

View File

@ -196,6 +196,9 @@ class InvenTreePart extends InvenTreeModel {
@override
String get URL => "part/";
@override
String get MODEL_TYPE => "part";
@override
List<String> get rolesRequired => ["part"];

View File

@ -20,6 +20,9 @@ class InvenTreePurchaseOrder extends InvenTreeOrder {
@override
String get URL => "order/po/";
@override
String get MODEL_TYPE => "purchaseorder";
@override
List<String> get rolesRequired => ["purchase_order"];

View File

@ -23,6 +23,9 @@ class InvenTreeSalesOrder extends InvenTreeOrder {
@override
String get URL => "order/so/";
@override
String get MODEL_TYPE => "salesorder";
@override
List<String> get rolesRequired => ["sales_order"];

View File

@ -142,6 +142,9 @@ class InvenTreeStockItem extends InvenTreeModel {
@override
String get URL => "stock/";
@override
String get MODEL_TYPE => "stockitem";
@override
List<String> get rolesRequired => ["stock"];
@ -649,6 +652,9 @@ class InvenTreeStockLocation extends InvenTreeModel {
@override
String get URL => "stock/location/";
@override
String get MODEL_TYPE => "stocklocation";
@override
List<String> get rolesRequired => ["stock_location"];

View File

@ -6,50 +6,18 @@ 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 {
BuildContext context,
List<Map<String, dynamic>> labels,
int instanceId,
String labelType,
String labelQuery,
) async {
if (!InvenTreeAPI().isConnected()) {
return;
@ -91,7 +59,7 @@ Future<void> selectAndPrintLabel(
for (var plugin in plugins) {
plugin_options.add({
"display_name": plugin.humanName,
"value": plugin.key
"value": InvenTreeAPI().supportsModenLabelPrinting ? plugin.pk : plugin.key
});
}
@ -124,38 +92,113 @@ Future<void> selectAndPrintLabel(
icon: FontAwesomeIcons.print,
onSuccess: (Map<String, dynamic> data) async {
int labelId = (data["label"] ?? -1) as int;
String pluginKey = (data["plugin"] ?? "") as String;
var pluginKey = data["plugin"];
if (labelId != -1 && pluginKey.isNotEmpty) {
String url = "/label/${labelType}/${labelId}/print/?${labelQuery}&plugin=${pluginKey}";
bool result = false;
if (labelId != -1 && pluginKey != null) {
showLoadingOverlay(context);
InvenTreeAPI().get(url).then((APIResponse response) {
hideLoadingOverlay();
if (response.isValid() && response.statusCode == 200) {
if (InvenTreeAPI().supportsModenLabelPrinting) {
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
);
// Modern label printing API uses a POST request to a single API endpoint.
await InvenTreeAPI().post(
"/label/print/",
body: {
"plugin": pluginKey,
"template": labelId,
"items": [instanceId]
}
).then((APIResponse response) {
hideLoadingOverlay();
if (response.isValid() && response.statusCode >= 200 &&
response.statusCode <= 201) {
var data = response.asMap();
if (data.containsKey("output")) {
var label_file = (data["output"] ?? "") as String;
// Attempt to open generated file
InvenTreeAPI().downloadFile(label_file);
result = true;
}
}
} else {
showSnackIcon(
L10().printLabelFailure,
success: false,
);
}
});
} else {
// Legacy label printing API
// Uses a GET request to a specially formed URL which depends on the parameters
String url = "/label/${labelType}/${labelId}/print/?${labelQuery}&plugin=${pluginKey}";
await 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);
result = true;
}
}
});
}
},
);
if (result) {
showSnackIcon(
L10().printLabelSuccess,
success: true
);
} else {
showSnackIcon(
L10().printLabelFailure,
success: false,
);
}
}
});
}
/*
* 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";
String url = "/label/template/";
if (InvenTreeAPI().supportsModenLabelPrinting) {
data["model_type"] = labelType;
} else {
// Legacy label printing API endpoint
url = "/label/${labelType}/";
}
List<Map<String, dynamic>> labels = [];
await InvenTreeAPI().get(
url,
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;
}

View File

@ -194,7 +194,7 @@ class InvenTreeAboutWidget extends StatelessWidget {
tiles.add(
ListTile(
title: Text(L10().documentation),
subtitle: Text("https://docs.inventree.org"),
subtitle: Text("https://docs.inventree.org/app"),
leading: FaIcon(FontAwesomeIcons.book, color: COLOR_ACTION),
onTap: () {
_openDocs();

View File

@ -129,6 +129,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
selectAndPrintLabel(
context,
labels,
widget.part.pk,
"part",
"part=${widget.part.pk}"
);
@ -248,9 +249,16 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
allowLabelPrinting &= api.supportsMixin("labels");
if (allowLabelPrinting) {
_labels = await getLabelTemplates("part", {
"part": widget.part.pk.toString(),
});
String model_type = api.supportsModenLabelPrinting ? InvenTreePart().MODEL_TYPE : "part";
String item_key = api.supportsModenLabelPrinting ? "items" : "part";
_labels = await getLabelTemplates(
model_type,
{
item_key: widget.part.pk.toString()
}
);
}
if (mounted) {

View File

@ -193,6 +193,7 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
selectAndPrintLabel(
context,
labels,
widget.location!.pk,
"location",
"location=${widget.location!.pk}"
);
@ -247,9 +248,16 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
if (allowLabelPrinting) {
if (widget.location != null) {
_labels = await getLabelTemplates("location", {
"location": widget.location!.pk.toString()
});
String model_type = api.supportsModenLabelPrinting ? InvenTreeStockLocation().MODEL_TYPE : "location";
String item_key = api.supportsModenLabelPrinting ? "items" : "location";
_labels = await getLabelTemplates(
model_type,
{
item_key: widget.location!.pk.toString()
}
);
}
}

View File

@ -138,6 +138,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
selectAndPrintLabel(
context,
labels,
widget.item.pk,
"stock",
"item=${widget.item.pk}"
);
@ -264,10 +265,17 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
// Request information on labels available for this stock item
if (allowLabelPrinting) {
String model_type = api.supportsModenLabelPrinting ? InvenTreeStockLocation().MODEL_TYPE : "stock";
String item_key = api.supportsModenLabelPrinting ? "items" : "item";
// Clear the existing labels list
_labels = await getLabelTemplates("stock", {
"item": widget.item.pk.toString()
});
_labels = await getLabelTemplates(
model_type,
{
item_key: widget.item.pk.toString()
}
);
}
if (mounted) {

View File

@ -1,7 +1,7 @@
name: inventree
description: InvenTree stock management
version: 0.14.3+81
version: 0.15.0+82
environment:
sdk: ">=2.19.5 <3.13.0"