diff --git a/lib/api.dart b/lib/api.dart index 34e4a1c4..af67fac6 100644 --- a/lib/api.dart +++ b/lib/api.dart @@ -918,7 +918,7 @@ class InvenTreeAPI { /* * Complete an API request, and return an APIResponse object */ - Future completeRequest(HttpClientRequest request, {String? data, int? statusCode}) async { + Future completeRequest(HttpClientRequest request, {String? data, int? statusCode, bool ignoreResponse = false}) async { if (data != null && data.isNotEmpty) { @@ -955,7 +955,12 @@ class InvenTreeAPI { ); } else { - response.data = await responseToJson(_response) ?? {}; + + if (ignoreResponse) { + response.data = {}; + } else { + response.data = await responseToJson(_response) ?? {}; + } if (statusCode != null) { @@ -1042,6 +1047,31 @@ class InvenTreeAPI { return completeRequest(request); } + /* + * Perform a HTTP DELETE request + */ + Future delete(String url) async { + + HttpClientRequest? request = await apiRequest( + url, + "DELETE", + ); + + if (request == null) { + // Return an "invalid" APIResponse object + return APIResponse( + url: url, + method: "DELETE", + error: "HttpClientRequest is null", + ); + } + + return completeRequest( + request, + ignoreResponse: true, + ); + } + // Return a list of request headers Map defaultHeaders() { Map headers = {}; diff --git a/lib/inventree/model.dart b/lib/inventree/model.dart index a9eba502..116b2e08 100644 --- a/lib/inventree/model.dart +++ b/lib/inventree/model.dart @@ -222,6 +222,38 @@ class InvenTreeModel { return {}; } + /// Delete the instance on the remote server + /// Returns true if the operation was successful, else false + Future delete() async { + var response = await api.delete(url); + + if (!response.isValid() || response.data == null || (response.data is! Map)) { + + if (response.statusCode > 0) { + await sentryReportMessage( + "InvenTreeModel.delete() returned invalid response", + context: { + "url": url, + "statusCode": response.statusCode.toString(), + "data": response.data?.toString() ?? "null", + "error": response.error, + "errorDetail": response.errorDetail, + } + ); + } + + showServerError( + L10().serverError, + L10().errorDelete, + ); + + return false; + } + + // Status code should be 204 for "record deleted" + return response.statusCode == 204; + } + /* * Reload this object, by requesting data from the server */ @@ -242,7 +274,7 @@ class InvenTreeModel { "valid": response.isValid().toString(), "error": response.error, "errorDetail": response.errorDetail, - } + }, ); } @@ -466,8 +498,6 @@ class InvenTreeModel { // Provide a listing of objects at the endpoint // TODO - Static function which returns a list of objects (of this class) - // TODO - Define a "delete" function - // TODO - Define a "save" / "update" function // Override this function for each sub-class diff --git a/lib/widget/dialogs.dart b/lib/widget/dialogs.dart index 6dca2ec5..e5a1e8d9 100644 --- a/lib/widget/dialogs.dart +++ b/lib/widget/dialogs.dart @@ -8,7 +8,7 @@ import "package:font_awesome_flutter/font_awesome_flutter.dart"; import "package:inventree/l10.dart"; import "package:one_context/one_context.dart"; -Future confirmationDialog(String title, String text, {String? acceptText, String? rejectText, Function? onAccept, Function? onReject}) async { +Future confirmationDialog(String title, String text, {IconData icon = FontAwesomeIcons.questionCircle, String? acceptText, String? rejectText, Function? onAccept, Function? onReject}) async { String _accept = acceptText ?? L10().ok; String _reject = rejectText ?? L10().cancel; @@ -18,7 +18,7 @@ Future confirmationDialog(String title, String text, {String? acceptText, return AlertDialog( title: ListTile( title: Text(title), - leading: FaIcon(FontAwesomeIcons.questionCircle), + leading: FaIcon(icon), ), content: Text(text), actions: [ diff --git a/lib/widget/stock_detail.dart b/lib/widget/stock_detail.dart index 69af88a7..43242f7b 100644 --- a/lib/widget/stock_detail.dart +++ b/lib/widget/stock_detail.dart @@ -157,6 +157,27 @@ class _StockItemDisplayState extends RefreshableState { }); } + /// Delete the stock item from the database + Future _deleteItem(BuildContext context) async { + + confirmationDialog( + L10().stockItemDelete, + L10().stockItemDeleteConfirm, + icon: FontAwesomeIcons.trashAlt, + onAccept: () async { + final bool result = await item.delete(); + + if (result) { + Navigator.of(context).pop(); + showSnackIcon(L10().stockItemDeleteSuccess, success: true); + } else { + showSnackIcon(L10().stockItemDeleteFailure, success: false); + } + }, + ); + + } + /// Opens a popup dialog allowing user to select a label for printing Future _printLabel(BuildContext context) async { @@ -1003,6 +1024,19 @@ class _StockItemDisplayState extends RefreshableState { ); } + // If the user has permission to delete this stock item + if (InvenTreeAPI().checkPermission("stock", "delete")) { + tiles.add( + ListTile( + title: Text("Delete Stock Item"), + leading: FaIcon(FontAwesomeIcons.trashAlt, color: COLOR_DANGER), + onTap: () { + _deleteItem(context); + }, + ) + ); + } + return tiles; }