diff --git a/application/Espo/Entities/Team.php b/application/Espo/Entities/Team.php
index b27254ee30..45c814d29c 100644
--- a/application/Espo/Entities/Team.php
+++ b/application/Espo/Entities/Team.php
@@ -49,4 +49,12 @@ class Team extends \Espo\Core\ORM\Entity
/** @var ?Link */
return $this->getValueObject('layoutSet');
}
+
+ /**
+ * @return string[]
+ */
+ public function getPositionList(): array
+ {
+ return $this->get('positionList') ?? [];
+ }
}
diff --git a/application/Espo/Resources/i18n/en_US/User.json b/application/Espo/Resources/i18n/en_US/User.json
index 87112d58fa..91e9bbbeb0 100644
--- a/application/Espo/Resources/i18n/en_US/User.json
+++ b/application/Espo/Resources/i18n/en_US/User.json
@@ -166,5 +166,8 @@
"active": "Active",
"activePortal": "Portal Active",
"activeApi": "API Active"
+ },
+ "actions": {
+ "changePosition": "Change Position"
}
}
diff --git a/application/Espo/Resources/metadata/clientDefs/Team.json b/application/Espo/Resources/metadata/clientDefs/Team.json
index 7949e3c552..2d39572919 100644
--- a/application/Espo/Resources/metadata/clientDefs/Team.json
+++ b/application/Espo/Resources/metadata/clientDefs/Team.json
@@ -11,11 +11,18 @@
},
"relationshipPanels": {
"users": {
- "create": false,
- "rowActionsView": "views/record/row-actions/relationship-unlink-only",
+ "createDisabled": true,
+ "editDisabled": true,
+ "removeDisabled": true,
"layout": "listForTeam",
"selectPrimaryFilterName": "active",
- "filterList": ["all", "active"]
+ "filterList": ["all", "active"],
+ "rowActionList": [
+ "changeTeamPosition"
+ ],
+ "selectMandatoryAttributeList": [
+ "teamRole"
+ ]
}
},
"recordViews": {
diff --git a/application/Espo/Resources/metadata/clientDefs/User.json b/application/Espo/Resources/metadata/clientDefs/User.json
index 6688b13c09..312bf11f97 100644
--- a/application/Espo/Resources/metadata/clientDefs/User.json
+++ b/application/Espo/Resources/metadata/clientDefs/User.json
@@ -18,6 +18,13 @@
"detail": "views/user/modals/detail",
"massUpdate": "views/user/modals/mass-update"
},
+ "rowActionDefs": {
+ "changeTeamPosition": {
+ "labelTranslation": "User.actions.changePosition",
+ "handler": "handlers/user/change-team-position-row-action",
+ "groupIndex": 3
+ }
+ },
"defaultSidePanel": {
"detail": {
"name": "default",
diff --git a/application/Espo/Resources/routes.json b/application/Espo/Resources/routes.json
index f51a3c1c53..e573396dde 100644
--- a/application/Espo/Resources/routes.json
+++ b/application/Espo/Resources/routes.json
@@ -436,6 +436,11 @@
"actionClassName": "Espo\\Tools\\UserSecurity\\Api\\PostChangePasswordByRequest",
"noAuth": true
},
+ {
+ "route": "/Team/:id/userPosition",
+ "method": "put",
+ "actionClassName": "Espo\\Tools\\User\\Api\\PutTeamUserPosition"
+ },
{
"route": "/Oidc/authorizationData",
"method": "get",
diff --git a/application/Espo/Tools/User/Api/PutTeamUserPosition.php b/application/Espo/Tools/User/Api/PutTeamUserPosition.php
new file mode 100644
index 0000000000..e05e58b4e9
--- /dev/null
+++ b/application/Espo/Tools/User/Api/PutTeamUserPosition.php
@@ -0,0 +1,65 @@
+user->isAdmin()) {
+ throw new Forbidden();
+ }
+
+ $teamId = $request->getRouteParam('id') ?? throw new BadRequest();
+ $userId = $request->getParsedBody()->id;
+ $position = $request->getParsedBody()->position;
+
+ if (!is_string($userId) || !$userId) {
+ throw new BadRequest("Bad userId.");
+ }
+
+ if (!is_string($position) && $position !== null) {
+ throw new BadRequest("Bad position.");
+ }
+
+ $user = $this->entityProvider->getByClass(User::class, $userId);
+ $team = $this->entityProvider->getByClass(Team::class, $teamId);
+
+ if (!$this->acl->checkEntityEdit($user)) {
+ throw new Forbidden();
+ }
+
+ if ($position !== null && !in_array($position, $team->getPositionList())) {
+ throw new BadRequest("Not allowed position.");
+ }
+
+ $this->entityManager
+ ->getRelation($team, 'users')
+ ->updateColumns($user, ['role' => $position]);
+
+ return ResponseComposer::json(true);
+ }
+}
diff --git a/client/src/handlers/user/change-team-position-row-action.js b/client/src/handlers/user/change-team-position-row-action.js
new file mode 100644
index 0000000000..b2b36aef40
--- /dev/null
+++ b/client/src/handlers/user/change-team-position-row-action.js
@@ -0,0 +1,95 @@
+/************************************************************************
+ * This file is part of EspoCRM.
+ *
+ * EspoCRM – Open Source CRM application.
+ * Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
+ * Website: https://www.espocrm.com
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see