change user position action

This commit is contained in:
Yuri Kuznetsov
2024-08-22 17:25:38 +03:00
parent 14d405542b
commit e571a63a30
8 changed files with 309 additions and 3 deletions
+8
View File
@@ -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') ?? [];
}
}
@@ -166,5 +166,8 @@
"active": "Active",
"activePortal": "Portal Active",
"activeApi": "API Active"
},
"actions": {
"changePosition": "Change Position"
}
}
@@ -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": {
@@ -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",
+5
View File
@@ -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",
@@ -0,0 +1,65 @@
<?php
/**LICENSE**/
namespace Espo\Tools\User\Api;
use Espo\Core\Acl;
use Espo\Core\Api\Action;
use Espo\Core\Api\Request;
use Espo\Core\Api\Response;
use Espo\Core\Api\ResponseComposer;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Record\EntityProvider;
use Espo\Entities\Team;
use Espo\Entities\User;
use Espo\ORM\EntityManager;
/**
* @noinspection PhpUnused
*/
class PutTeamUserPosition implements Action
{
public function __construct(
private Acl $acl,
private User $user,
private EntityProvider $entityProvider,
private EntityManager $entityManager,
) {}
public function process(Request $request): Response
{
if (!$this->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);
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
import RowActionHandler from 'handlers/row-action';
import UserSelectPositionModalView from 'views/user/modals/select-position';
// noinspection JSUnusedGlobalSymbols
export default class ChangeUserTeamPositionRowActionHandler extends RowActionHandler {
async process(model, action) {
if (!model.collection || !model.collection.parentModel) {
console.error(`Team model cannot be obtained.`);
return;
}
const team = model.collection.parentModel;
/** @type {string[]} */
const positionList = team.attributes.positionList || [];
const position = model.attributes.teamRole;
const view = new UserSelectPositionModalView({
position: position,
positionList: positionList,
name: model.attributes.name,
onApply: position => {
this.savePosition(team.id, model, position);
},
});
await this.view.assignView('dialog', view);
await view.render();
}
isAvailable(model, action) {
if (!model.collection || !model.collection.parentModel) {
return false;
}
if (!this.view.getAcl().checkModel(model, 'edit')) {
return false;
}
if (!this.view.getUser().isAdmin()) {
return false;
}
return true;
}
/**
* @private
* @param {string} teamId
* @param {import('model').default} model
* @param {string|null} position
*/
async savePosition(teamId, model, position) {
Espo.Ui.notify(' ... ');
await Espo.Ajax.putRequest(`Team/${teamId}/userPosition`, {
id: model.id,
position: position,
});
model.setMultiple({teamRole: position});
Espo.Ui.success(this.view.translate('Saved'));
}
}
@@ -0,0 +1,116 @@
/************************************************************************
* 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 <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
import ModalView from 'views/modal';
import Model from 'model';
import EditForModalRecordView from 'views/record/edit-for-modal';
import EnumFieldView from 'views/fields/enum';
export default class UserSelectPositionModalView extends ModalView {
templateContent = '<div class="record no-side-margin">{{{record}}}</div>'
className = 'dialog dialog-record'
shortcutKeys = {
'Control+Enter': 'apply',
}
/**
* @param {{
* positionList: string[],
* position: string|null,
* name: string,
* onApply: function(string|null),
* }} options
*/
constructor(options) {
super(options);
/** @private */
this.props = options;
}
setup() {
this.headerText =
this.translate('changePosition', 'actions', 'User') + ' · ' +
this.props.name;
this.buttonList = [
{
name: 'save',
label: 'Save',
style: 'primary',
onClick: () => this.apply(),
},
{
name: 'cancel',
label: 'Cancel',
}
];
this.model = new Model();
this.model.setMultiple({position: this.props.position});
this.recordView = new EditForModalRecordView({
model: this.model,
detailLayout: [
{
rows: [
[
{
view: new EnumFieldView({
name: 'position',
params: {
options: ['', ...this.props.positionList],
},
labelText: this.translate('teamRole', 'fields', 'User'),
}),
},
false
]
]
}
]
});
this.assignView('record', this.recordView, '.record');
}
/**
* @private
*/
apply() {
if (this.recordView.validate()) {
return;
}
this.props.onApply(this.model.attributes.position);
this.close();
}
}