diff --git a/assets/release_notes.md b/assets/release_notes.md
index 7ada37f6..a2983274 100644
--- a/assets/release_notes.md
+++ b/assets/release_notes.md
@@ -4,6 +4,7 @@
 - Fix background image transparency for dark mode
 - Fix link to Bill of Materials from Part screen
 - Improvements to supplier part detail screen
+- Add "notes" field to more models
 
 
 ### 0.11.4 - April 2023
diff --git a/lib/api.dart b/lib/api.dart
index fb883fa1..0950eeb1 100644
--- a/lib/api.dart
+++ b/lib/api.dart
@@ -650,16 +650,20 @@ class InvenTreeAPI {
    * e.g. "part", "change"
    */
   bool checkPermission(String role, String permission) {
+
     // If we do not have enough information, assume permission is allowed
     if (roles.isEmpty) {
+      debug("checkPermission - no roles defined!");
       return true;
     }
 
     if (!roles.containsKey(role)) {
+      debug("checkPermission - role '$role' not found!");
       return true;
     }
 
     if (roles[role] == null) {
+      debug("checkPermission - role '$role' is null!");
       return true;
     }
 
diff --git a/lib/inventree/company.dart b/lib/inventree/company.dart
index ae711b7b..7af1229a 100644
--- a/lib/inventree/company.dart
+++ b/lib/inventree/company.dart
@@ -18,6 +18,9 @@ class InvenTreeCompany extends InvenTreeModel {
   @override
   String get URL => "company/";
 
+  @override
+  List<String> get rolesRequired => ["purchase_order", "sales_order", "return_order"];
+
   @override
   Map<String, dynamic> formFields() {
     return {
@@ -118,6 +121,9 @@ class InvenTreeSupplierPart extends InvenTreeModel {
   @override
   String get URL => "company/part/";
 
+  @override
+  List<String> get rolesRequired => ["part", "purchase_order"];
+
   @override
   Map<String, dynamic> formFields() {
     return {
diff --git a/lib/inventree/model.dart b/lib/inventree/model.dart
index 7c75129a..576329fd 100644
--- a/lib/inventree/model.dart
+++ b/lib/inventree/model.dart
@@ -10,6 +10,7 @@ import "package:inventree/api.dart";
 import "package:inventree/api_form.dart";
 import "package:inventree/fa_icon_mapping.dart";
 import "package:inventree/l10.dart";
+import "package:inventree/helpers.dart";
 import "package:inventree/inventree/sentry.dart";
 import "package:inventree/widget/dialogs.dart";
 
@@ -78,7 +79,63 @@ class InvenTreeModel {
     } else {
       return "";
     }
+  }
 
+  /* Return a list of roles which may be required for this model
+   * If multiple roles are required, *any* role which passes the check is sufficient
+   */
+  List<String> get rolesRequired {
+    // Default implementation should not be called
+    debug("rolesRequired() not implemented for model ${URL} - returning empty list");
+    return [];
+  }
+
+  // Test if the user can "edit" this model
+  bool get canEdit {
+    for (String role in rolesRequired) {
+      if (InvenTreeAPI().checkPermission(role, "change")) {
+        return true;
+      }
+    }
+
+    // Fallback
+    return false;
+  }
+
+  // Test if the user can "create" this model
+  bool get canCreate {
+    for (String role in rolesRequired) {
+      if (InvenTreeAPI().checkPermission(role, "add")) {
+        return true;
+      }
+    }
+
+    // Fallback
+    return false;
+  }
+
+  // Test if the user can "delete" this model
+  bool get canDelete {
+    for (String role in rolesRequired) {
+      if (InvenTreeAPI().checkPermission(role, "delete")) {
+        return true;
+      }
+    }
+
+    // Fallback
+    return false;
+  }
+
+  // Test if the user can "view" this model
+  bool get canView {
+    for (String role in rolesRequired) {
+      if (InvenTreeAPI().checkPermission(role, "view")) {
+        return true;
+      }
+    }
+
+    // Fallback
+    return false;
   }
 
   // Fields for editing / creating this model
diff --git a/lib/inventree/part.dart b/lib/inventree/part.dart
index 339382b2..e2ef2d91 100644
--- a/lib/inventree/part.dart
+++ b/lib/inventree/part.dart
@@ -23,6 +23,9 @@ class InvenTreePartCategory extends InvenTreeModel {
   @override
   String get URL => "part/category/";
 
+  @override
+  List<String> get rolesRequired => ["part_category"];
+
   @override
   Map<String, dynamic> formFields() {
 
@@ -182,6 +185,9 @@ class InvenTreePart extends InvenTreeModel {
   @override
   String get URL => "part/";
 
+  @override
+  List<String> get rolesRequired => ["part"];
+
   @override
   Map<String, dynamic> formFields() {
     return {
diff --git a/lib/inventree/purchase_order.dart b/lib/inventree/purchase_order.dart
index abfd0ba3..53a297a0 100644
--- a/lib/inventree/purchase_order.dart
+++ b/lib/inventree/purchase_order.dart
@@ -18,6 +18,9 @@ class InvenTreePurchaseOrder extends InvenTreeModel {
   @override
   String get URL => "order/po/";
 
+  @override
+  List<String> get rolesRequired => ["purchase_order"];
+
   String get receive_url => "${url}receive/";
 
   @override
diff --git a/lib/inventree/stock.dart b/lib/inventree/stock.dart
index ab6cc146..cd22e3fb 100644
--- a/lib/inventree/stock.dart
+++ b/lib/inventree/stock.dart
@@ -20,6 +20,9 @@ class InvenTreeStockItemTestResult extends InvenTreeModel {
   @override
   String get URL => "stock/test/";
 
+  @override
+  List<String> get rolesRequired => ["stock"];
+
   @override
   Map<String, dynamic> formFields() {
     return {
@@ -134,6 +137,9 @@ class InvenTreeStockItem extends InvenTreeModel {
   @override
   String get URL => "stock/";
 
+  @override
+  List<String> get rolesRequired => ["stock"];
+
   // URLs for performing stock actions
   static String transferStockUrl() => "stock/transfer/";
 
@@ -611,6 +617,9 @@ class InvenTreeStockLocation extends InvenTreeModel {
   @override
   String get URL => "stock/location/";
 
+  @override
+  List<String> get rolesRequired => ["stock_location"];
+
   String get pathstring => (jsondata["pathstring"] ?? "") as String;
 
   @override
diff --git a/lib/widget/category_display.dart b/lib/widget/category_display.dart
index 404150d8..31ead036 100644
--- a/lib/widget/category_display.dart
+++ b/lib/widget/category_display.dart
@@ -40,7 +40,7 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
     List<Widget> actions = [];
 
     if (widget.category != null) {
-      if (api.checkPermission("part_category", "change")) {
+      if (InvenTreePartCategory().canEdit) {
         actions.add(
           IconButton(
             icon:  Icon(Icons.edit_square),
@@ -60,7 +60,7 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
   List<SpeedDialChild> actionButtons(BuildContext context) {
     List<SpeedDialChild> actions = [];
 
-    if (api.checkPermission("part", "add")) {
+    if (InvenTreePart().canCreate) {
      actions.add(
        SpeedDialChild(
          child: FaIcon(FontAwesomeIcons.shapes),
@@ -70,7 +70,7 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
      );
     }
 
-    if (api.checkPermission("part_category", "add")) {
+    if (InvenTreePartCategory().canCreate) {
       actions.add(
         SpeedDialChild(
           child: FaIcon(FontAwesomeIcons.sitemap),
diff --git a/lib/widget/company_detail.dart b/lib/widget/company_detail.dart
index ff62fd46..c1d98a5c 100644
--- a/lib/widget/company_detail.dart
+++ b/lib/widget/company_detail.dart
@@ -49,17 +49,15 @@ class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> {
   List<Widget> appBarActions(BuildContext context) {
     List<Widget> actions = [];
 
-    if (api.checkPermission("purchase_order", "change") ||
-        api.checkPermission("sales_order", "change") ||
-        api.checkPermission("return_order", "change")) {
+    if (InvenTreeCompany().canEdit) {
       actions.add(
-          IconButton(
-              icon: Icon(Icons.edit_square),
-              tooltip: L10().companyEdit,
-              onPressed: () {
-                editCompany(context);
-              }
-          )
+        IconButton(
+            icon: Icon(Icons.edit_square),
+            tooltip: L10().companyEdit,
+            onPressed: () {
+              editCompany(context);
+            }
+        )
       );
     }
     
@@ -281,7 +279,7 @@ class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> {
               builder: (context) => AttachmentWidget(
                 InvenTreeCompanyAttachment(),
                 widget.company.pk,
-                api.checkPermission("purchase_order", "change") || api.checkPermission("sales_order", "change")
+                InvenTreeCompany().canEdit
               )
             )
           );
diff --git a/lib/widget/drawer.dart b/lib/widget/drawer.dart
index a745d2bf..2ca8d88e 100644
--- a/lib/widget/drawer.dart
+++ b/lib/widget/drawer.dart
@@ -3,6 +3,9 @@ import "package:font_awesome_flutter/font_awesome_flutter.dart";
 
 import "package:inventree/api.dart";
 import "package:inventree/app_colors.dart";
+import "package:inventree/inventree/company.dart";
+import "package:inventree/inventree/purchase_order.dart";
+import "package:inventree/inventree/stock.dart";
 import "package:inventree/l10.dart";
 import "package:inventree/settings/settings.dart";
 import "package:inventree/widget/category_display.dart";
@@ -95,7 +98,7 @@ class InvenTreeDrawer extends StatelessWidget {
 
     tiles.add(Divider());
 
-    if (InvenTreeAPI().checkPermission("part_category", "view")) {
+    if (InvenTreeCompany().canView) {
       tiles.add(
         ListTile(
           title: Text(L10().parts),
@@ -105,7 +108,7 @@ class InvenTreeDrawer extends StatelessWidget {
       );
     }
 
-    if (InvenTreeAPI().checkPermission("stock_location", "view")) {
+    if (InvenTreeStockLocation().canView) {
       tiles.add(
         ListTile(
           title: Text(L10().stock),
@@ -115,7 +118,7 @@ class InvenTreeDrawer extends StatelessWidget {
       );
     }
 
-    if (InvenTreeAPI().checkPermission("purchase_order", "view")) {
+    if (InvenTreePurchaseOrder().canView) {
       tiles.add(
         ListTile(
           title: Text(L10().purchaseOrders),
diff --git a/lib/widget/location_display.dart b/lib/widget/location_display.dart
index dec7be66..38cc63d2 100644
--- a/lib/widget/location_display.dart
+++ b/lib/widget/location_display.dart
@@ -63,7 +63,7 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
     }
 
     // Add "edit" button
-    if (location != null && api.checkPermission("stock_location", "change")) {
+    if (location != null && InvenTreeStockLocation().canEdit) {
       actions.add(
           IconButton(
               icon: Icon(Icons.edit_square),
@@ -85,7 +85,7 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
 
     if (location != null) {
       // Scan items into this location
-      if (api.checkPermission("stock", "change")) {
+      if (InvenTreeStockItem().canEdit) {
         actions.add(
             SpeedDialChild(
                 child: FaIcon(FontAwesomeIcons.qrcode),
@@ -105,7 +105,7 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
       }
 
       // Scan this location into another one
-      if (api.checkPermission("stock_location", "change")) {
+      if (InvenTreeStockLocation().canEdit) {
         actions.add(
             SpeedDialChild(
                 child: FaIcon(FontAwesomeIcons.qrcode),
@@ -144,7 +144,7 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
     List<SpeedDialChild> actions = [];
 
     // Create new location
-    if (api.checkPermission("stock_location", "add")) {
+    if (InvenTreeStockLocation().canCreate) {
       actions.add(
           SpeedDialChild(
               child: FaIcon(FontAwesomeIcons.sitemap),
@@ -157,7 +157,7 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
     }
 
     // Create new item
-    if (location != null && api.checkPermission("stock", "add")) {
+    if (location != null && InvenTreeStockItem().canCreate) {
       actions.add(
           SpeedDialChild(
               child: FaIcon(FontAwesomeIcons.boxesStacked),
diff --git a/lib/widget/part_notes.dart b/lib/widget/notes_widget.dart
similarity index 59%
rename from lib/widget/part_notes.dart
rename to lib/widget/notes_widget.dart
index 15b9ee76..a5c11465 100644
--- a/lib/widget/part_notes.dart
+++ b/lib/widget/notes_widget.dart
@@ -1,49 +1,56 @@
 import "package:flutter/material.dart";
 import "package:font_awesome_flutter/font_awesome_flutter.dart";
-import "package:inventree/api.dart";
-import "package:inventree/inventree/part.dart";
+import "package:inventree/inventree/model.dart";
 import "package:inventree/widget/refreshable_state.dart";
 import "package:flutter_markdown/flutter_markdown.dart";
 import "package:inventree/l10.dart";
 
 
-class PartNotesWidget extends StatefulWidget {
+/*
+ * A widget for displaying the notes associated with a given model.
+ * We need to pass in the following parameters:
+ * 
+ * - Model instance
+ * - Title for the app bar
+ */
+class NotesWidget extends StatefulWidget {
 
-  const PartNotesWidget(this.part, {Key? key}) : super(key: key);
+  const NotesWidget(this.model, {Key? key}) : super(key: key);
 
-  final InvenTreePart part;
+  final InvenTreeModel model;
 
   @override
-  _PartNotesState createState() => _PartNotesState(part);
+  _NotesState createState() => _NotesState();
 }
 
 
-class _PartNotesState extends RefreshableState<PartNotesWidget> {
+/*
+ * Class representing the state of the NotesWidget
+ */
+class _NotesState extends RefreshableState<NotesWidget> {
 
-  _PartNotesState(this.part);
-
-  final InvenTreePart part;
+  _NotesState();
 
   @override
   Future<void> request(BuildContext context) async {
-    await part.reload();
+    await widget.model.reload();
   }
 
   @override
-  String getAppBarTitle() => L10().partNotes;
+  String getAppBarTitle() => L10().editNotes;
 
   @override
   List<Widget> appBarActions(BuildContext context) {
 
     List<Widget> actions = [];
 
-    if (InvenTreeAPI().checkPermission("part", "change")) {
+    if (widget.model.canEdit) {
       actions.add(
         IconButton(
           icon: FaIcon(FontAwesomeIcons.penToSquare),
           tooltip: L10().edit,
           onPressed: () {
-            part.editForm(
+            widget.model.editForm(
               context,
               L10().editNotes,
               fields: {
@@ -67,7 +74,7 @@ class _PartNotesState extends RefreshableState<PartNotesWidget> {
   Widget getBody(BuildContext context) {
     return Markdown(
       selectable: false,
-      data: part.notes,
+      data: widget.model.notes,
     );
   }
 
diff --git a/lib/widget/part_detail.dart b/lib/widget/part_detail.dart
index 485b35b0..bcfb733c 100644
--- a/lib/widget/part_detail.dart
+++ b/lib/widget/part_detail.dart
@@ -16,7 +16,7 @@ import "package:inventree/preferences.dart";
 import "package:inventree/widget/attachment_widget.dart";
 import "package:inventree/widget/bom_list.dart";
 import "package:inventree/widget/part_list.dart";
-import "package:inventree/widget/part_notes.dart";
+import "package:inventree/widget/notes_widget.dart";
 import "package:inventree/widget/part_parameter_widget.dart";
 import "package:inventree/widget/progress.dart";
 import "package:inventree/widget/category_display.dart";
@@ -72,7 +72,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
   List<Widget> appBarActions(BuildContext context) {
     List<Widget> actions = [];
 
-    if (api.checkPermission("part", "change")) {
+    if (InvenTreePart().canEdit) {
       actions.add(
           IconButton(
               icon: Icon(Icons.edit_square),
@@ -90,7 +90,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
   List<SpeedDialChild> barcodeButtons(BuildContext context) {
     List<SpeedDialChild> actions = [];
 
-    if (api.checkPermission("part", "change")) {
+    if (InvenTreePart().canEdit) {
       if (api.supportModernBarcodes) {
         actions.add(
             customBarcodeAction(
@@ -109,7 +109,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
   List<SpeedDialChild> actionButtons(BuildContext context) {
     List<SpeedDialChild> actions = [];
 
-    if (api.checkPermission("stock", "add")) {
+    if (InvenTreeStockItem().canCreate) {
       actions.add(
         SpeedDialChild(
           child: FaIcon(FontAwesomeIcons.box),
@@ -234,7 +234,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
    */
   Future <void> _toggleStar(BuildContext context) async {
 
-    if (api.checkPermission("part", "view")) {
+    if (InvenTreePart().canView) {
       showLoadingOverlay(context);
       await part.update(values: {"starred": "${!part.starred}"});
       hideLoadingOverlay();
@@ -557,7 +557,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
           onTap: () {
             Navigator.push(
                 context,
-                MaterialPageRoute(builder: (context) => PartNotesWidget(part))
+                MaterialPageRoute(builder: (context) => NotesWidget(part))
             );
           },
         )
@@ -575,7 +575,8 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
               builder: (context) => AttachmentWidget(
                   InvenTreePartAttachment(),
                   part.pk,
-                  api.checkPermission("part", "change"))
+                  part.canEdit
+                )
             )
           );
         },
@@ -678,10 +679,6 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
       Tab(text: L10().stock)
     ];
 
-    if (showBom && part.isAssembly) {
-      icons.add(Tab(text: L10().bom));
-    }
-
     if (showParameters) {
       icons.add(Tab(text: L10().parameters));
     }
@@ -703,10 +700,6 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
       PaginatedStockItemList({"part": part.pk.toString()}, true)
     ];
 
-    if (showBom && part.isAssembly) {
-      tabs.add(PaginatedBomList({"part": part.pk.toString()}, showSearch: true, isParentPart: true));
-    }
-
     if (showParameters) {
       tabs.add(PaginatedParameterList({"part": part.pk.toString()}, true));
     }
diff --git a/lib/widget/part_image_widget.dart b/lib/widget/part_image_widget.dart
index c548e8e0..68b12bde 100644
--- a/lib/widget/part_image_widget.dart
+++ b/lib/widget/part_image_widget.dart
@@ -42,7 +42,7 @@ class _PartImageState extends RefreshableState<PartImageWidget> {
 
     List<Widget> actions = [];
 
-    if (InvenTreeAPI().checkPermission("part", "change")) {
+    if (part.canEdit) {
 
       // File upload
       actions.add(
diff --git a/lib/widget/purchase_order_detail.dart b/lib/widget/purchase_order_detail.dart
index 06dd8a63..1e405f07 100644
--- a/lib/widget/purchase_order_detail.dart
+++ b/lib/widget/purchase_order_detail.dart
@@ -14,6 +14,7 @@ import "package:inventree/inventree/company.dart";
 import "package:inventree/inventree/purchase_order.dart";
 import "package:inventree/widget/attachment_widget.dart";
 import "package:inventree/widget/company_detail.dart";
+import "package:inventree/widget/notes_widget.dart";
 import "package:inventree/widget/refreshable_state.dart";
 import "package:inventree/widget/snacks.dart";
 import "package:inventree/widget/stock_list.dart";
@@ -49,7 +50,7 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
   List<Widget> appBarActions(BuildContext context) {
     List<Widget> actions = [];
 
-    if (InvenTreeAPI().checkPermission("purchase_order", "change")) {
+    if (order.canEdit) {
       actions.add(
         IconButton(
           icon: Icon(Icons.edit_square),
@@ -68,7 +69,7 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
   List<SpeedDialChild> actionButtons(BuildContext context) {
     List<SpeedDialChild> actions = [];
 
-    if (api.checkPermission("purchase_order", "add")) {
+    if (order.canCreate) {
       if (order.isPending) {
         actions.add(
           SpeedDialChild(
@@ -255,6 +256,22 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
       ));
     }
 
+    // Notes tile
+    tiles.add(
+        ListTile(
+          title: Text(L10().notes),
+          leading: FaIcon(FontAwesomeIcons.noteSticky, color: COLOR_ACTION),
+          onTap: () {
+            Navigator.push(
+              context,
+              MaterialPageRoute(
+                builder: (context) => NotesWidget(order)
+              )
+            );
+          },
+        )
+    );
+
     // Attachments
     tiles.add(
         ListTile(
@@ -263,13 +280,14 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
           trailing: attachmentCount > 0 ? Text(attachmentCount.toString()) : null,
           onTap: () {
             Navigator.push(
-                context,
-                MaterialPageRoute(
-                    builder: (context) => AttachmentWidget(
-                        InvenTreePurchaseOrderAttachment(),
-                        order.pk,
-                        InvenTreeAPI().checkPermission("purchase_order", "change"))
+              context,
+              MaterialPageRoute(
+                builder: (context) => AttachmentWidget(
+                    InvenTreePurchaseOrderAttachment(),
+                    order.pk,
+                    order.canEdit
                 )
+              )
             );
           },
         )
diff --git a/lib/widget/purchase_order_list.dart b/lib/widget/purchase_order_list.dart
index f2e51419..a2281985 100644
--- a/lib/widget/purchase_order_list.dart
+++ b/lib/widget/purchase_order_list.dart
@@ -52,7 +52,7 @@ class _PurchaseOrderListWidgetState extends RefreshableState<PurchaseOrderListWi
   List<SpeedDialChild> actionButtons(BuildContext context) {
     List<SpeedDialChild> actions = [];
 
-    if (api.checkPermission("purchase_order", "add")) {
+    if (InvenTreePurchaseOrder().canCreate) {
       actions.add(
         SpeedDialChild(
           child: FaIcon(FontAwesomeIcons.circlePlus),
diff --git a/lib/widget/search.dart b/lib/widget/search.dart
index 750717e5..fd8fcabb 100644
--- a/lib/widget/search.dart
+++ b/lib/widget/search.dart
@@ -207,29 +207,29 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
       };
 
       // Part search
-      if (api.checkPermission("part", "view")) {
+      if (InvenTreePart().canView) {
         body["part"] = {};
       }
 
       // PartCategory search
-      if (api.checkPermission("part_category", "view")) {
+      if (InvenTreePartCategory().canView) {
         body["partcategory"] = {};
       }
 
       // StockItem search
-      if (api.checkPermission("stock", "view")) {
+      if (InvenTreeStockItem().canView) {
         body["stockitem"] = {
           "in_stock": true,
         };
       }
 
       // StockLocation search
-      if (api.checkPermission("stock_location", "view")) {
+      if (InvenTreeStockLocation().canView) {
         body["stocklocation"] = {};
       }
 
       // PurchaseOrder search
-      if (api.checkPermission("purchase_order", "view")) {
+      if (InvenTreePurchaseOrder().canView) {
         body["purchaseorder"] = {
           "outstanding": true
         };
@@ -253,7 +253,7 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
   Future<void> legacySearch(String term) async {
 
     // Search parts
-    if (api.checkPermission("part", "view")) {
+    if (InvenTreePart().canView) {
       nPendingSearches++;
       InvenTreePart().count(searchQuery: term).then((int n) {
         if (term == searchController.text) {
@@ -268,7 +268,7 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
     }
 
     // Search part categories
-    if (api.checkPermission("part_category", "view")) {
+    if (InvenTreePartCategory().canView) {
       nPendingSearches++;
       InvenTreePartCategory().count(searchQuery: term,).then((int n) {
         if (term == searchController.text) {
@@ -283,7 +283,7 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
     }
 
     // Search stock items
-    if (api.checkPermission("stock", "view")) {
+    if (InvenTreeStockItem().canView) {
       nPendingSearches++;
       InvenTreeStockItem().count(searchQuery: term).then((int n) {
         if (term == searchController.text) {
@@ -298,7 +298,7 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
     }
 
     // Search stock locations
-    if (api.checkPermission("stock_location", "view")) {
+    if (InvenTreeStockLocation().canView) {
       nPendingSearches++;
       InvenTreeStockLocation().count(searchQuery: term).then((int n) {
         if (term == searchController.text) {
@@ -313,7 +313,7 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
     }
 
     // Search purchase orders
-    if (api.checkPermission("purchase_order", "view")) {
+    if (InvenTreePurchaseOrder().canView) {
      nPendingSearches++;
       InvenTreePurchaseOrder().count(
           searchQuery: term,
diff --git a/lib/widget/stock_detail.dart b/lib/widget/stock_detail.dart
index 0149e097..b2dcc2c8 100644
--- a/lib/widget/stock_detail.dart
+++ b/lib/widget/stock_detail.dart
@@ -25,7 +25,7 @@ import "package:inventree/widget/refreshable_state.dart";
 import "package:inventree/widget/snacks.dart";
 import "package:inventree/widget/stock_item_history.dart";
 import "package:inventree/widget/stock_item_test_results.dart";
-import "package:inventree/widget/stock_notes.dart";
+import "package:inventree/widget/notes_widget.dart";
 
 
 class StockDetailWidget extends StatefulWidget {
@@ -64,7 +64,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
       );
     }
 
-    if (api.checkPermission("stock", "change")) {
+    if (widget.item.canEdit) {
       actions.add(
           IconButton(
               icon: Icon(Icons.edit_square),
@@ -84,7 +84,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
 
     List<SpeedDialChild> actions = [];
 
-    if (api.checkPermission("stock", "change")) {
+    if (widget.item.canEdit) {
 
       // Stock adjustment actions available if item is *not* serialized
       if (!widget.item.isSerialized()) {
@@ -138,7 +138,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
       );
     }
 
-    if (api.checkPermission("stock", "delete")) {
+    if (widget.item.canDelete) {
       actions.add(
           SpeedDialChild(
               child: FaIcon(FontAwesomeIcons.trashCan, color: Colors.red),
@@ -157,7 +157,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
   List<SpeedDialChild> barcodeButtons(BuildContext context) {
     List<SpeedDialChild> actions = [];
 
-    if (api.checkPermission("stock", "change")) {
+    if (widget.item.canEdit) {
       // Scan item into location
       actions.add(
           SpeedDialChild(
@@ -816,7 +816,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
         onTap: () {
           Navigator.push(
             context,
-            MaterialPageRoute(builder: (context) => StockNotesWidget(widget.item))
+            MaterialPageRoute(builder: (context) => NotesWidget(widget.item))
           );
         }
       )
@@ -829,13 +829,14 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
           trailing: attachmentCount > 0 ? Text(attachmentCount.toString()) : null,
           onTap: () {
             Navigator.push(
-                context,
-                MaterialPageRoute(
-                    builder: (context) => AttachmentWidget(
-                        InvenTreeStockItemAttachment(),
-                        widget.item.pk,
-                        InvenTreeAPI().checkPermission("stock", "change"))
+              context,
+              MaterialPageRoute(
+                builder: (context) => AttachmentWidget(
+                  InvenTreeStockItemAttachment(),
+                  widget.item.pk,
+                  widget.item.canEdit,
                 )
+              )
             );
           },
         )
diff --git a/lib/widget/stock_notes.dart b/lib/widget/stock_notes.dart
deleted file mode 100644
index c990d0ef..00000000
--- a/lib/widget/stock_notes.dart
+++ /dev/null
@@ -1,77 +0,0 @@
-
-import "package:flutter/material.dart";
-import "package:font_awesome_flutter/font_awesome_flutter.dart";
-import "package:inventree/inventree/stock.dart";
-import "package:inventree/widget/refreshable_state.dart";
-import "package:flutter_markdown/flutter_markdown.dart";
-import "package:inventree/l10.dart";
-
-import "package:inventree/api.dart";
-
-
-class StockNotesWidget extends StatefulWidget {
-
-  const StockNotesWidget(this.item, {Key? key}) : super(key: key);
-
-  final InvenTreeStockItem item;
-
-  @override
-  _StockNotesState createState() => _StockNotesState(item);
-}
-
-
-class _StockNotesState extends RefreshableState<StockNotesWidget> {
-
-  _StockNotesState(this.item);
-
-  final InvenTreeStockItem item;
-
-  @override
-  String getAppBarTitle() => L10().stockItemNotes;
-
-  @override
-  Future<void> request(BuildContext context) async {
-    if (item.pk > 0) {
-      await item.reload();
-    }
-  }
-
-  @override
-  List<Widget> appBarActions(BuildContext context) {
-    List<Widget> actions = [];
-
-    if (InvenTreeAPI().checkPermission("stock", "change")) {
-      actions.add(
-          IconButton(
-              icon: FaIcon(FontAwesomeIcons.penToSquare),
-              tooltip: L10().edit,
-              onPressed: () {
-                item.editForm(
-                  context,
-                  L10().editNotes,
-                  fields: {
-                    "notes": {
-                      "multiline": true,
-                    }
-                  },
-                  onSuccess: (data) async {
-                    refresh(context);
-                  }
-                );
-              }
-          )
-      );
-    }
-
-    return actions;
-  }
-
-  @override
-  Widget getBody(BuildContext context) {
-    return Markdown(
-      selectable: false,
-      data: item.notes,
-    );
-  }
-
-}
\ No newline at end of file
diff --git a/lib/widget/supplier_part_detail.dart b/lib/widget/supplier_part_detail.dart
index 85661d45..662e266d 100644
--- a/lib/widget/supplier_part_detail.dart
+++ b/lib/widget/supplier_part_detail.dart
@@ -57,10 +57,7 @@ class _SupplierPartDisplayState extends RefreshableState<SupplierPartDetailWidge
   List<SpeedDialChild> barcodeButtons(BuildContext context) {
     List<SpeedDialChild> actions = [];
 
-    if (api.checkPermission("purchase_order", "change") ||
-        api.checkPermission("sales_order", "change") ||
-        api.checkPermission("return_order", "change")) {
-
+    if (widget.supplierPart.canEdit) {
       actions.add(
         customBarcodeAction(
           context, this,
@@ -78,9 +75,7 @@ class _SupplierPartDisplayState extends RefreshableState<SupplierPartDetailWidge
   List<Widget> appBarActions(BuildContext context) {
     List<Widget> actions = [];
 
-    if (api.checkPermission("purchase_order", "change") ||
-        api.checkPermission("sales_order", "change") ||
-        api.checkPermission("return_order", "change")) {
+    if (widget.supplierPart.canEdit) {
       actions.add(
           IconButton(
               icon: Icon(Icons.edit_square),