mirror of
https://github.com/inventree/inventree-app.git
synced 2025-04-28 05:26:47 +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:
parent
91cb24c74c
commit
3c0bca276d
@ -1,3 +1,9 @@
|
|||||||
|
### 0.15.0 - May 2024
|
||||||
|
---
|
||||||
|
|
||||||
|
- Support modern label printing API
|
||||||
|
- Updated translations
|
||||||
|
|
||||||
### 0.14.3 - April 2024
|
### 0.14.3 - April 2024
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -345,6 +345,9 @@ class InvenTreeAPI {
|
|||||||
// Does the server support "active" status on Company and SupplierPart API endpoints?
|
// Does the server support "active" status on Company and SupplierPart API endpoints?
|
||||||
bool get supportsCompanyActiveStatus => isConnected() && apiVersion >= 189;
|
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)
|
// Cached list of plugins (refreshed when we connect to the server)
|
||||||
List<InvenTreePlugin> _plugins = [];
|
List<InvenTreePlugin> _plugins = [];
|
||||||
|
|
||||||
|
@ -64,6 +64,9 @@ class InvenTreeModel {
|
|||||||
// Note: If the WEB_URL is the same (except for /api/) as URL then just leave blank
|
// Note: If the WEB_URL is the same (except for /api/) as URL then just leave blank
|
||||||
String get WEB_URL => "";
|
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
|
// Helper function to set a value in the JSON data
|
||||||
void setValue(String key, dynamic value) {
|
void setValue(String key, dynamic value) {
|
||||||
jsondata[key] = value;
|
jsondata[key] = value;
|
||||||
|
@ -196,6 +196,9 @@ class InvenTreePart extends InvenTreeModel {
|
|||||||
@override
|
@override
|
||||||
String get URL => "part/";
|
String get URL => "part/";
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get MODEL_TYPE => "part";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<String> get rolesRequired => ["part"];
|
List<String> get rolesRequired => ["part"];
|
||||||
|
|
||||||
|
@ -20,6 +20,9 @@ class InvenTreePurchaseOrder extends InvenTreeOrder {
|
|||||||
@override
|
@override
|
||||||
String get URL => "order/po/";
|
String get URL => "order/po/";
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get MODEL_TYPE => "purchaseorder";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<String> get rolesRequired => ["purchase_order"];
|
List<String> get rolesRequired => ["purchase_order"];
|
||||||
|
|
||||||
|
@ -23,6 +23,9 @@ class InvenTreeSalesOrder extends InvenTreeOrder {
|
|||||||
@override
|
@override
|
||||||
String get URL => "order/so/";
|
String get URL => "order/so/";
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get MODEL_TYPE => "salesorder";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<String> get rolesRequired => ["sales_order"];
|
List<String> get rolesRequired => ["sales_order"];
|
||||||
|
|
||||||
|
@ -142,6 +142,9 @@ class InvenTreeStockItem extends InvenTreeModel {
|
|||||||
@override
|
@override
|
||||||
String get URL => "stock/";
|
String get URL => "stock/";
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get MODEL_TYPE => "stockitem";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<String> get rolesRequired => ["stock"];
|
List<String> get rolesRequired => ["stock"];
|
||||||
|
|
||||||
@ -649,6 +652,9 @@ class InvenTreeStockLocation extends InvenTreeModel {
|
|||||||
@override
|
@override
|
||||||
String get URL => "stock/location/";
|
String get URL => "stock/location/";
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get MODEL_TYPE => "stocklocation";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<String> get rolesRequired => ["stock_location"];
|
List<String> get rolesRequired => ["stock_location"];
|
||||||
|
|
||||||
|
137
lib/labels.dart
137
lib/labels.dart
@ -6,50 +6,18 @@ import "package:inventree/api_form.dart";
|
|||||||
import "package:inventree/l10.dart";
|
import "package:inventree/l10.dart";
|
||||||
import "package:inventree/widget/snacks.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,
|
* Select a particular label, from a provided list of options,
|
||||||
* and print against the selected instances.
|
* and print against the selected instances.
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
Future<void> selectAndPrintLabel(
|
Future<void> selectAndPrintLabel(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
List<Map<String, dynamic>> labels,
|
List<Map<String, dynamic>> labels,
|
||||||
|
int instanceId,
|
||||||
String labelType,
|
String labelType,
|
||||||
String labelQuery,
|
String labelQuery,
|
||||||
) async {
|
) async {
|
||||||
|
|
||||||
if (!InvenTreeAPI().isConnected()) {
|
if (!InvenTreeAPI().isConnected()) {
|
||||||
return;
|
return;
|
||||||
@ -91,7 +59,7 @@ Future<void> selectAndPrintLabel(
|
|||||||
for (var plugin in plugins) {
|
for (var plugin in plugins) {
|
||||||
plugin_options.add({
|
plugin_options.add({
|
||||||
"display_name": plugin.humanName,
|
"display_name": plugin.humanName,
|
||||||
"value": plugin.key
|
"value": InvenTreeAPI().supportsModenLabelPrinting ? plugin.pk : plugin.key
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,38 +92,113 @@ Future<void> selectAndPrintLabel(
|
|||||||
icon: FontAwesomeIcons.print,
|
icon: FontAwesomeIcons.print,
|
||||||
onSuccess: (Map<String, dynamic> data) async {
|
onSuccess: (Map<String, dynamic> data) async {
|
||||||
int labelId = (data["label"] ?? -1) as int;
|
int labelId = (data["label"] ?? -1) as int;
|
||||||
String pluginKey = (data["plugin"] ?? "") as String;
|
var pluginKey = data["plugin"];
|
||||||
|
|
||||||
if (labelId != -1 && pluginKey.isNotEmpty) {
|
bool result = false;
|
||||||
String url = "/label/${labelType}/${labelId}/print/?${labelQuery}&plugin=${pluginKey}";
|
|
||||||
|
if (labelId != -1 && pluginKey != null) {
|
||||||
|
|
||||||
showLoadingOverlay(context);
|
showLoadingOverlay(context);
|
||||||
|
|
||||||
InvenTreeAPI().get(url).then((APIResponse response) {
|
if (InvenTreeAPI().supportsModenLabelPrinting) {
|
||||||
hideLoadingOverlay();
|
|
||||||
if (response.isValid() && response.statusCode == 200) {
|
|
||||||
|
|
||||||
|
// 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();
|
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 {
|
||||||
|
// 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")) {
|
if (data.containsKey("file")) {
|
||||||
var label_file = (data["file"] ?? "") as String;
|
var label_file = (data["file"] ?? "") as String;
|
||||||
|
|
||||||
// Attempt to open remote file
|
// Attempt to open remote file
|
||||||
InvenTreeAPI().downloadFile(label_file);
|
InvenTreeAPI().downloadFile(label_file);
|
||||||
} else {
|
result = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result) {
|
||||||
showSnackIcon(
|
showSnackIcon(
|
||||||
L10().printLabelSuccess,
|
L10().printLabelSuccess,
|
||||||
success: true
|
success: true
|
||||||
);
|
);
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
showSnackIcon(
|
showSnackIcon(
|
||||||
L10().printLabelFailure,
|
L10().printLabelFailure,
|
||||||
success: false,
|
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;
|
||||||
}
|
}
|
@ -194,7 +194,7 @@ class InvenTreeAboutWidget extends StatelessWidget {
|
|||||||
tiles.add(
|
tiles.add(
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(L10().documentation),
|
title: Text(L10().documentation),
|
||||||
subtitle: Text("https://docs.inventree.org"),
|
subtitle: Text("https://docs.inventree.org/app"),
|
||||||
leading: FaIcon(FontAwesomeIcons.book, color: COLOR_ACTION),
|
leading: FaIcon(FontAwesomeIcons.book, color: COLOR_ACTION),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_openDocs();
|
_openDocs();
|
||||||
|
@ -129,6 +129,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
|||||||
selectAndPrintLabel(
|
selectAndPrintLabel(
|
||||||
context,
|
context,
|
||||||
labels,
|
labels,
|
||||||
|
widget.part.pk,
|
||||||
"part",
|
"part",
|
||||||
"part=${widget.part.pk}"
|
"part=${widget.part.pk}"
|
||||||
);
|
);
|
||||||
@ -248,9 +249,16 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
|||||||
allowLabelPrinting &= api.supportsMixin("labels");
|
allowLabelPrinting &= api.supportsMixin("labels");
|
||||||
|
|
||||||
if (allowLabelPrinting) {
|
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) {
|
if (mounted) {
|
||||||
|
@ -193,6 +193,7 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
|
|||||||
selectAndPrintLabel(
|
selectAndPrintLabel(
|
||||||
context,
|
context,
|
||||||
labels,
|
labels,
|
||||||
|
widget.location!.pk,
|
||||||
"location",
|
"location",
|
||||||
"location=${widget.location!.pk}"
|
"location=${widget.location!.pk}"
|
||||||
);
|
);
|
||||||
@ -247,9 +248,16 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
|
|||||||
if (allowLabelPrinting) {
|
if (allowLabelPrinting) {
|
||||||
|
|
||||||
if (widget.location != null) {
|
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()
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,6 +138,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
|||||||
selectAndPrintLabel(
|
selectAndPrintLabel(
|
||||||
context,
|
context,
|
||||||
labels,
|
labels,
|
||||||
|
widget.item.pk,
|
||||||
"stock",
|
"stock",
|
||||||
"item=${widget.item.pk}"
|
"item=${widget.item.pk}"
|
||||||
);
|
);
|
||||||
@ -264,10 +265,17 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
|||||||
|
|
||||||
// Request information on labels available for this stock item
|
// Request information on labels available for this stock item
|
||||||
if (allowLabelPrinting) {
|
if (allowLabelPrinting) {
|
||||||
|
|
||||||
|
String model_type = api.supportsModenLabelPrinting ? InvenTreeStockLocation().MODEL_TYPE : "stock";
|
||||||
|
String item_key = api.supportsModenLabelPrinting ? "items" : "item";
|
||||||
|
|
||||||
// Clear the existing labels list
|
// Clear the existing labels list
|
||||||
_labels = await getLabelTemplates("stock", {
|
_labels = await getLabelTemplates(
|
||||||
"item": widget.item.pk.toString()
|
model_type,
|
||||||
});
|
{
|
||||||
|
item_key: widget.item.pk.toString()
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
name: inventree
|
name: inventree
|
||||||
description: InvenTree stock management
|
description: InvenTree stock management
|
||||||
|
|
||||||
version: 0.14.3+81
|
version: 0.15.0+82
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.19.5 <3.13.0"
|
sdk: ">=2.19.5 <3.13.0"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user