diff --git a/assets/release_notes.md b/assets/release_notes.md index 826a4cbc..debb55d3 100644 --- a/assets/release_notes.md +++ b/assets/release_notes.md @@ -1,3 +1,8 @@ +### 0.18.0 - February 2025 +--- +- Adds ability to create new companies from the app +- Updated translations + ### 0.17.4 - January 2025 --- - Display responsible owner for orders diff --git a/lib/api.dart b/lib/api.dart index 2c7227ae..f0e2dd50 100644 --- a/lib/api.dart +++ b/lib/api.dart @@ -237,9 +237,12 @@ class InvenTreeAPI { UserProfile? profile; - // Available user roles (permissions) are loaded when connecting to the server + // Available user roles are loaded when connecting to the server Map roles = {}; + // Available user permissions are loaded when connecting to the server + Map permissions = {}; + // Profile authentication token String get token => profile?.token ?? ""; @@ -701,12 +704,11 @@ class InvenTreeAPI { var data = response.asMap(); - if (data.containsKey("roles")) { - // Save a local copy of the user roles - roles = (response.data["roles"] ?? {}) as Map; + if (!data.containsKey("roles")) { + + roles = {}; + permissions = {}; - return true; - } else { showServerError( apiUrl, L10().serverError, @@ -714,6 +716,11 @@ class InvenTreeAPI { ); return false; } + + roles = (data["roles"] ?? {}) as Map; + permissions = (data["permissions"] ?? {}) as Map; + + return true; } // Request plugin information from the server @@ -740,9 +747,9 @@ class InvenTreeAPI { /* * Check if the user has the given role.permission assigned - * e.g. "part", "change" + * e.g. "sales_order", "change" */ - bool checkPermission(String role, String permission) { + bool checkRole(String role, String permission) { if (!_connected) { return false; @@ -750,17 +757,17 @@ class InvenTreeAPI { // If we do not have enough information, assume permission is allowed if (roles.isEmpty) { - debug("checkPermission - no roles defined!"); + debug("checkRole - no roles defined!"); return true; } if (!roles.containsKey(role)) { - debug("checkPermission - role '$role' not found!"); + debug("checkRole - role '$role' not found!"); return true; } if (roles[role] == null) { - debug("checkPermission - role '$role' is null!"); + debug("checkRole - role '$role' is null!"); return false; } @@ -773,7 +780,7 @@ class InvenTreeAPI { } else { // Unknown error - report it! sentryReportError( - "api.checkPermission", + "api.checkRole", error, stackTrace, context: { "role": role, @@ -788,6 +795,54 @@ class InvenTreeAPI { } } + /* + * Check if the user has the particular model permission assigned + * e.g. "company", "add" + */ + bool checkPermission(String model, String permission) { + if (!_connected) { + return false; + } + + if (permissions.isEmpty) { + // Not enough information available - default to True + return true; + } + + if (!permissions.containsKey(model)) { + debug("checkPermission - model '$model' not found!"); + return false; + } + + if (permissions[model] == null) { + debug("checkPermission - model '$model' is null!"); + return false; + } + + try { + List perms = List.from(permissions[model] as List); + return perms.contains(permission); + } catch (error, stackTrace) { + if (error is TypeError) { + // Ignore TypeError + } else { + // Unknown error - report it! + sentryReportError( + "api.checkPermission", + error, stackTrace, + context: { + "model": model, + "permission": permission, + "error": error.toString(), + } + ); + } + + // Unable to determine permission - assume true? + return true; + } + } + // Perform a PATCH request Future patch(String url, {Map body = const {}, int? expectedStatusCode}) async { diff --git a/lib/inventree/model.dart b/lib/inventree/model.dart index e707df98..0673d191 100644 --- a/lib/inventree/model.dart +++ b/lib/inventree/model.dart @@ -212,7 +212,7 @@ class InvenTreeModel { // Test if the user can "edit" this model bool get canEdit { for (String role in rolesRequired) { - if (InvenTreeAPI().checkPermission(role, "change")) { + if (InvenTreeAPI().checkRole(role, "change")) { return true; } } @@ -224,7 +224,7 @@ class InvenTreeModel { // Test if the user can "create" this model bool get canCreate { for (String role in rolesRequired) { - if (InvenTreeAPI().checkPermission(role, "add")) { + if (InvenTreeAPI().checkRole(role, "add")) { return true; } } @@ -236,7 +236,7 @@ class InvenTreeModel { // Test if the user can "delete" this model bool get canDelete { for (String role in rolesRequired) { - if (InvenTreeAPI().checkPermission(role, "delete")) { + if (InvenTreeAPI().checkRole(role, "delete")) { return true; } } @@ -248,7 +248,7 @@ class InvenTreeModel { // Test if the user can "view" this model bool get canView { for (String role in rolesRequired) { - if (InvenTreeAPI().checkPermission(role, "view")) { + if (InvenTreeAPI().checkRole(role, "view")) { return true; } } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index d013f925..07d2d46b 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -234,6 +234,9 @@ "company": "Company", "@company": {}, + "companyAdd": "Add Company", + "@companyAdd": {}, + "companyEdit": "Edit Company", "@companyEdit": {}, diff --git a/lib/widget/company/company_list.dart b/lib/widget/company/company_list.dart index 1c7db1b9..e44b6bea 100644 --- a/lib/widget/company/company_list.dart +++ b/lib/widget/company/company_list.dart @@ -1,5 +1,7 @@ import "package:flutter/material.dart"; +import "package:flutter_speed_dial/flutter_speed_dial.dart"; +import "package:flutter_tabler_icons/flutter_tabler_icons.dart"; import "package:inventree/api.dart"; import "package:inventree/l10.dart"; @@ -35,6 +37,48 @@ class _CompanyListWidgetState extends RefreshableState { @override String getAppBarTitle() => widget.title; + Future _addCompany(BuildContext context) async { + + InvenTreeCompany().createForm( + context, + L10().companyAdd, + data: widget.filters, + onSuccess: (result) async { + Map data = result as Map; + + if (data.containsKey("pk")) { + var company = InvenTreeCompany.fromJson(data); + + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => CompanyDetailWidget(company) + ) + ); + } + } + ); + } + + @override + List actionButtons(BuildContext context) { + List actions = []; + + if (InvenTreeAPI().checkPermission("company", "add")) { + actions.add( + SpeedDialChild( + child: Icon(TablerIcons.circle_plus, color: Colors.green), + label: L10().companyAdd, + onTap: () { + _addCompany(context); + } + ) + ); + } + + return actions; + } + @override Widget getBody(BuildContext context) { return PaginatedCompanyList(widget.title, widget.filters); diff --git a/lib/widget/home.dart b/lib/widget/home.dart index 748a4247..36eb3a82 100644 --- a/lib/widget/home.dart +++ b/lib/widget/home.dart @@ -183,7 +183,7 @@ class _InvenTreeHomePageState extends State with BaseWidgetPr bool allowed = true; if (role.isNotEmpty || permission.isNotEmpty) { - allowed = InvenTreeAPI().checkPermission(role, permission); + allowed = InvenTreeAPI().checkRole(role, permission); } return GestureDetector( diff --git a/pubspec.yaml b/pubspec.yaml index 8de29472..8473d69f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: inventree description: InvenTree stock management -version: 0.17.4+96 +version: 0.18.0+97 environment: sdk: ">=2.19.5 <3.13.0" diff --git a/test/api_test.dart b/test/api_test.dart index 86d52d2e..2a6145f5 100644 --- a/test/api_test.dart +++ b/test/api_test.dart @@ -117,10 +117,10 @@ void main() { assert(api.roles.isNotEmpty); // Check available permissions - assert(api.checkPermission("part", "change")); - assert(api.checkPermission("stock_location", "delete")); - assert(!api.checkPermission("part", "weirdpermission")); - assert(api.checkPermission("blah", "bloo")); + assert(api.checkRole("part", "change")); + assert(api.checkRole("stock_location", "delete")); + assert(!api.checkRole("part", "weirdpermission")); + assert(api.checkRole("blah", "bloo")); debugContains("Received token from server"); debugContains("showSnackIcon: 'Connected to Server'");