snapshot of https://github.com/espocrm/espocrm.git (master, 2026-09-18; blobs > 131072B omitted)
Some checks failed
Test / Test on PHP 8.3 (push) Failing after 5s
Test / Test on PHP 8.4 (push) Failing after 4s
Test / Test on PHP 8.5 (push) Failing after 3s
TypeScript static analysis / TypeScript check (push) Failing after 14s
Test Integration on MySQL / Test on PHP 8.3 (push) Failing after 2s
Test Integration on MySQL / Test on PHP 8.5 (push) Failing after 0s
Test Integration on PostgreSQL / Test on PHP 8.4 fix (push) Failing after 1s
Test Integration on PostgreSQL / Test on PHP 8.4 master (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-18 04:19:15 +05:00
commit 7cc7e6b4f3
10077 changed files with 900756 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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 ActionHandler from 'action-handler';
class PopulateDefaultsHandler extends ActionHandler {
async populate() {
const confirmMessage = this.view.translate('confirmPopulateDefaults', 'messages', 'AddressCountry');
await this.view.confirm(confirmMessage);
Espo.Ui.notifyWait();
await Espo.Ajax.postRequest('AddressCountry/action/populateDefaults');
await this.view.collection.fetch();
Espo.Ui.success(this.view.translate('Done'));
}
}
// noinspection JSUnusedGlobalSymbols
export default PopulateDefaultsHandler;

View File

@@ -0,0 +1,46 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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 ActionHandler from 'action-handler';
import {inject} from 'di';
import Router from 'router';
// noinspection JSUnusedGlobalSymbols
export default class extends ActionHandler {
/**
* @type {Router}
* @private
*/
@inject(Router)
router
process() {
window.open('api/v1/OpenApi', '_blank');
}
}

View File

@@ -0,0 +1,67 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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 type {CreateRelatedHandler as CreateRelatedHandlerInterface} from 'contracts/relation';
import type ViewHelper from 'view-helper';
import type Model from 'model';
/**
* Prepares attributes for a related record that is being created.
*/
abstract class CreateRelatedHandler<M extends Model = Model> implements CreateRelatedHandlerInterface<M> {
/**
* @internal
*/
protected readonly viewHelper: ViewHelper
/**
* @internal
*/
constructor(viewHelper: ViewHelper) {
this.viewHelper = viewHelper;
}
/**
* Get attributes for a new record.
*
* @param model A model.
* @param link A link name. As of v9.2.0.
* @return {Promise<Object.<string, unknown>>} Attributes.
*/
async getAttributes(model: M, link: string): Promise<Record<string, unknown>> {
// noinspection BadExpressionStatementJS
model;
// noinspection BadExpressionStatementJS
link;
return {};
}
}
export default CreateRelatedHandler;

View File

@@ -0,0 +1,65 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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 CreateRelatedHandler from 'handlers/create-related';
import {inject} from 'di';
import ModelFactory from 'model-factory';
export default class SetParentHandler extends CreateRelatedHandler {
/**
* @private
* @type {ModelFactory}
*/
@inject(ModelFactory)
modelFactory
async getAttributes(model, link) {
const entityType = model.getLinkParam(link, 'entity');
if (!entityType) {
return {};
}
const seed = await this.modelFactory.create(entityType);
/** @type {string[]} */
const parentEntityTypeList = seed.getFieldParam('parent', 'entityList') ?? [];
if (!parentEntityTypeList.includes(model.entityType)) {
return {};
}
return {
parentId: model.id,
parentName: model.attributes.name,
parentType: model.entityType,
}
}
}

View File

@@ -0,0 +1,49 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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 DefaultsPreparator from 'handlers/model/defaults-preparator';
import {inject} from 'di';
import Settings from 'models/settings';
// noinspection JSUnusedGlobalSymbols
export default class extends DefaultsPreparator {
/**
* @private
* @type {Settings}
*/
@inject(Settings)
config
async prepare(model) {
return {
baseCode: this.config.get('baseCurrency'),
};
}
}

View File

@@ -0,0 +1,44 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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 ActionHandler from 'action-handler';
export default class CurrencyRecordMenuActionSettingsHandler extends ActionHandler {
process() {
document.location.href = '#Admin/currency';
}
// noinspection JSUnusedGlobalSymbols
isVisible() {
/** @type {Record} */
const params = this.view.options?.params ?? {};
return params.fromSettings ?? false;
}
}

View File

@@ -0,0 +1,45 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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.
************************************************************************/
export default class CurrencyRecordRecordDetailHandler {
/**
* @param {import('views/record/detail').default} view
*/
constructor(view) {
this.view = view;
}
process() {
const model = this.view.model;
this.view.listenTo(model, 'after:relate:rates after:unrelate:rates after:related-change:rates', () => {
model.fetch();
});
}
}

View File

@@ -0,0 +1,54 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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 ActionHandler from 'action-handler';
import {inject} from 'di';
import Language from 'language';
import ResetFetchDataModalView from 'views/email-account/modals/reset-fetch-data';
export default class ResetFetchDataActionHandler extends ActionHandler {
/**
* @private
* @type {Language}
*/
@inject(Language)
language
async process() {
const model = this.view.model;
const view = new ResetFetchDataModalView({
model: model,
});
await this.view.assignView('dialog', view);
await view.render();
}
}

View File

@@ -0,0 +1,126 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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 DynamicHandler from 'dynamic-handler';
class EmailFilterHandler extends DynamicHandler {
init() {
if (this.model.isNew()) {
if (!this.recordView.getUser().isAdmin()) {
this.recordView.hideField('isGlobal');
}
}
if (
!this.model.isNew() &&
!this.recordView.getUser().isAdmin() &&
!this.model.get('isGlobal')
) {
this.recordView.hideField('isGlobal');
}
if (this.model.isNew() && !this.model.get('parentId')) {
this.model.set('parentType', 'User');
this.model.set('parentId', this.recordView.getUser().id);
this.model.set('parentName', this.recordView.getUser().get('name'));
if (!this.recordView.getUser().isAdmin()) {
this.recordView.setFieldReadOnly('parent');
}
}
else if (
this.model.get('parentType') &&
!this.recordView.options.duplicateSourceId
) {
this.recordView.setFieldReadOnly('parent');
this.recordView.setFieldReadOnly('isGlobal');
}
this.recordView.listenTo(this.model, 'change:isGlobal', (model, value, o) => {
if (!o.ui) {
return;
}
if (value) {
this.model.set({
action: 'Skip',
parentName: null,
parentType: null,
parentId: null,
emailFolderId: null,
groupEmailFolderId: null,
markAsRead: false,
});
}
});
this.recordView.listenTo(this.model, 'change:parentType', (model, value, o) => {
if (!o.ui) {
return;
}
// Avoiding side effects.
setTimeout(() => {
if (value !== 'User') {
this.model.set('markAsRead', false);
}
if (value === 'EmailAccount') {
this.model.set('action', 'Skip');
this.model.set('emailFolderId', null);
this.model.set('groupEmailFolderId', null);
this.model.set('markAsRead', false);
return;
}
if (value !== 'InboundEmail') {
if (this.model.get('action') === 'Move to Group Folder') {
this.model.set('action', 'Skip');
}
this.model.set('groupEmailFolderId', null);
return;
}
if (value !== 'User') {
if (this.model.get('action') === 'Move to Folder') {
this.model.set('action', 'Skip');
}
this.model.set('groupFolderId', null);
}
}, 40);
});
}
}
export default EmailFilterHandler;

View File

@@ -0,0 +1,54 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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 ActionHandler from 'action-handler';
import ImportEmlModal from 'views/email/modals/import-eml';
class EmailListActionsHandler extends ActionHandler {
// noinspection JSUnusedGlobalSymbols
async importEml() {
const view = new ImportEmlModal();
await this.view.assignView('dialog', view);
await view.render();
}
// noinspection JSUnusedGlobalSymbols
/**
* @return {boolean}
*/
checkImportEml() {
const acl = this.view.getAcl();
return acl.checkScope('Email', 'create') &&
acl.checkScope('Import');
}
}
export default EmailListActionsHandler;

View File

@@ -0,0 +1,53 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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 SelectRelatedHandler from 'handlers/select-related';
export default class extends SelectRelatedHandler {
/**
* @param {import('model').default} model
* @return {Promise<module:handlers/select-related~filters>}
*/
async getFilters(model) {
const acl = this.viewHelper.acl;
const permission = acl.getPermissionLevel('assignment');
/** @type {string[]} */
const boolFilterList = [];
if (permission === 'team') {
boolFilterList.push('onlyMyTeam');
} else if (permission === 'own') {
boolFilterList.push('onlyMe');
}
return {bool: boolFilterList};
}
}

View File

@@ -0,0 +1,51 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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 ActionHandler from 'action-handler';
class ImportHandler extends ActionHandler {
// noinspection JSUnusedGlobalSymbols
errorExport() {
Espo.Ajax
.postRequest(`Import/${this.view.model.id}/exportErrors`)
.then(data => {
if (!data.attachmentId) {
const message = this.view.translate('noErrors', 'messages', 'Import');
Espo.Ui.warning(message);
return;
}
window.location = this.view.getBasePath() + '?entryPoint=download&id=' + data.attachmentId;
});
}
}
export default ImportHandler;

View File

@@ -0,0 +1,45 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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 SelectRelatedHandler from 'handlers/select-related';
// noinspection JSUnusedGlobalSymbols
export default class extends SelectRelatedHandler {
async getFilters(model) {
return {
advanced: {
entityType: {
type: 'in',
attribute: 'entityType',
value: ['Lead'],
},
},
};
}
}

View File

@@ -0,0 +1,53 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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 {inject} from 'di';
import Metadata from 'metadata';
export default class LeadCaptureRecordDetailViewSetupHandler {
/**
* @private
* @type {Metadata}
*/
@inject(Metadata)
metadata
/**
* @param {import('views/record/detail').default} view
*/
constructor(view) {
this.view = view;
}
process() {
if (!this.metadata.get(`scopes.Lead.pipelines`)) {
this.view.hideField('pipeline', true);
}
}
}

View File

@@ -0,0 +1,70 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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.
************************************************************************/
/** @module handlers/login */
/**
* Custom login handling. To be extended.
*
* @abstract
*/
class LoginHandler {
/**
* @param {import('views/login').default} loginView A login view.
* @param {Object.<string, *>} data Additional metadata.
*/
constructor(loginView, data) {
/**
* A login view.
* @protected
* @type {import('views/login').default}
*/
this.loginView = loginView;
/**
* Additional metadata.
* @protected
* @type {Object.<string, *>}
*/
this.data = data;
}
/**
* Process. Called on 'Sign in' button click.
*
* @public
* @abstract
* @return {Promise<Object.<string, string>>} Resolved with headers to be sent to the `App/user` endpoint.
*/
process() {
return Promise.resolve({});
}
}
export default LoginHandler;

View File

@@ -0,0 +1,240 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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 LoginHandler from 'handlers/login';
import Base64 from 'js-base64';
class OidcLoginHandler extends LoginHandler {
/** @inheritDoc */
process() {
const proxy = window.open(
'about:blank',
'ConnectWithOAuthOidc',
'location=0,status=0,width=800,height=800'
);
Espo.Ui.notifyWait();
return new Promise((resolve, reject) => {
Espo.Ajax.getRequest('Oidc/authorizationData')
.then(data => {
Espo.Ui.notify(false);
this.processWithData(data, proxy)
.then(info => {
const code = info.code;
const nonce = info.nonce;
const authString = Base64.encode('**oidc:' + code);
const headers = {
'Espo-Authorization': authString,
'Authorization': 'Basic ' + authString,
'X-Oidc-Authorization-Nonce': nonce,
};
resolve(headers);
})
.catch(() => {
proxy.close();
reject();
});
})
.catch(() => {
Espo.Ui.notify(false)
proxy.close();
reject();
});
});
}
/**
* @private
* @param {{
* endpoint: string,
* clientId: string,
* redirectUri: string,
* scopes: string[],
* claims: string|null,
* prompt: 'login'|'consent'|'select_account',
* maxAge: Number|null,
* codeChallenge: string|null,
* codeChallengeMethod: string|null
* }} data
* @param {WindowProxy} proxy
* @return {Promise<{code: string, nonce: string}>}
*/
processWithData(data, proxy) {
const state = (Math.random() + 1).toString(36).substring(4);
const nonce = (Math.random() + 1).toString(36).substring(4);
const params = {
client_id: data.clientId,
redirect_uri: data.redirectUri,
response_type: 'code',
scope: data.scopes.join(' '),
state: state,
nonce: nonce,
prompt: data.prompt,
};
if (data.codeChallenge && data.codeChallengeMethod) {
params.code_challenge = data.codeChallenge;
params.code_challenge_method = data.codeChallengeMethod;
}
if (data.maxAge || data.maxAge === 0) {
params.max_age = data.maxAge;
}
if (data.claims) {
params.claims = data.claims;
}
const partList = Object.entries(params)
.map(([key, value]) => {
return key + '=' + encodeURIComponent(value);
});
const url = data.endpoint + '?' + partList.join('&');
return this.processWindow(url, state, nonce, proxy);
}
/**
* @private
* @param {string} url
* @param {string} state
* @param {string} nonce
* @param {WindowProxy} proxy
* @return {Promise<{code: string, nonce: string}>}
*/
processWindow(url, state, nonce, proxy) {
proxy.location.href = url;
return new Promise((resolve, reject) => {
const fail = () => {
window.clearInterval(interval);
if (!proxy.closed) {
proxy.close();
}
reject();
};
const interval = window.setInterval(() => {
if (proxy.closed) {
fail();
return;
}
let url;
try {
url = proxy.location.href;
} catch (e) {
return;
}
if (!url) {
return;
}
const parsedData = this.parseWindowUrl(url);
if (!parsedData) {
fail();
Espo.Ui.error('Could not parse URL', true);
return;
}
if ((parsedData.error || parsedData.code) && parsedData.state !== state) {
fail();
Espo.Ui.error('State mismatch', true);
return;
}
if (parsedData.error) {
fail();
const message = parsedData.errorDescription || this.loginView.translate('Error') + '\n' +
parsedData.error;
Espo.Ui.error(message, true);
console.log(parsedData);
return;
}
if (parsedData.code) {
window.clearInterval(interval);
proxy.close();
resolve({
code: parsedData.code,
nonce: nonce,
});
}
}, 300);
});
}
/**
* @param {string} url
* @return {?{
* code: ?string,
* state: ?string,
* error: ?string,
* errorDescription: ?string,
* }}
*/
parseWindowUrl(url) {
try {
const params = new URL(url).searchParams;
return {
code: params.get('code'),
state: params.get('state'),
error: params.get('error'),
errorDescription: params.get('errorDescription') ?? params.get('error_description'),
};
}
catch (e) {
return null;
}
}
}
export default OidcLoginHandler;

View File

@@ -0,0 +1,163 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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 MapRenderer from 'handlers/map/renderer';
class GoogleMapsRenderer extends MapRenderer {
/**
* @param {module:handlers/map/renderer~addressData} addressData
*/
render(addressData) {
if ('google' in window && window.google.maps) {
this.initMapGoogle(addressData);
return;
}
// noinspection SpellCheckingInspection
if (typeof window.mapapiloaded === 'function') {
// noinspection SpellCheckingInspection
const mapapiloaded = window.mapapiloaded;
// noinspection SpellCheckingInspection
window.mapapiloaded = () => {
this.initMapGoogle(addressData);
mapapiloaded();
};
return;
}
// noinspection SpellCheckingInspection
window.mapapiloaded = () => this.initMapGoogle(addressData);
let src = 'https://maps.googleapis.com/maps/api/js?callback=mapapiloaded&loading=async&v=weekly&libraries=marker';
const apiKey = this.view.getConfig().get('googleMapsApiKey');
if (apiKey) {
src += '&key=' + apiKey;
}
const scriptElement = document.createElement('script');
scriptElement.setAttribute('defer', 'defer');
scriptElement.src = src;
document.head.appendChild(scriptElement);
}
/**
* @param {module:handlers/map/renderer~addressData} addressData
*/
initMapGoogle(addressData) {
// noinspection JSUnresolvedReference
const geocoder = new google.maps.Geocoder();
let map;
const mapId = this.view.getConfig().get('googleMapsMapId') || 'DEMO_MAP_ID';
try {
// noinspection SpellCheckingInspection,JSUnresolvedReference
map = new google.maps.Map(this.view.$el.find('.map').get(0), {
zoom: 15,
center: {lat: 0, lng: 0},
scrollwheel: false,
mapId: mapId,
});
}
catch (e) {
console.error(e.message);
return;
}
let address = '';
if (addressData.street) {
address += addressData.street;
}
if (addressData.city) {
if (address !== '') {
address += ', ';
}
address += addressData.city;
}
if (addressData.state) {
if (address !== '') {
address += ', ';
}
address += addressData.state;
}
if (addressData.postalCode) {
if (addressData.state || addressData.city) {
address += ' ';
}
else {
if (address) {
address += ', ';
}
}
address += addressData.postalCode;
}
if (addressData.country) {
if (address !== '') {
address += ', ';
}
address += addressData.country;
}
// noinspection JSUnresolvedReference
geocoder.geocode({'address': address}, (results, status) => {
// noinspection JSUnresolvedReference
if (status === google.maps.GeocoderStatus.OK) {
// noinspection JSUnresolvedReference
map.setCenter(results[0].geometry.location);
// noinspection JSUnresolvedReference
new google.maps.marker.AdvancedMarkerElement({
map: map,
position: results[0].geometry.location,
});
}
});
}
}
// noinspection JSUnusedGlobalSymbols
export default GoogleMapsRenderer;

View File

@@ -0,0 +1,63 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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.
************************************************************************/
/**
* @module handlers/map/renderer
*/
/**
* A map renderer.
*
* @abstract
*/
class MapRenderer {
/**
* @typedef {Object} module:handlers/map/renderer~addressData
* @property {string|null} street
* @property {string|null} city
* @property {string|null} country
* @property {string|null} state
* @property {string|null} postalCode
*/
/**
* @param {import('views/fields/map').default} view A field view.
*/
constructor(view) {
this.view = view;
}
/**
* @param {module:handlers/map/renderer~addressData} addressData
* @abstract
*/
render(addressData) {}
}
export default MapRenderer;

View File

@@ -0,0 +1,55 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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.
************************************************************************/
/**
* @abstract
*/
class DefaultsPreparator {
/**
* @param {import('view-helper').default} viewHelper
*/
constructor(viewHelper) {
/** @protected */
this.viewHelper = viewHelper;
}
// noinspection JSUnusedLocalSymbols
/**
* Prepare default attributes.
*
* @param {import('model').default} model A model.
* @return {Promise<Object.<string, *>>}
*/
prepare(model) {
return Promise.resolve({});
}
}
// noinspection JSUnusedGlobalSymbols
export default DefaultsPreparator;

View File

@@ -0,0 +1,48 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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 ActionHandler from 'action-handler';
class NavbarMenuHandler extends ActionHandler {
logout() {
this.view.getRouter().logout();
}
// noinspection JSUnusedGlobalSymbols
lastViewed() {
Espo.Ui.notifyWait();
this.view.createView('dialog', 'views/modals/last-viewed', {}, view => {
view.render();
Espo.Ui.notify(false);
});
}
}
export default NavbarMenuHandler;

View File

@@ -0,0 +1,55 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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.
************************************************************************/
export default class {
/**
* @param {import('views/record/detail').default} view
*/
constructor(view) {
this.view = view;
}
process() {
this.controlFields();
this.view.listenTo(this.view.model, 'sync', () => this.controlFields());
}
controlFields() {
const attachmentsIds = this.view.model.attributes.attachmentsIds;
if (!attachmentsIds || !attachmentsIds.length) {
this.view.hideField('attachments');
return;
}
this.view.showField('attachments');
}
}

View File

@@ -0,0 +1,74 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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 ActionHandler from 'action-handler';
import MassActionHelper from 'helpers/mass-action';
import ListRecordView from 'views/record/list';
import Ui from 'ui';
import Ajax from 'ajax';
/**
* Important. Used for AccessToken, RefreshToken, AuthorizationCode.
*/
// noinspection JSUnusedGlobalSymbols
export default class RevokeMassActionHandler extends ActionHandler {
async process() {
const view = this.view as ListRecordView;
const helper = new MassActionHelper(this.view);
const params = view.getMassActionSelectionPostData();
const idle = !!params.searchParams && helper.checkIsIdle(view.collection.total);
const onDone = (count: number) => {
const msg = this.view.translate('massRevokeDone', 'messages', 'OAuthAccessToken')
.replace('{count}', count.toString());
Ui.success(msg);
};
Ui.notifyWait();
const result = await Ajax.postRequest('MassAction', {
entityType: view.collection.entityType!,
action: 'revoke',
params: params,
idle: idle,
});
if (result.id) {
const view = await helper.process(result.id, 'revoke')
this.view.listenToOnce(view, 'close:success', result => onDone(result.count));
return;
}
onDone(result.count);
}
}

View File

@@ -0,0 +1,58 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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 Model from 'model';
import Ajax from 'ajax';
import Ui from 'ui';
import {inject} from 'di';
import Language from 'language';
/**
* Important. Used for AccessToken, RefreshToken, AuthorizationCode.
*/
// noinspection JSUnusedGlobalSymbols
export default class RevokeRowActionHandler extends RowActionHandler {
@inject(Language)
private language: Language
async process(model: Model, _action: string) {
Ui.notifyWait();
await Ajax.postRequest(`${model.entityType}/${model.id}/revoke`);
await model.fetch();
Ui.success(this.language.translate('Done'));
}
isAvailable(model: Model, _action: string): boolean {
return model.attributes.status === 'Active';
}
}

View File

@@ -0,0 +1,92 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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';
export default class PipelineStageMoveRowActionHandler extends RowActionHandler
{
isAvailable(model, action) {
const collection = model.collection;
if (!collection) {
return false;
}
if (action === 'moveUp') {
if (collection.indexOf(model) <= 0) {
return false;
}
const index = collection.models.findIndex(m => m === model);
const prevModel = this.collection.at(index - 1);
if (!prevModel || prevModel.attributes.mappedStatus !== model.attributes.mappedStatus) {
return false;
}
}
if (action === 'moveDown') {
if (collection.indexOf(model) >= collection.total - 1) {
return false;
}
const index = collection.models.findIndex(m => m === model);
const nextModel = this.collection.at(index + 1);
if (!nextModel || nextModel.attributes.mappedStatus !== model.attributes.mappedStatus) {
return false;
}
}
return collection.orderBy === 'order' && collection.order === 'asc';
}
async process(model, action) {
let type;
if (action === 'moveUp') {
type = 'up';
} else if (action === 'moveDown') {
type = 'down';
} else {
throw new Error();
}
Espo.Ui.notifyWait();
await Espo.Ajax.postRequest(`${model.entityType}/${model.id}/move/${type}`, {
whereGroup: this.collection.getWhere(),
});
await this.collection.fetch();
Espo.Ui.notify();
}
}

View File

@@ -0,0 +1,80 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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';
export default class PipelineMoveRowActionHandler extends RowActionHandler {
isAvailable(model, action) {
const collection = model.collection;
if (!collection) {
return false;
}
if (action === 'moveToTop' || action === 'moveUp') {
if (collection.indexOf(model) <= 0) {
return false;
}
}
if (action === 'moveToBottom' || action === 'moveDown') {
if (collection.indexOf(model) >= collection.total - 1) {
return false;
}
}
return collection.orderBy === 'order' && collection.order === 'asc';
}
async process(model, action) {
let type;
if (action === 'moveToTop') {
type = 'top';
} else if (action === 'moveToBottom') {
type = 'bottom';
} else if (action === 'moveUp') {
type = 'up';
} else if (action === 'moveDown') {
type = 'down';
} else {
throw new Error();
}
Espo.Ui.notifyWait();
await Espo.Ajax.postRequest(`${model.entityType}/${model.id}/move/${type}`, {
whereGroup: this.collection.getWhere(),
});
await this.collection.fetch();
Espo.Ui.notify();
}
}

View File

@@ -0,0 +1,147 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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 ActionHandler from 'action-handler';
import {inject} from 'di';
import AclManager from 'acl-manager';
import Metadata from 'metadata';
// noinspection JSUnusedGlobalSymbols
export default class LockActionHandler extends ActionHandler {
/**
* @type {AclManager}
*/
@inject(AclManager)
acl
/**
* @type {Metadata}
*/
@inject(Metadata)
metadata
async actionLock() {
const model = this.view.model;
Espo.Ui.notifyWait();
let attributes;
try {
attributes = await Espo.Ajax.postRequest('Action', {
action: 'lock',
entityType: model.entityType,
id: model.id,
});
} catch (e) {
return;
}
model.setMultiple(attributes, {sync: true});
model.trigger('sync', this.model, null, {});
Espo.Ui.success(this.view.translate('locked', 'messages'));
}
async actionUnlock() {
const model = this.view.model;
Espo.Ui.notifyWait();
let attributes;
try {
attributes = await Espo.Ajax.postRequest('Action', {
action: 'unlock',
entityType: model.entityType,
id: model.id,
});
} catch (e) {
return;
}
model.setMultiple(attributes, {sync: true});
model.trigger('sync', this.model, null, {});
Espo.Ui.success(this.view.translate('unlocked', 'messages'));
}
// noinspection JSUnusedGlobalSymbols
/**
* @return {boolean}
*/
canBeLocked() {
const model = this.view.model;
if (!this.isEnabled(model.entityType)) {
return false;
}
if (this.acl.getPermissionLevel('lock') !== 'yes') {
return false;
}
if (model.attributes.isLocked) {
return false;
}
return true;
}
// noinspection JSUnusedGlobalSymbols
/**
* @return {boolean}
*/
canBeUnlocked() {
const model = this.view.model;
if (!this.isEnabled(model.entityType)) {
return false;
}
if (this.acl.getPermissionLevel('lock') !== 'yes') {
return false;
}
if (!model.attributes.isLocked) {
return false;
}
return true;
}
/**
* @private
* @param {string} entityType
* @return {boolean}
*/
isEnabled(entityType) {
return this.metadata.get(`scopes.${entityType}.lockable`) === true;
}
}

View File

@@ -0,0 +1,145 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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 ActionHandler from 'action-handler';
import {inject} from 'di';
import AclManager from 'acl-manager';
import Language from 'language';
import MassActionHelper from 'helpers/mass-action';
import Metadata from 'metadata';
// noinspection JSUnusedGlobalSymbols
export default class LockMassActionHandler extends ActionHandler {
/**
* @type {AclManager}
*/
@inject(AclManager)
acl
/**
* @type {Language}
*/
@inject(Language)
language
/**
* @type {Metadata}
*/
@inject(Metadata)
metadata
// noinspection JSUnusedGlobalSymbols
async actionLock() {
const msg = this.language.translate('confirmMassLock', 'messages');
await this.view.confirm(msg);
await this.process('lock');
}
// noinspection JSUnusedGlobalSymbols
async actionUnlock() {
const msg = this.language.translate('confirmMassUnlock', 'messages');
await this.view.confirm(msg);
await this.process('unlock');
}
/**
* @private
* @param {string} action
*/
async process(action) {
const helper = new MassActionHelper(this.view);
const params = this.view.getMassActionSelectionPostData();
const idle = !!params.searchParams && helper.checkIsIdle(this.view.collection.total);
const onDone = count => {
const labelKey = action === 'lock' ? 'massLockDone': 'massUnlockDone';
const msg = this.view.translate(labelKey, 'messages')
.replace('{count}', count.toString());
Espo.Ui.success(msg);
};
Espo.Ui.notifyWait();
const result = await Espo.Ajax.postRequest('MassAction', {
entityType: this.view.entityType,
action: action,
params: params,
idle: idle,
});
if (result.id) {
const view = await helper.process(result.id, action)
this.view.listenToOnce(view, 'close:success', result => onDone(result.count));
return;
}
onDone(result.count);
}
// noinspection JSUnusedGlobalSymbols
initLock() {
if (
!this.view.collection ||
!this.isEnabled(this.view.collection.entityType) ||
this.acl.getPermissionLevel('massUpdate') !== 'yes' ||
this.acl.getPermissionLevel('lock') !== 'yes'
) {
this.view.removeMassAction('lock');
}
}
// noinspection JSUnusedGlobalSymbols
initUnlock() {
if (
!this.view.collection ||
!this.isEnabled(this.view.collection.entityType) ||
this.acl.getPermissionLevel('massUpdate') !== 'yes' ||
this.acl.getPermissionLevel('lock') !== 'yes'
) {
this.view.removeMassAction('unlock');
}
}
/**
* @private
* @param {string} entityType
* @return {boolean}
*/
isEnabled(entityType) {
return this.metadata.get(`scopes.${entityType}.lockable`) === true;
}
}

View File

@@ -0,0 +1,163 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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 {inject} from 'di';
import Metadata from 'metadata';
import AssignmentHelper from 'helpers/assignment';
// noinspection JSUnusedGlobalSymbols
export default class LockViewSetup {
/**
* @private
* @type {Metadata}
*/
@inject(Metadata)
metadata
/**
* @private
* @type {import('model').default}
*/
model
/**
* @private
* @type {import('views/record/detail').default}
*/
view
ignoreFieldList = [
'isLocked',
'modifiedAt',
'modifiedBy',
'streamUpdatedAt',
]
/**
* @param {import('views/record/detail').default} view
*/
constructor(view) {
this.view = view;
this.model = view.model;
}
process() {
const entityType = this.model.entityType;
if (this.metadata.get(`scopes.${entityType}.lockable`) !== true) {
return;
}
let wasLocked = false;
const lockedMap = {};
const ignoreFieldList = this.getIgnoreFieldList();
const fieldsDefs = /** @type {Record.<string, Record>} */
this.metadata.get(`entityDefs.${entityType}.fields`) ?? {};
const lockableFields = Object.keys(fieldsDefs)
.filter(field => {
if (ignoreFieldList.includes(field)) {
return false;
}
const defs = fieldsDefs[field];
return !defs.notLockable &&
!defs.readOnly &&
!defs.readOnlyAfterCreate;
});
const controlLocked = () => {
const isLocked = this.model.attributes.isLocked;
if (!isLocked && !wasLocked) {
return;
}
if (isLocked) {
wasLocked = true;
}
lockableFields.forEach(field => {
if (isLocked) {
if (
this.view.recordHelper &&
this.view.recordHelper.getFieldStateParam(field, 'readOnly')
) {
return;
}
lockedMap[field] = true;
this.view.setFieldReadOnly(field);
return;
}
if (!lockedMap[field]) {
return;
}
delete lockedMap[field];
this.view.setFieldNotReadOnly(field);
});
}
controlLocked();
this.view.listenTo(this.model, 'change:isLocked', () => controlLocked());
}
/**
* @private
* @return {string[]}
*/
getIgnoreFieldList() {
const entityType = this.model.entityType;
const ignoreFieldList = [...this.ignoreFieldList];
const helper = new AssignmentHelper;
if (helper.hasCollaboratorsField(entityType)) {
if (helper.hasAssignedUsersField(entityType)) {
if (this.model.getFieldParam('assignedUsers', 'notLockable')) {
ignoreFieldList.push('collaborators');
}
} else if (helper.hasAssignedUserField(entityType)) {
if (this.model.getFieldParam('assignedUser', 'notLockable')) {
ignoreFieldList.push('collaborators');
}
}
}
return ignoreFieldList;
}
}

View File

@@ -0,0 +1,145 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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 {inject} from 'di';
import Metadata from 'metadata';
import AppParams from 'app-params';
import PipelinesHelper from 'helpers/misc/pipelines';
// noinspection JSUnusedGlobalSymbols
export default class PipelineViewSetupHandler {
/**
* @private
* @type {Metadata}
*/
@inject(Metadata)
metadata
/**
* @private
* @type {import('model').default}
*/
model
/**
* @private
* @type {import('views/record/detail').default}
*/
view
/**
* @private
* @type {AppParams}
*/
@inject(AppParams)
appParams
/**
* @private
* @type {{id: string, stages: {id: string, name: string, style: string|null}[]}[]}
*/
pipelines
/**
* @param {import('views/record/detail').default} view
*/
constructor(view) {
this.view = view;
this.model = view.model;
}
process() {
const entityType = this.model.entityType;
if (this.metadata.get(`scopes.${entityType}.pipelines`) !== true) {
return;
}
this.pipelines = new PipelinesHelper().get(entityType);
this.model.onChange({
owner: this.view,
attributes: ['pipelineId'],
callback: o => {
if (!o.ui) {
return;
}
setTimeout(() => this.updateStage(), 1);
},
});
}
/**
* @private
*/
updateStage() {
const pipelineId = this.model.attributes.pipelineId;
if (!pipelineId) {
this.setStageNull();
return;
}
const pipeline = this.pipelines.find(it => it.id === pipelineId);
if (!pipeline) {
this.setStageNull();
return;
}
const stage = pipeline.stages[0];
if (!stage) {
this.setStageNull();
return;
}
this.model.setMultiple({
pipelineStageId: stage.id,
pipelineStageName: stage.name,
});
this.model.trigger('pipeline-changed');
}
/**
* @private
*/
setStageNull() {
this.model.setMultiple({
pipelineStageId: null,
pipelineStageName: null,
});
this.model.trigger('pipeline-changed');
}
}

View File

@@ -0,0 +1,72 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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 StreamViewAuditLogModalView from 'views/stream/modals/view-audit-log';
class ViewAuditLogHandler {
constructor(/** import('views/record/detail').default */view) {
this.view = view;
this.metadata = /** @type {module:metadata} */view.getMetadata();
this.entityType = this.view.entityType;
this.model = /** @type {module:model} */this.view.model;
this.hasAudited =
this.metadata.get(`scopes.${this.entityType}.statusField`) ||
this.model.getFieldList().find(field => this.model.getFieldParam(field, 'audited')) !== undefined;
if (this.entityType === 'User' && !this.view.getUser().isAdmin()) {
this.hasAudited = false;
}
if (this.view.getUser().isPortal()) {
this.hasAudited = false;
}
if (this.view.getAcl().getPermissionLevel('audit') !== 'yes') {
this.hasAudited = false;
}
}
isAvailable() {
return this.hasAudited;
}
show() {
const view = new StreamViewAuditLogModalView({model: this.model});
this.view.assignView('dialog', view)
.then(() => {
view.render();
})
}
}
// noinspection JSUnusedGlobalSymbols
export default ViewAuditLogHandler;

View File

@@ -0,0 +1,120 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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 RelatedListModalView from 'views/modals/related-list';
export default class {
toShow
constructor(/** import('views/record/detail').default */view) {
this.view = view;
this.metadata = /** @type {module:metadata} */view.getMetadata();
this.entityType = this.view.entityType;
this.model = this.view.model;
const level = this.view.getAcl().getPermissionLevel('user');
this.toShow = (level === 'all' || level === 'team') &&
(
this.metadata.get(`scopes.${this.entityType}.object`) ||
this.metadata.get(`scopes.${this.entityType}.acl`)
)
this.view.getAcl().checkScope('User');
}
isAvailable() {
return this.toShow;
}
async show() {
const actionList = this.getActionList();
/** @type {Record[]} */
const listLayout = [
{
name: 'name',
link: true,
view: 'views/user/fields/name',
},
];
//const width = Math.round((100.0 - 40) / (actionList.length + 1));
actionList.forEach(action => {
listLayout.push({
name: 'recordAccessLevel' + action,
customLabel: this.view.translate(action, 'recordActions'),
view: 'views/user/fields/record-access-level',
notSortable: true,
width: 16,
});
});
const view = new RelatedListModalView({
model: this.model,
link: 'usersAccess',
entityType: 'User',
title: this.view.translate('View User Access'),
url: `${this.entityType}/${this.model.id}/usersAccess`,
createDisabled: true,
selectDisabled: true,
massActionsDisabled: true,
maxSize: this.view.getConfig().get('recordsPerPageSmall'),
rowActionsView: null,
listLayout: listLayout,
filter: 'active',
});
await this.view.assignView('dialog', view);
await view.render();
}
/**
* @private
* @return {string[]}
*/
getActionList() {
/** @type {string[]} */
let actionList = this.metadata.get(`scopes.${this.entityType}.aclActionList`);
if (!actionList) {
actionList = [
'read',
'edit',
'delete',
];
if (this.metadata.get(`scopes.${this.entityType}.stream`)) {
actionList.push('stream');
}
}
return actionList.filter(it => it !== 'create');
}
}

View File

@@ -0,0 +1,73 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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.
************************************************************************/
/** @module handlers/row-action */
/**
* @abstract
*/
class RowActionHandler {
/**
* @protected
* @type {import('collection').default}
*/
collection
/**
* @protected
* @type {import('views/record/list').default} view
*/
view
/**
* @param {import('views/record/list').default} view
*/
constructor(view) {
this.view = view;
this.collection = this.view.collection;
}
/**
* @param {import('model').default} model A model.
* @param {string} action An action.
* @return {boolean}
*/
isAvailable(model, action) {
return true;
}
/**
* @abstract
* @param {import('model').default} model A model.
* @param {string} action An action.
*/
process(model, action) {}
}
export default RowActionHandler;

View File

@@ -0,0 +1,70 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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 type {SelectFieldHandler} from 'contracts/relation';
import type ViewHelper from 'view-helper';
import type Model from 'model';
/**
* @since 10.0.0
*/
abstract class SelectFieldRelationHandler<
M extends Model = Model,
P extends Model = Model,
> implements SelectFieldHandler<M> {
/**
* @internal
*/
protected viewHelper: ViewHelper
/**
* A parent model.
*/
protected readonly model: P
/**
* @internal
*/
constructor(viewHelper: ViewHelper, model: P) {
this.viewHelper = viewHelper;
this.model = model;
}
/**
* @inheritDoc
*/
abstract getAttributes(model: M): Promise<Record<string, unknown>>
/**
* @inheritDoc
*/
abstract getClearAttributes(): Promise<Record<string, unknown>>
}
export default SelectFieldRelationHandler;

View File

@@ -0,0 +1,65 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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 type {SelectRelatedFilters, SelectRelatedHandler as SelectRelatedHandlerInterface} from 'contracts/relation';
import type ViewHelper from 'view-helper';
import type Model from 'model';
/**
* Prepares filters for selecting records to relate.
* Use the interface directly rather than extending this class.
*/
abstract class SelectRelatedHandler<M extends Model = Model> implements SelectRelatedHandlerInterface<M> {
/**
* @internal
*/
protected readonly viewHelper: ViewHelper
/**
* @internal
*/
constructor(viewHelper: ViewHelper) {
this.viewHelper = viewHelper;
}
/**
* @inheritDoc
*
* @param model A model.
* @return {Promise<SelectRelatedFilters>} Filters.
*/
async getFilters(model: M): Promise<SelectRelatedFilters> {
// noinspection BadExpressionStatementJS
model;
return {};
}
}
export default SelectRelatedHandler;

View File

@@ -0,0 +1,72 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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 SelectRelatedHandler from 'handlers/select-related';
class SameAccountManySelectRelatedHandler extends SelectRelatedHandler {
/**
* @param {module:model} model
* @return {Promise<module:handlers/select-related~filters>}
*/
getFilters(model) {
const advanced = {};
let accountId = null;
let accountName = null;
if (model.get('accountId')) {
accountId = model.get('accountId');
accountName = model.get('accountName');
}
if (!accountId && model.get('parentType') === 'Account' && model.get('parentId')) {
accountId = model.get('parentId');
accountName = model.get('parentName');
}
if (accountId) {
const nameHash = {};
nameHash[accountId] = accountName;
advanced.accounts = {
field: 'accounts',
type: 'linkedWith',
value: [accountId],
data: {nameHash: nameHash},
};
}
return Promise.resolve({
advanced: advanced,
});
}
}
// noinspection JSUnusedGlobalSymbols
export default SameAccountManySelectRelatedHandler;

View File

@@ -0,0 +1,72 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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 SelectRelatedHandler from 'handlers/select-related';
class SameAccountSelectRelatedHandler extends SelectRelatedHandler {
/**
* @param {module:model} model
* @return {Promise<module:handlers/select-related~filters>}
*/
getFilters(model) {
const advanced = {};
let accountId = null;
let accountName = null;
if (model.get('accountId')) {
accountId = model.get('accountId');
accountName = model.get('accountName');
}
if (!accountId && model.get('parentType') === 'Account' && model.get('parentId')) {
accountId = model.get('parentId');
accountName = model.get('parentName');
}
if (accountId) {
advanced.account = {
attribute: 'accountId',
type: 'equals',
value: accountId,
data: {
type: 'is',
nameValue: accountName,
},
};
}
return Promise.resolve({
advanced: advanced,
});
}
}
// noinspection JSUnusedGlobalSymbols
export default SameAccountSelectRelatedHandler;

View File

@@ -0,0 +1,95 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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.notifyWait();
await Espo.Ajax.putRequest(`Team/${teamId}/userPosition`, {
id: model.id,
position: position,
});
model.setMultiple({teamRole: position});
Espo.Ui.success(this.view.translate('Saved'));
}
}

View File

@@ -0,0 +1,55 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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 SelectRelatedHandler from 'handlers/select-related';
export default class extends SelectRelatedHandler {
/**
* @param {import('model').default} model
*/
async getFilters(model) {
/** @type {string[]|null} */
const accountIds = model.attributes.accountsIds;
if (accountIds && accountIds.length) {
return {
advanced: {
accounts: {
field: 'accounts',
type: 'linkedWith',
value: accountIds,
data: {nameHash: model.attributes.accountsNames || {}},
},
},
};
}
return {};
}
}

View File

@@ -0,0 +1,54 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* 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 {Events} from 'bullbone';
/**
* @mixes Bull.Events
*/
class WorkingTimeRangeHandler {
constructor(view) {
/** @type {module:views/record/edit} */
this.view = view;
}
process() {
this.listenTo(this.view.model, 'change:dateStart', (model, value, o) => {
if (!o.ui || model.get('dateEnd')) {
return;
}
setTimeout(() => model.set('dateEnd', value), 50);
});
}
}
Object.assign(WorkingTimeRangeHandler.prototype, Events);
export default WorkingTimeRangeHandler;