diff --git a/application/Espo/Entities/User.php b/application/Espo/Entities/User.php index cd014a82e1..b6d2572525 100644 --- a/application/Espo/Entities/User.php +++ b/application/Espo/Entities/User.php @@ -145,6 +145,12 @@ class User extends Person return $this->getValueObject('workingTimeCalendar'); } + public function getLayoutSet(): ?Link + { + /** @var ?Link */ + return $this->getValueObject('layoutSet'); + } + public function getTeams(): LinkMultiple { /** @var LinkMultiple */ diff --git a/application/Espo/Resources/i18n/en_US/User.json b/application/Espo/Resources/i18n/en_US/User.json index 101049fd73..e8a2e0806d 100644 --- a/application/Espo/Resources/i18n/en_US/User.json +++ b/application/Espo/Resources/i18n/en_US/User.json @@ -40,7 +40,8 @@ "authMethod": "Authentication Method", "auth2FAEnable": "Enable 2-Factor Authentication", "auth2FAMethod": "2FA Method", - "auth2FATotpSecret": "2FA TOTP Secret" + "auth2FATotpSecret": "2FA TOTP Secret", + "layoutSet": "Layout Set" }, "links": { "defaultTeam": "Default Team", @@ -56,7 +57,8 @@ "userData": "User Data", "dashboardTemplate": "Dashboard Template", "workingTimeCalendar": "Working Time Calendar", - "workingTimeRanges": "Working Time Ranges" + "workingTimeRanges": "Working Time Ranges", + "layoutSet": "Layout Set" }, "labels": { "Create User": "Create User", @@ -93,7 +95,8 @@ "teams": "Teams which this user belongs to. Access control level is inherited from team's roles.", "roles": "Additional access roles. Use it if user doesn't belong to any team or you need to extend access control level exclusively for this user.", "portalRoles": "Additional portal roles. Use it to extend access control level exclusively for this user.", - "portals": "Portals which this user has access to." + "portals": "Portals which this user has access to.", + "layoutSet": "Layouts from a specified set will be applied for the user instead of default ones." }, "messages": { "loginAs": "Open the login link in an incognito window to preserve your current session. Use your admin credentials to log in.", diff --git a/application/Espo/Resources/metadata/entityAcl/User.json b/application/Espo/Resources/metadata/entityAcl/User.json index bcb7f811b9..0307fffe56 100644 --- a/application/Espo/Resources/metadata/entityAcl/User.json +++ b/application/Espo/Resources/metadata/entityAcl/User.json @@ -50,6 +50,9 @@ "workingTimeCalendar": { "nonAdminReadOnly": true }, + "layoutSet": { + "onlyAdmin": true + }, "accounts": { "nonAdminReadOnly": true }, diff --git a/application/Espo/Resources/metadata/entityDefs/User.json b/application/Espo/Resources/metadata/entityDefs/User.json index e24b64f8a0..dc637355ae 100644 --- a/application/Espo/Resources/metadata/entityDefs/User.json +++ b/application/Espo/Resources/metadata/entityDefs/User.json @@ -414,6 +414,13 @@ "layoutListDisabled": true, "customizationAuditedDisabled": true }, + "layoutSet": { + "type": "link", + "layoutDetailDisabled": true, + "layoutListDisabled": true, + "customizationAuditedDisabled": true, + "tooltip": true + }, "auth2FA": { "type": "foreign", "link": "userData", @@ -503,6 +510,11 @@ "foreign": "users", "entity": "WorkingTimeRange" }, + "layoutSet": { + "type": "belongsTo", + "entity": "LayoutSet", + "noJoin": true + }, "preferences": { "type": "hasOne", "entity": "Preferences", diff --git a/application/Espo/Tools/Layout/Service.php b/application/Espo/Tools/Layout/Service.php index 516fa82d53..3d563b2b80 100644 --- a/application/Espo/Tools/Layout/Service.php +++ b/application/Espo/Tools/Layout/Service.php @@ -106,38 +106,9 @@ class Service } catch (NotImplemented) {} - $layoutSetId = null; $data = null; - if ($this->user->isPortal()) { - $portalId = $this->user->getPortalId(); - - if ($portalId) { - $portal = $this->entityManager - ->getRDBRepositoryByClass(Portal::class) - ->select(['layoutSetId']) - ->where(['id' => $portalId]) - ->findOne(); - - if ($portal) { - $layoutSetId = $portal->getLayoutSet()?->getId(); - } - } - } else { - $teamId = $this->user->getDefaultTeam()?->getId(); - - if ($teamId) { - $team = $this->entityManager - ->getRDBRepositoryByClass(Team::class) - ->select(['layoutSetId']) - ->where(['id' => $teamId]) - ->findOne(); - - if ($team) { - $layoutSetId = $team->getLayoutSet()?->getId(); - } - } - } + $layoutSetId = $this->getUserLayoutSetId(); if ($layoutSetId) { $nameReal = $name; @@ -353,4 +324,49 @@ class Service { $this->layoutManager->resetToDefault($scope, 'relationships'); } + + private function getUserLayoutSetId(): ?string + { + if ($this->user->isPortal()) { + $portalId = $this->user->getPortalId(); + + if (!$portalId) { + return null; + } + + $portal = $this->entityManager + ->getRDBRepositoryByClass(Portal::class) + ->select(['layoutSetId']) + ->where(['id' => $portalId]) + ->findOne(); + + if (!$portal) { + return null; + } + + return $portal->getLayoutSet()?->getId(); + } + + if ($this->user->getLayoutSet()) { + return $this->user->getLayoutSet()->getId(); + } + + $teamId = $this->user->getDefaultTeam()?->getId(); + + if (!$teamId) { + return null; + } + + $team = $this->entityManager + ->getRDBRepositoryByClass(Team::class) + ->select(['layoutSetId']) + ->where(['id' => $teamId]) + ->findOne(); + + if (!$team) { + return null; + } + + return $team->getLayoutSet()?->getId(); + } } diff --git a/client/src/views/user/record/detail.js b/client/src/views/user/record/detail.js index a78835a4c3..b6c20a0642 100644 --- a/client/src/views/user/record/detail.js +++ b/client/src/views/user/record/detail.js @@ -359,7 +359,7 @@ class UserDetailRecordView extends DetailRecordView { "label": "Misc", "name": "misc", "rows": [ - [{"name": "workingTimeCalendar"}, false], + [{"name": "workingTimeCalendar"}, {"name": "layoutSet"}], ] }); } diff --git a/client/src/views/user/record/edit.js b/client/src/views/user/record/edit.js index 5c2ee20edc..1273d24ed7 100644 --- a/client/src/views/user/record/edit.js +++ b/client/src/views/user/record/edit.js @@ -209,7 +209,7 @@ class UserEditRecordView extends EditRecordView { "label": "Misc", "name": "misc", "rows": [ - [{"name": "workingTimeCalendar"}, false] + [{"name": "workingTimeCalendar"}, {"name": "layoutSet"}] ] }); }