mirror of
				https://github.com/inventree/inventree-app.git
				synced 2025-10-31 13:25:40 +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:
		| @@ -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"]; | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										175
									
								
								lib/labels.dart
									
									
									
									
									
								
							
							
						
						
									
										175
									
								
								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, | ||||||
|     String labelType, |   int instanceId, | ||||||
|     String labelQuery, |   String labelType, | ||||||
|     ) async { |   String labelQuery, | ||||||
|  | ) 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) { |  | ||||||
|  |  | ||||||
|             var data = response.asMap(); |           // Modern label printing API uses a POST request to a single API endpoint. | ||||||
|  |           await InvenTreeAPI().post( | ||||||
|             if (data.containsKey("file")) { |             "/label/print/", | ||||||
|               var label_file = (data["file"] ?? "") as String; |             body: { | ||||||
|  |               "plugin": pluginKey, | ||||||
|               // Attempt to open remote file |               "template": labelId, | ||||||
|               InvenTreeAPI().downloadFile(label_file); |               "items": [instanceId] | ||||||
|             } else { |             } | ||||||
|               showSnackIcon( |           ).then((APIResponse response) { | ||||||
|                   L10().printLabelSuccess, |             hideLoadingOverlay(); | ||||||
|                   success: true |  | ||||||
|               ); |             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; | ||||||
| } | } | ||||||
| @@ -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" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user