2
0
mirror of https://github.com/inventree/inventree-app.git synced 2025-04-27 04:56:48 +00:00

Company create (#610)
Some checks failed
Android / build (push) Has been cancelled
CI / test (push) Has been cancelled
iOS / build (push) Has been cancelled

* API: refactor checkPermission

- Rename to checkRole (actually what it is doing)
- Permission check will be incoming

* Add checkPermission function for API

* Create a new company

* Bump release notes

* Cleanup

* Fix
This commit is contained in:
Oliver 2025-02-08 10:36:26 +11:00 committed by GitHub
parent 0c5944a8a0
commit 1a3f48f48c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 129 additions and 22 deletions

View File

@ -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

View File

@ -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<String, dynamic> roles = {};
// Available user permissions are loaded when connecting to the server
Map<String, dynamic> 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<String, dynamic>;
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<String, dynamic>;
permissions = (data["permissions"] ?? {}) as Map<String, dynamic>;
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<String> perms = List.from(permissions[model] as List<dynamic>);
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<APIResponse> patch(String url, {Map<String, dynamic> body = const {}, int? expectedStatusCode}) async {

View File

@ -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;
}
}

View File

@ -234,6 +234,9 @@
"company": "Company",
"@company": {},
"companyAdd": "Add Company",
"@companyAdd": {},
"companyEdit": "Edit Company",
"@companyEdit": {},

View File

@ -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<CompanyListWidget> {
@override
String getAppBarTitle() => widget.title;
Future<void> _addCompany(BuildContext context) async {
InvenTreeCompany().createForm(
context,
L10().companyAdd,
data: widget.filters,
onSuccess: (result) async {
Map<String, dynamic> data = result as Map<String, dynamic>;
if (data.containsKey("pk")) {
var company = InvenTreeCompany.fromJson(data);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CompanyDetailWidget(company)
)
);
}
}
);
}
@override
List<SpeedDialChild> actionButtons(BuildContext context) {
List<SpeedDialChild> 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);

View File

@ -183,7 +183,7 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> with BaseWidgetPr
bool allowed = true;
if (role.isNotEmpty || permission.isNotEmpty) {
allowed = InvenTreeAPI().checkPermission(role, permission);
allowed = InvenTreeAPI().checkRole(role, permission);
}
return GestureDetector(

View File

@ -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"

View File

@ -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'");