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 1s
Test Integration on MySQL / Test on PHP 8.5 (push) Failing after 1s
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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
<div class="calendar-container no-window-scroll">
{{{calendar}}}
</div>

View File

@@ -0,0 +1,32 @@
{{#if header}}
<div class="row button-container">
<div class="col-sm-4 col-xs-5">
<div class="btn-group range-switch-group">
<button class="btn btn-text btn-icon" data-action="prev"><span class="fas fa-chevron-left"></span></button>
<button class="btn btn-text btn-icon" data-action="next"><span class="fas fa-chevron-right"></span></button>
</div>
<div class="btn-group range-switch-group">
<button class="btn btn-text strong" data-action="today" title="{{todayLabel}}">
<span class="hidden-sm hidden-xs">{{todayLabel}}</span><span class="visible-sm visible-xs">{{todayLabelShort}}</span>
</button>
</div>
<button
class="btn btn-text{{#unless isCustomView}} hidden{{/unless}} btn-icon"
data-action="editCustomView"
title="{{translate 'Edit'}}"
><span class="fas fa-pencil-alt fa-sm"></span></button>
</div>
<div class="date-title col-sm-4 col-xs-7">
<h4><span style="cursor: pointer;" data-action="refresh" title="{{translate 'Refresh'}}"></span></h4></div>
<div class="col-sm-4 col-xs-12">
<div class="btn-group pull-right mode-buttons">
{{{modeButtons}}}
</div>
</div>
</div>
{{/if}}
<div class="calendar"></div>

View File

@@ -0,0 +1,20 @@
{{#if isNew}}
<div class="scope-switcher radio-container">
{{#each scopeList}}
<div>
<label class="radio-label">
<input
type="radio"
name="scope"
class="form-radio"
{{#ifEqual this ../scope}} checked{{/ifEqual}}
value="{{./this}}"
>
{{translate this category='scopeNames'}}
</label>
</div>
{{/each}}
</div>
{{/if}}
<div class="edit-container record no-side-margin">{{{edit}}}</div>

View File

@@ -0,0 +1,53 @@
{{#each visibleModeDataList}}
<button class="btn btn-text strong{{#ifEqual mode ../mode}} active{{/ifEqual}}" data-action="mode" data-mode="{{mode}}" title="{{label}}"><span class="hidden-md hidden-sm hidden-xs">{{label}}</span><span class="visible-md visible-sm visible-xs">{{labelShort}}</span></button>
{{/each}}
<div class="btn-group" role="group">
<button type="button" class="btn btn-text dropdown-toggle" data-toggle="dropdown"><span class="fas fa-ellipsis-h"></span></button>
<ul class="dropdown-menu pull-right">
{{#each hiddenModeDataList}}
<li>
<a
role="button"
tabindex="0"
class="{{#ifEqual mode ../mode}} active{{/ifEqual}}"
data-action="mode"
data-mode="{{mode}}"
>{{label}}</a>
</li>
{{/each}}
{{#if hiddenModeDataList.length}}
<li class="divider"></li>
{{/if}}
{{#each scopeFilterDataList}}
<li>
<a
role="button"
tabindex="0"
data-action="toggleScopeFilter"
data-name="{{scope}}"
>
<span class="fas fa-check filter-check-icon check-icon pull-right{{#if disabled}} hidden{{/if}}"></span>
<div>{{translate scope category='scopeNamesPlural'}}</div>
</a>
</li>
{{/each}}
{{#if hasMoreItems}}
<li class="divider"></li>
{{/if}}
{{#if isCustomViewAvailable}}
<li>
<a
role="button"
tabindex="0"
data-action="createCustomView"
>{{translate 'Create Shared View' scope='Calendar'}}</a>
</li>
{{/if}}
{{#if hasWorkingTimeCalendarLink}}
<li>
<a href="#WorkingTimeCalendar">{{translate 'WorkingTimeCalendar' category='scopeNamesPlural'}}</a>
</li>
{{/if}}
</ul>
</div>

View File

@@ -0,0 +1,55 @@
<link href="{{basePath}}client/modules/crm/css/vis.css" rel="stylesheet">
{{#if header}}
<div class="row button-container">
<div class="col-sm-4 col-xs-12">
<div class="btn-group">
<button
class="btn btn-text btn-icon"
title="{{translate 'Refresh'}}"
data-action="refresh"
><span class="fas fa-sync-alt"></span></button>
<button
class="btn btn-text"
data-action="today"
>{{translate 'Today' scope='Calendar'}}</button>
</div>{{#if calendarTypeSelectEnabled}}<div class="btn-group calendar-type-button-group">
<div class="btn-group " role="group">
<button
type="button"
class="btn btn-text dropdown-toggle"
data-toggle="dropdown"
><span class="calendar-type-label">{{calendarTypeLabel}}</span> <span class="caret"></span></button>
<ul class="dropdown-menu">
{{#each calendarTypeDataList}}
<li>
<a role="button" tabindex="0" data-action="toggleCalendarType" data-name="{{type}}">
<span
class="fas fa-check calendar-type-check-icon pull-right{{#if disabled}} hidden{{/if}}"
></span> {{label}}
</a>
</li>
{{/each}}
</ul>
</div>
<button
class="btn btn-text{{#ifNotEqual calendarType 'shared'}} hidden{{/ifNotEqual}} btn-icon"
data-action="showSharedCalendarOptions"
title="{{translate 'Shared Mode Options' scope='Calendar'}}"
><span class="fas fa-pencil-alt fa-sm"></span></button>
</div>
{{/if}}
</div>
<div class="date-title col-sm-4 hidden-xs">
<h4><span style="cursor: pointer;" data-action="refresh" title="{{translate 'Refresh'}}"></span></h4></div>
<div class="col-sm-4 col-xs-12">
<div class="btn-group pull-right mode-buttons">
{{{modeButtons}}}
</div>
</div>
</div>
{{/if}}
<div class="timeline"></div>

View File

@@ -0,0 +1 @@
{{{value}}}

View File

@@ -0,0 +1,11 @@
<div class="row">
<div class="cell col-md-6">
<div class="field" data-name="link">
<select class="form-control" data-name="link">
{{#each linkList}}
<option value="{{./this}}">{{translate this category='links' scope='TargetList'}}</option>
{{/each}}
</select>
</div>
</div>
</div>

View File

@@ -0,0 +1,21 @@
<div class="container content">
<div class="block-center-md">
<div class="panel panel-default">
<div class="panel-body">
<p>
{{#if isSubscribed}}
<a
class="btn btn-primary{{#if inProcess}} disabled{{/if}}"
data-action="unsubscribe"
>{{translate 'Unsubscribe' scope='Campaign'}}</a>
{{else}}
<a
class="btn btn-default{{#if inProcess}} disabled{{/if}}"
data-action="subscribe"
>{{translate 'Subscribe again' scope='Campaign'}}</a>
{{/if}}
</p>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1 @@
{{#if accountIsInactive}}<del>{{/if}}<span title="{{value}}">{{value}}</span>{{#if accountIsInactive}}</del>{{/if}}

View File

@@ -0,0 +1,9 @@
<div class="page-header">{{{header}}}</div>
<div class="search-container">{{{search}}}</div>
<div class="row">
{{#unless categoriesDisabled}}
<div class="categories-container{{#unless categoriesDisabled}} col-md-3 col-sm-4{{else}} col-md-12{{/unless}}">{{{categories}}}</div>
{{/unless}}
<div class="list-container{{#unless categoriesDisabled}} col-md-9 col-sm-8{{else}} col-md-12{{/unless}}">{{{list}}}</div>
</div>

View File

@@ -0,0 +1,14 @@
<div class="search-container">{{{search}}}</div>
<div class="row">
{{#unless categoriesDisabled}}
<div class="categories-container{{#unless categoriesDisabled}} col-md-3 col-sm-4{{else}} col-md-12{{/unless}}">{{{categories}}}</div>
{{/unless}}
<div class="list-container{{#unless categoriesDisabled}} col-md-9 col-sm-8{{else}} col-md-12{{/unless}}">{{{list}}}</div>
</div>
{{#if createButton}}
<div class="button-container">
<button class="btn btn-default" data-action="create">{{translate 'Create'}}</button>
</div>
{{/if}}

View File

@@ -0,0 +1,36 @@
<div class="container content">
<div class="block-center-md">
<div class="panel panel-default">
<div class="panel-body">
<h4 class="margin-bottom-2x">{{actionData.translatedEntityType}}: {{actionData.eventName}}</h4>
{{#if dateStart}}
<div class="margin-bottom-2x">
{{#if dateStartChanged}}
<div style="text-decoration: line-through;">{{sentDateStart}}</div>
{{/if}}
<div>{{dateStart}}</div>
</div>
{{/if}}
<div>
<span class="label label-{{style}} label-md">{{actionData.translatedStatus}}</span>
&nbsp;<div class="btn-group">
{{#if actionDataList}}
<a role="button" class="dropdown-toggle text-soft" data-toggle="dropdown">
<span class="fas fa-ellipsis-h"></span>
</a>
<ul class="dropdown-menu">
{{#each actionDataList}}
<li>
<a {{#if link}}href="{{link}}"{{/if}}>{{label}}
{{#if active}}<span class="fas fa-check pull-right"></span>{{/if}}
</a>
</li>
{{/each}}
</ul>
{{/if}}
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,10 @@
<div class="page-header">{{{header}}}</div>
<div class="search-container">{{{search}}}</div>
<div class="row">
{{#unless categoriesDisabled}}
<div class="categories-container{{#unless categoriesDisabled}} col-md-3 col-sm-4{{else}} col-md-12{{/unless}}">{{{categories}}}</div>
{{/unless}}
<div class="list-container{{#unless categoriesDisabled}} col-md-9 col-sm-8{{else}} col-md-12{{/unless}}">{{{list}}}</div>
</div>

View File

@@ -0,0 +1,14 @@
<div class="search-container">{{{search}}}</div>
<div class="row">
{{#unless categoriesDisabled}}
<div class="categories-container{{#unless categoriesDisabled}} col-md-3 col-sm-4{{else}} col-md-12{{/unless}}">{{{categories}}}</div>
{{/unless}}
<div class="list-container{{#unless categoriesDisabled}} col-md-9 col-sm-8{{else}} col-md-12{{/unless}}">{{{list}}}</div>
</div>
{{#if createButton}}
<div class="button-container">
<button class="btn btn-default" data-action="create">{{translate 'Create'}}</button>
</div>
{{/if}}

View File

@@ -0,0 +1,24 @@
<div class="header page-header">{{{header}}}</div>
{{#each scopeList}}
<div class="record">
<label style="user-select: none; cursor: pointer;" class="text-large">
<input
type="checkbox"
class="check-scope form-checkbox"
data-scope="{{./this}}"
>
<span>{{translate this category='scopeNames'}}</span>
</label>
<div class="edit-container-{{toDom this}} hide">
{{{var this ../this}}}
</div>
</div>
{{/each}}
<div class="button-container margin-top">
<div class="btn-group">
<button class="btn btn-primary" data-action="convert">{{translate 'Convert' scope='Lead'}}</button>
<button class="btn btn-default" data-action="cancel">{{translate 'Cancel'}}</button>
</div>
</div>

View File

@@ -0,0 +1,5 @@
{{#if value}}
{{{value}}}
{{else}}
<span class="none-value">{{translate 'None'}}</span>
{{/if}}

View File

@@ -0,0 +1,6 @@
<div class="reminders-container"></div>
<button
data-action="addReminder"
class="btn btn-default"
type="button"
><span class="fas fa-plus"></span></button>

View File

@@ -0,0 +1,39 @@
{{#if closeButton~}}
<a
role="button"
tabindex="0"
class="pull-right close"
data-action="close"
aria-hidden="true"
title="{{translate 'Close'}}"
><span class="fas fa-times"></span></a>
{{~/if~}}
{{#if collapseButton~}}
<a
role="button"
tabindex="0"
class="pull-right text-muted"
data-action="collapse"
aria-hidden="true"
title="{{translate 'Collapse'}}"
><span class="fas fa-minus"></span></a>
{{~/if}}
<h4>{{header}}</h4>
<div class="cell form-group">
<div class="field">
<a
href="#{{notificationData.entityType}}/view/{{notificationData.id}}"
data-action="close"
>{{notificationData.name}}</a>
</div>
</div>
<div class="cell form-group" data-name="{{dateField}}">
<div class="field" data-name="{{dateField}}">
{{{date}}}
</div>
</div>

View File

@@ -0,0 +1,3 @@
<div class="list-container">
{{{list}}}
</div>

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 AclPortal from 'acl-portal';
class AccountAclPortal extends AclPortal {
checkInAccount(model) {
const accountIdList = this.getUser().getLinkMultipleIdList('accounts');
if (!accountIdList.length) {
return false;
}
if (~accountIdList.indexOf(model.id)) {
return true;
}
return false;
}
}
export default AccountAclPortal;

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 AclPortal from 'acl-portal';
class ContactAclPortal extends AclPortal {
checkIsOwnContact(model) {
const contactId = this.getUser().get('contactId');
if (!contactId) {
return false;
}
if (contactId === model.id) {
return true;
}
return false;
}
}
export default ContactAclPortal;

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 AclPortal from 'acl-portal';
class DocumentAclPortal extends AclPortal {
// noinspection JSUnusedGlobalSymbols
checkModelEdit(model, data, precise) {
let result = this.checkModel(model, data, 'delete', precise);
if (result) {
return true;
}
if (data.edit === 'account') {
return true;
}
return false;
}
}
export default DocumentAclPortal;

View File

@@ -0,0 +1,33 @@
/************************************************************************
* 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 MeetingAcl from 'modules/crm/acl/meeting';
class CallAcl extends MeetingAcl {}
export default CallAcl;

View File

@@ -0,0 +1,50 @@
/************************************************************************
* 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 Acl from 'acl';
class CampaignTrackingUrlAcl extends Acl {
checkIsOwner(model) {
if (model.has('campaignId')) {
return true;
}
return false;
}
checkInTeam(model) {
if (model.has('campaignId')) {
return true;
}
return false;
}
}
export default CampaignTrackingUrlAcl;

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 Acl from 'acl';
class MassEmailAcl extends Acl {
checkScope(data, action, precise, entityAccessData) {
if (action === 'create') {
return super.checkScope(data, 'edit', precise, entityAccessData);
}
return super.checkScope(data, action, precise, entityAccessData);
}
checkIsOwner(model) {
if (model.has('campaignId')) {
return true;
}
return super.checkIsOwner(model);
}
checkInTeam(model) {
if (model.has('campaignId')) {
return true;
}
return super.checkInTeam(model);
}
}
export default MassEmailAcl;

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 Acl from 'acl';
class MeetingAcl extends Acl {
// noinspection JSUnusedGlobalSymbols
checkModelRead(model, data, precise) {
return this._checkModelCustom('read', model, data, precise);
}
// noinspection JSUnusedGlobalSymbols
checkModelStream(model, data, precise) {
return this._checkModelCustom('stream', model, data, precise);
}
_checkModelCustom(action, model, data, precise) {
let result = this.checkModel(model, data, action, precise);
if (result) {
return true;
}
if (data === false) {
return false;
}
let d = data || {};
if (d[action] === 'no') {
return false;
}
if (model.has('usersIds')) {
if (~(model.get('usersIds') || []).indexOf(this.getUser().id)) {
return true;
}
}
else if (precise) {
return null;
}
return result;
}
}
export default MeetingAcl;

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.
************************************************************************/
import Controller from 'controller';
export default class ActivitiesController extends Controller {
checkAccess(action) {
return this.getAcl().check('Activities');
}
// noinspection JSUnusedGlobalSymbols
actionActivities(options) {
this.processList('activities', options.entityType, options.id, options.targetEntityType);
}
actionHistory(options) {
this.processList('history', options.entityType, options.id, options.targetEntityType);
}
/**
* @private
* @param {'activities'|'history'} type
* @param {string} entityType
* @param {string} id
* @param {string} targetEntityType
*/
async processList(type, entityType, id, targetEntityType) {
const viewName = 'modules/crm/views/activities/list';
const model = await this.modelFactory.create(entityType)
model.id = id;
await model.fetch({main: true});
const collection = await this.collectionFactory.create(targetEntityType);
collection.url = `Activities/${model.entityType}/${id}/${type}/list/${targetEntityType}`;
this.main(viewName, {
scope: entityType,
model: model,
collection: collection,
link: `${type}_${targetEntityType}`,
type: type,
});
}
}

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 Controller from 'controller';
class CalendarController extends Controller {
checkAccess() {
if (this.getAcl().check('Calendar')) {
return true;
}
return false;
}
// noinspection JSUnusedGlobalSymbols
actionShow(options) {
this.actionIndex(options);
}
actionIndex(options) {
this.handleCheckAccess('');
this.main('crm:views/calendar/calendar-page', {
date: options.date,
mode: options.mode,
userId: options.userId,
userName: options.userName,
});
}
}
export default CalendarController;

View File

@@ -0,0 +1,47 @@
/************************************************************************
* 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 Controller from 'controller';
class EventConfirmationController extends Controller {
// noinspection JSUnusedGlobalSymbols
actionConfirmEvent(actionData) {
const viewName = this.getMetadata().get(['clientDefs', 'EventConfirmation', 'confirmationView']) ||
'crm:views/event-confirmation/confirmation';
this.entire(viewName, {
actionData: actionData,
}, view => {
view.render();
});
}
}
// noinspection JSUnusedGlobalSymbols
export default EventConfirmationController;

View File

@@ -0,0 +1,39 @@
/************************************************************************
* 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 RecordController from 'controllers/record';
class LeadController extends RecordController {
// noinspection JSUnusedGlobalSymbols
actionConvert(options) {
this.main('crm:views/lead/convert', {id: options.id});
}
}
export default LeadController;

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 RecordController from 'controllers/record';
export default class TaskController extends RecordController {
actionCreate(options) {
const attributes = {...options.attributes};
if (options.emailId) {
attributes.emailId = options.emailId;
options.attributes = attributes;
}
super.actionCreate(options);
}
}

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 Controller from 'controller';
class TrackingUrlController extends Controller {
// noinspection JSUnusedGlobalSymbols
actionDisplayMessage(data) {
const viewName = data.view || 'crm:views/campaign/tracking-url';
this.entire(viewName, {
message: data.message,
template: data.template,
}, view => {
view.render();
});
}
}
export default TrackingUrlController

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 Controller from 'controller';
class UnsubscribeController extends Controller {
// noinspection JSUnusedGlobalSymbols
actionUnsubscribe(data) {
const viewName = data.view || 'crm:views/campaign/unsubscribe';
this.entire(viewName, {
actionData: data.actionData,
template: data.template,
}, view => {
view.render();
});
}
}
export default UnsubscribeController;

View File

@@ -0,0 +1,40 @@
/************************************************************************
* 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 CreateRelated from 'handlers/create-related';
class MassEmailsCreateHandler extends CreateRelated {
getAttributes(model) {
return Promise.resolve({
name: model.get('name') + ' ' + this.viewHelper.dateTime.getToday(),
});
}
}
export default MassEmailsCreateHandler;

View File

@@ -0,0 +1,91 @@
/************************************************************************
* 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 CaseDetailActionHandler extends ActionHandler {
close() {
const model = this.view.model;
model.save({status: 'Closed'}, {patch: true})
.then(() => {
Espo.Ui.success(this.view.translate('Closed', 'labels', 'Case'));
});
}
reject() {
const model = this.view.model;
model.save({status: 'Rejected'}, {patch: true})
.then(() => {
Espo.Ui.success(this.view.translate('Rejected', 'labels', 'Case'));
});
}
// noinspection JSUnusedGlobalSymbols
isCloseAvailable() {
return this.isStatusAvailable('Closed');
}
// noinspection JSUnusedGlobalSymbols
isRejectAvailable() {
return this.isStatusAvailable('Rejected');
}
isStatusAvailable(status) {
const model = this.view.model;
const acl = this.view.getAcl();
const metadata = this.view.getMetadata();
/** @type {string[]} */
const notActualStatuses = metadata.get('entityDefs.Case.fields.status.notActualOptions') || [];
if (notActualStatuses.includes(model.get('status'))) {
return false;
}
if (!acl.check(model, 'edit')) {
return false;
}
if (!acl.checkField(model.entityType, 'status', 'edit')) {
return false;
}
const statusList = metadata.get(['entityDefs', 'Case', 'fields', 'status', 'options']) || [];
if (!statusList.includes(status)) {
return false;
}
return true;
}
}
export default CaseDetailActionHandler;

View File

@@ -0,0 +1,93 @@
/************************************************************************
* 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 RemindersHandler {
/**
* @param {import('views/record/detail').default} view
*/
constructor(view) {
this.view = view;
/** @type {import('model').default} */
this.model = view.model;
/** @type {import('models/user').default} */
this.user = this.view.getUser();
this.ignoreStatusList = [
...(this.view.getMetadata().get(['scopes', this.view.entityType, 'completedStatusList']) || []),
...(this.view.getMetadata().get(['scopes', this.view.entityType, 'canceledStatusList']) || []),
];
}
process() {
this.control();
this.listenTo(this.model, 'change', () => {
if (
!this.model.hasChanged('assignedUserId') &&
!this.model.hasChanged('usersIds') &&
!this.model.hasChanged('assignedUsersIds') &&
!this.model.hasChanged('status')
) {
return;
}
this.control();
});
}
control() {
const usersIds = /** @type {string[]} */this.model.get('usersIds') || [];
const assignedUsersIds = /** @type {string[]} */this.model.get('assignedUsersIds') || [];
if (
!this.ignoreStatusList.includes(this.model.get('status')) &&
(
this.model.get('assignedUserId') === this.user.id ||
usersIds.includes(this.user.id) ||
assignedUsersIds.includes(this.user.id)
)
) {
this.view.showField('reminders');
return;
}
this.view.hideField('reminders');
}
}
Object.assign(RemindersHandler.prototype, Events);
// noinspection JSUnusedGlobalSymbols
export default RemindersHandler;

View File

@@ -0,0 +1,136 @@
/************************************************************************
* 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';
class MoveActionHandler extends RowActionHandler {
isAvailable(model, action) {
return model.collection &&
model.collection.orderBy === 'order' &&
model.collection.order === 'asc';
}
process(model, action) {
if (action === 'moveToTop') {
this.moveToTop(model);
return;
}
if (action === 'moveToBottom') {
this.moveToBottom(model);
return;
}
if (action === 'moveUp') {
this.moveUp(model);
return;
}
if (action === 'moveDown') {
this.moveDown(model);
}
}
moveToTop(model) {
const index = this.collection.indexOf(model);
if (index === 0) {
return;
}
Espo.Ui.notifyWait();
Espo.Ajax.postRequest('KnowledgeBaseArticle/action/moveToTop', {
id: model.id,
whereGroup: this.collection.getWhere(),
}).then(() => {
this.collection.fetch()
.then(() => Espo.Ui.notify(false));
});
}
moveUp(model) {
const index = this.collection.indexOf(model);
if (index === 0) {
return;
}
Espo.Ui.notifyWait();
Espo.Ajax.postRequest('KnowledgeBaseArticle/action/moveUp', {
id: model.id,
whereGroup: this.collection.getWhere(),
}).then(() => {
this.collection.fetch()
.then(() => Espo.Ui.notify(false));
});
}
moveDown(model) {
const index = this.collection.indexOf(model);
if ((index === this.collection.length - 1) && (this.collection.length === this.collection.total)) {
return;
}
Espo.Ui.notifyWait();
Espo.Ajax.postRequest('KnowledgeBaseArticle/action/moveDown', {
id: model.id,
whereGroup: this.collection.getWhere(),
}).then(() => {
this.collection.fetch()
.then(() => Espo.Ui.notify(false));
});
}
moveToBottom(model) {
const index = this.collection.indexOf(model);
if ((index === this.collection.length - 1) && (this.collection.length === this.collection.total)) {
return;
}
Espo.Ui.notifyWait();
Espo.Ajax.postRequest('KnowledgeBaseArticle/action/moveToBottom', {
id: model.id,
whereGroup: this.collection.getWhere(),
}).then(() => {
this.collection.fetch()
.then(() => Espo.Ui.notify(false));
});
}
}
export default MoveActionHandler;

View File

@@ -0,0 +1,149 @@
/************************************************************************
* 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';
class SendInEmailHandler extends RowActionHandler {
isAvailable(model, action) {
return this.view.getAcl().checkScope('Email', 'create');
}
process(model, action) {
const parentModel = this.view.getParentView().model;
const modelFactory = this.view.getModelFactory();
const collectionFactory = this.view.getCollectionFactory();
Espo.Ui.notifyWait();
model.fetch()
.then(() => {
return new Promise(resolve => {
if (
parentModel.get('contactsIds') &&
parentModel.get('contactsIds').length
) {
collectionFactory.create('Contact', contactList => {
const contactListFinal = [];
contactList.url = 'Case/' + parentModel.id + '/contacts';
contactList.fetch().then(() => {
contactList.forEach(contact => {
if (contact.id === parentModel.get('contactId')) {
contactListFinal.unshift(contact);
} else {
contactListFinal.push(contact);
}
});
resolve(contactListFinal);
});
});
return;
}
if (parentModel.get('accountId')) {
modelFactory.create('Account', account => {
account.id = parentModel.get('accountId');
account.fetch()
.then(() => resolve([account]));
});
return;
}
if (parentModel.get('leadId')) {
modelFactory.create('Lead', lead => {
lead.id = parentModel.get('leadId');
lead.fetch()
.then(() => resolve([lead]));
});
return;
}
resolve([]);
});
})
.then(list => {
const attributes = {
parentType: 'Case',
parentId: parentModel.id,
parentName: parentModel.get('name'),
name: '[#' + parentModel.get('number') + ']',
};
attributes.to = '';
attributes.cc = '';
attributes.nameHash = {};
list.forEach((model, i) => {
if (model.get('emailAddress')) {
if (i === 0) {
attributes.to += model.get('emailAddress') + ';';
} else {
attributes.cc += model.get('emailAddress') + ';';
}
attributes.nameHash[model.get('emailAddress')] = model.get('name');
}
});
Espo.loader.require('crm:knowledge-base-helper', Helper => {
const helper = new Helper(this.view.getLanguage());
helper.getAttributesForEmail(model, attributes, attributes => {
const viewName = this.view.getMetadata().get('clientDefs.Email.modalViews.compose') ||
'views/modals/compose-email';
this.view.createView('composeEmail', viewName, {
attributes: attributes,
selectTemplateDisabled: true,
signatureDisabled: true,
}, view => {
Espo.Ui.notify(false);
view.render();
this.view.listenToOnce(view, 'after:send', () => {
parentModel.trigger('after:relate');
});
});
});
});
})
.catch(() => {
Espo.Ui.notify(false);
});
}
}
export default SendInEmailHandler;

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 {inject} from 'di';
import AppParams from 'app-params';
// noinspection JSUnusedGlobalSymbols
export default class MeetingExternalServiceHandler{
/**
* @private
* @type {AppParams}
*/
@inject(AppParams)
appParams
/**
* @param {import('views/record/detail').default} view
*/
constructor(view) {
this.view = view;
}
process() {
this.controlField();
this.view.listenTo(this.view.model, 'change:externalService', () => this.controlField());
}
/**
* @private
*/
controlField() {
const model = this.view.model;
if (model.attributes.externalService) {
this.view.showField('externalService');
return;
}
const list = this.appParams.get('meetingServices') ?? [];
if (!list.length) {
this.view.hideField('externalService');
}
}
}

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 CreateRelated from 'handlers/create-related';
class ContactsCreateHandler extends CreateRelated {
getAttributes(model) {
const attributes = {};
if (model.get('accountId')) {
attributes['accountsIds'] = [model.get('accountId')]
}
return Promise.resolve(attributes);
}
}
export default ContactsCreateHandler;

View File

@@ -0,0 +1,56 @@
/************************************************************************
* 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 Metadata from 'metadata';
import {inject} from 'di';
// noinspection JSUnusedGlobalSymbols
export default class extends DefaultsPreparator {
/**
* @private
* @type {Metadata}
*/
@inject(Metadata)
metadata
prepare(model) {
const probabilityMap = this.metadata.get('entityDefs.Opportunity.fields.stage.probabilityMap') || {};
const stage = model.attributes.stage;
const attributes = {};
if (stage in probabilityMap) {
attributes.probability = probabilityMap[stage];
}
return Promise.resolve(attributes);
}
}

View File

@@ -0,0 +1,89 @@
/************************************************************************
* 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 Metadata from 'metadata';
import Ui from 'ui';
class DetailActions extends ActionHandler {
/**
* @type {string[]}
* @private
*/
historyStatusList
/**
* @private
* @type {string|null}
*/
completedStatusValue
@inject(Metadata)
metadata
constructor(view) {
super(view);
/** @var string[]*/
const completedStatusList = this.metadata.get(`scopes.Task.completedStatusList`, []);
this.historyStatusList = [
...completedStatusList,
...this.metadata.get(`scopes.Task.canceledStatusList`, []),
];
this.completedStatusValue = completedStatusList[0] ?? null;
}
async complete() {
const model = this.view.model;
Ui.notifyWait();
await model.save({status: this.completedStatusValue}, {patch: true});
// Needed for calendar update.
this.view.trigger('after:save', model);
Ui.success(this.view.getLanguage().translateOption('Completed', 'status', 'Task'));
}
// noinspection JSUnusedGlobalSymbols
/**
* @return {boolean}
*/
isCompleteAvailable() {
const status = this.view.model.attributes.status;
return !this.historyStatusList.includes(status);
}
}
export default DetailActions;

View File

@@ -0,0 +1,90 @@
/************************************************************************
* 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 Metadata from 'metadata';
import Ui from 'ui';
export default class TaskMenuHandler extends ActionHandler {
/**
* @type {string[]}
* @private
*/
historyStatusList
/**
* @private
* @type {string|null}
*/
completedStatusValue
@inject(Metadata)
metadata
constructor(view) {
super(view);
/** @var string[]*/
const completedStatusList = this.metadata.get(`scopes.Task.completedStatusList`, []);
this.historyStatusList = [
...completedStatusList,
...this.metadata.get(`scopes.Task.canceledStatusList`, []),
];
this.completedStatusValue = completedStatusList[0] ?? null;
}
async complete() {
const model = this.view.model;
Ui.notifyWait();
await model.save({status: this.completedStatusValue}, {patch: true});
Ui.success(this.view.getLanguage().translateOption('Completed', 'status', 'Task'));
}
// noinspection JSUnusedGlobalSymbols
/**
* @return {boolean}
*/
isCompleteAvailable() {
const status = this.view.model.attributes.status;
const view = /** @type {import('views/detail').default} */this.view;
if (view.getRecordView().isEditMode()) {
return false;
}
return !this.historyStatusList.includes(status);
}
}

View File

@@ -0,0 +1,97 @@
/************************************************************************
* 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 RemindersHandler {
/**
* @param {import('views/record/detail').default} view
*/
constructor(view) {
this.view = view;
this.model = view.model;
this.user = this.view.getUser();
this.ignoreStatusList = [
...(this.view.getMetadata().get(['scopes', this.view.entityType, 'completedStatusList']) || []),
...(this.view.getMetadata().get(['scopes', this.view.entityType, 'canceledStatusList']) || []),
];
}
process() {
this.control();
this.listenTo(this.model, 'change', () => {
if (
!this.model.hasChanged('assignedUserId') &&
!this.model.hasChanged('assignedUsersIds') &&
!this.model.hasChanged('dateEnd') &&
!this.model.hasChanged('dateEndDate') &&
!this.model.hasChanged('status')
) {
return;
}
this.control();
});
}
control() {
if (!this.model.attributes.dateEnd && !this.model.attributes.dateEndDate) {
this.view.hideField('reminders');
return;
}
/** @type {string[]} */
const assignedUsersIds = this.model.attributes.assignedUsersIds || [];
if (
!this.ignoreStatusList.includes(this.model.attributes.status) &&
(
this.model.attributes.assignedUserId === this.user.id ||
assignedUsersIds.includes(this.user.id)
)
) {
this.view.showField('reminders');
return;
}
this.view.hideField('reminders');
}
}
Object.assign(RemindersHandler.prototype, Events);
// noinspection JSUnusedGlobalSymbols
export default RemindersHandler;

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 Ajax from 'ajax';
/**
* @todo Move to modules/crm/helpers.
*/
class KnowledgeBaseHelper {
/**
* @param {module:language} language
*/
constructor(language) {
this.language = language;
}
getAttributesForEmail(model, attributes, callback) {
attributes = attributes || {};
attributes.body = model.get('body');
if (attributes.name) {
attributes.name = attributes.name + ' ';
} else {
attributes.name = '';
}
attributes.name += this.language.translate('KnowledgeBaseArticle', 'scopeNames') + ': ' +
model.get('name');
Ajax.postRequest('KnowledgeBaseArticle/action/getCopiedAttachments', {
id: model.id,
parentType: 'Email',
field : 'attachments',
}).then(data => {
attributes.attachmentsIds = data.ids;
attributes.attachmentsNames = data.names;
attributes.isHtml = true;
callback(attributes);
});
}
}
export default KnowledgeBaseHelper;

View File

@@ -0,0 +1,212 @@
/************************************************************************
* 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 _ from 'underscore';
import {Events} from 'bullbone';
const Handler = function (view) {
this.view = view;
};
_.extend(Handler.prototype, {
process: function () {
this.listenTo(this.view, 'after:render', () => this.initDragDrop());
this.listenTo(this.view, 'remove', () => this.disable());
},
disable: function () {
const $el = this.view.$el.parent();
/** @type {Element} */
const el = $el.get(0);
$el.off('drop');
if (!el) {
return;
}
if (!this.onDragoverBind) {
return;
}
el.removeEventListener('dragover', this.onDragoverBind);
el.removeEventListener('dragenter', this.onDragenterBind);
el.removeEventListener('dragleave', this.onDragleaveBind);
},
initDragDrop: function () {
this.disable();
const $el = this.view.$el.parent();
const el = $el.get(0);
$el.on('drop', e => {
e.preventDefault();
e.stopPropagation();
e = e.originalEvent;
if (
e.dataTransfer &&
e.dataTransfer.files &&
e.dataTransfer.files.length === 1 &&
this.dropEntered
) {
this.removeDrop();
this.create(e.dataTransfer.files[0]);
return;
}
this.removeDrop($el);
});
this.dropEntered = false;
this.onDragoverBind = this.onDragover.bind(this);
this.onDragenterBind = this.onDragenter.bind(this);
this.onDragleaveBind = this.onDragleave.bind(this);
el.addEventListener('dragover', this.onDragoverBind);
el.addEventListener('dragenter', this.onDragenterBind);
el.addEventListener('dragleave', this.onDragleaveBind);
},
renderDrop: function () {
this.dropEntered = true;
const $backdrop =
$('<div class="dd-backdrop">')
.css('pointer-events', 'none')
.append('<span class="fas fa-paperclip"></span>')
.append(' ')
.append(
$('<span>')
.text(this.view.getLanguage().translate('Create Document', 'labels', 'Document'))
);
this.view.$el.append($backdrop);
},
removeDrop: function () {
this.view.$el.find('> .dd-backdrop').remove();
this.dropEntered = false;
},
create: function (file) {
this.view
.actionQuickCreate()
.then(view => {
const fileView = view.getRecordView().getFieldView('file');
if (!fileView) {
const msg = "No 'file' field on the layout.";
Espo.Ui.error(msg);
console.error(msg);
return;
}
if (fileView.isRendered()) {
fileView.uploadFile(file);
return;
}
this.listenToOnce(fileView, 'after:render', () => {
fileView.uploadFile(file);
});
});
},
/**
* @param {DragEvent} e
*/
onDragover: function (e) {
e.preventDefault();
},
/**
* @param {DragEvent} e
*/
onDragenter: function (e) {
e.preventDefault();
if (!e.dataTransfer.types || !e.dataTransfer.types.length) {
return;
}
if (!~e.dataTransfer.types.indexOf('Files')) {
return;
}
if (!this.dropEntered) {
this.renderDrop();
}
},
/**
* @param {DragEvent} e
*/
onDragleave: function (e) {
e.preventDefault();
if (!this.dropEntered) {
return;
}
let fromElement = e.fromElement || e.relatedTarget;
if (
fromElement &&
$.contains(this.view.$el.parent().get(0), fromElement)
) {
return;
}
if (
fromElement &&
fromElement.parentNode &&
fromElement.parentNode.toString() === '[object ShadowRoot]'
) {
return;
}
this.removeDrop();
},
});
Object.assign(Handler.prototype, Events);
// noinspection JSUnusedGlobalSymbols
export default Handler;

View File

@@ -0,0 +1,32 @@
/************************************************************************
* 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 DetailView from 'views/detail';
/** Left for bc. */
export default class extends DetailView {}

View File

@@ -0,0 +1,135 @@
/************************************************************************
* 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 AddressFieldView from 'views/fields/address';
export default class extends AddressFieldView {
copyFrom = 'billingAddress'
setup() {
super.setup();
this.addActionHandler('copyFromBilling', () => this.copy());
this.attributePartList = this.getMetadata().get(['fields', 'address', 'actualFields']) || [];
this.allAddressAttributeList = [];
this.attributePartList.forEach(part => {
this.allAddressAttributeList.push(this.copyFrom + Espo.Utils.upperCaseFirst(part));
this.allAddressAttributeList.push(this.name + Espo.Utils.upperCaseFirst(part));
});
this.listenTo(this.model, 'change', () => {
let isChanged = false;
for (const attribute of this.allAddressAttributeList) {
if (this.model.hasChanged(attribute)) {
isChanged = true;
break;
}
}
if (!isChanged) {
return;
}
if (!this.isEditMode() || !this.isRendered() || !this.copyButtonElement) {
return;
}
if (this.toShowCopyButton()) {
this.copyButtonElement.classList.remove('hidden');
} else {
this.copyButtonElement.classList.add('hidden');
}
});
}
afterRender() {
super.afterRender();
if (this.mode === this.MODE_EDIT && this.element) {
const label = this.translate('Copy Billing', 'labels', 'Account');
const button = this.copyButtonElement = document.createElement('button');
button.classList.add('btn', 'btn-default', 'btn-sm', 'action');
button.textContent = label;
button.setAttribute('data-action', 'copyFromBilling')
if (!this.toShowCopyButton()) {
button.classList.add('hidden');
}
this.element.append(button);
}
}
/**
* @private
*/
copy() {
const fieldFrom = this.copyFrom;
Object.keys(this.getMetadata().get('fields.address.fields') || {})
.forEach(attr => {
const destField = this.name + Espo.Utils.upperCaseFirst(attr);
const sourceField = fieldFrom + Espo.Utils.upperCaseFirst(attr);
this.model.set(destField, this.model.get(sourceField));
});
}
/**
* @private
* @return {boolean}
*/
toShowCopyButton() {
let billingIsNotEmpty = false;
let shippingIsNotEmpty = false;
this.attributePartList.forEach(part => {
const attribute1 = this.copyFrom + Espo.Utils.upperCaseFirst(part);
if (this.model.get(attribute1)) {
billingIsNotEmpty = true;
}
const attribute2 = this.name + Espo.Utils.upperCaseFirst(part);
if (this.model.get(attribute2)) {
shippingIsNotEmpty = true;
}
});
return billingIsNotEmpty && !shippingIsNotEmpty;
}
}

View File

@@ -0,0 +1,113 @@
/************************************************************************
* 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 ListRelatedView from 'views/list-related';
export default class ActivitiesListView extends ListRelatedView {
createButton = false
unlinkDisabled = true
filtersDisabled = true
allResultDisabled = true;
setup() {
this.rowActionsView = 'views/record/row-actions/default';
super.setup();
this.type = this.options.type;
}
getHeader() {
const name = this.model.get('name') || this.model.id;
const recordUrl = `#${this.scope}/view/${this.model.id}`;
const $name =
$('<a>')
.attr('href', recordUrl)
.addClass('font-size-flexible title')
.text(name)
.css('user-select', 'none');
if (this.model.get('deleted')) {
$name.css('text-decoration', 'line-through');
}
const headerIconHtml = this.getHelper().getScopeColorIconHtml(this.foreignScope);
const scopeLabel = this.getLanguage().translate(this.scope, 'scopeNamesPlural');
let $root = $('<span>').text(scopeLabel);
if (!this.rootLinkDisabled) {
$root = $('<span>')
.append(
$('<a>')
.attr('href', '#' + this.scope)
.addClass('action')
.attr('data-action', 'navigateToRoot')
.text(scopeLabel)
);
}
$root.css('user-select', 'none');
if (headerIconHtml) {
$root.prepend(headerIconHtml);
}
const linkLabel = this.type === 'history' ? this.translate('History') : this.translate('Activities');
const $link = $('<span>').text(linkLabel);
$link
.css('user-select', 'none');
const $target = $('<span>').text(this.translate(this.foreignScope, 'scopeNamesPlural'));
$target
.css('user-select', 'none')
.css('cursor', 'pointer')
.attr('data-action', 'fullRefresh')
.attr('title', this.translate('clickToRefresh', 'messages'))
return this.buildHeaderHtml([
$root,
$name,
$link,
$target,
]);
}
/**
* @inheritDoc
*/
updatePageTitle() {
this.setPageTitle(this.translate(this.foreignScope, 'scopeNamesPlural'));
}
}

View File

@@ -0,0 +1,41 @@
/************************************************************************
* 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 MultiEnumFieldView from 'views/fields/multi-enum';
export default class extends MultiEnumFieldView {
setupOptions() {
const entityType = this.model.get('name');
const options = this.getMetadata().get(['entityDefs', entityType, 'fields', 'status', 'options']) || [];
this.params.options = [...options];
this.params.translation = `${entityType}.options.status`;
}
}

View File

@@ -0,0 +1,601 @@
/************************************************************************
* 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 View from 'view';
import CalendarEditViewModal from 'crm:views/calendar/modals/edit-view';
import {inject} from 'di';
import ShortcutManager from 'helpers/site/shortcut-manager';
import DebounceHelper from 'helpers/util/debounce';
import WebSocketManager from 'web-socket-manager';
import Utils from 'utils';
class CalendarPage extends View {
template = 'crm:calendar/calendar-page'
//el = '#main'
fullCalendarModeList = [
'month',
'agendaWeek',
'agendaDay',
'basicWeek',
'basicDay',
'listWeek',
]
events = {
/** @this CalendarPage */
'click [data-action="createCustomView"]': function () {
this.createCustomView();
},
/** @this CalendarPage */
'click [data-action="editCustomView"]': function () {
this.editCustomView();
},
}
/**
* @private
* @type {ShortcutManager}
*/
@inject(ShortcutManager)
shortcutManager
/**
* @private
* @type {DebounceHelper|null}
*/
webSocketDebounceHelper = null
/**
* @private
* @type {number}
*/
webSocketDebounceInterval = 500
/**
* @private
* @type {number}
*/
webSocketBlockInterval = 1000
/**
* @private
* @type {WebSocketManager}
*/
@inject(WebSocketManager)
webSocketManager
/**
* A shortcut-key => action map.
*
* @protected
* @type {?Object.<string, function (KeyboardEvent): void>}
*/
shortcutKeys = {
/** @this CalendarPage */
'Home': function (e) {
this.handleShortcutKeyHome(e);
},
/** @this CalendarPage */
'Numpad7': function (e) {
this.handleShortcutKeyHome(e);
},
/** @this CalendarPage */
'Numpad4': function (e) {
this.handleShortcutKeyArrowLeft(e);
},
/** @this CalendarPage */
'Numpad6': function (e) {
this.handleShortcutKeyArrowRight(e);
},
/** @this CalendarPage */
'ArrowLeft': function (e) {
this.handleShortcutKeyArrowLeft(e);
},
/** @this CalendarPage */
'ArrowRight': function (e) {
this.handleShortcutKeyArrowRight(e);
},
/** @this CalendarPage */
'Control+ArrowLeft': function (e) {
this.handleShortcutKeyArrowLeft(e);
},
/** @this CalendarPage */
'Control+ArrowRight': function (e) {
this.handleShortcutKeyArrowRight(e);
},
/** @this CalendarPage */
'Minus': function (e) {
this.handleShortcutKeyMinus(e);
},
/** @this CalendarPage */
'Equal': function (e) {
this.handleShortcutKeyPlus(e);
},
/** @this CalendarPage */
'NumpadSubtract': function (e) {
this.handleShortcutKeyMinus(e);
},
/** @this CalendarPage */
'NumpadAdd': function (e) {
this.handleShortcutKeyPlus(e);
},
/** @this CalendarPage */
'Digit1': function (e) {
this.handleShortcutKeyDigit(e, 1);
},
/** @this CalendarPage */
'Digit2': function (e) {
this.handleShortcutKeyDigit(e, 2);
},
/** @this CalendarPage */
'Digit3': function (e) {
this.handleShortcutKeyDigit(e, 3);
},
/** @this CalendarPage */
'Digit4': function (e) {
this.handleShortcutKeyDigit(e, 4);
},
/** @this CalendarPage */
'Digit5': function (e) {
this.handleShortcutKeyDigit(e, 5);
},
/** @this CalendarPage */
'Digit6': function (e) {
this.handleShortcutKeyDigit(e, 6);
},
/** @this CalendarPage */
'Control+Space': function (e) {
this.handleShortcutKeyControlSpace(e);
},
}
/**
* @param {{
* userId?: string,
* userName?: string|null,
* mode?: string|null,
* date?: string|null,
* }} options
*/
constructor(options) {
super(options);
this.options = options;
}
setup() {
this.mode = this.mode || this.options.mode || null;
this.date = this.date || this.options.date || null;
if (!this.mode) {
this.mode = this.getStorage().get('state', 'calendarMode') || null;
if (this.mode && this.mode.indexOf('view-') === 0) {
const viewId = this.mode.slice(5);
const calendarViewDataList = this.getPreferences().get('calendarViewDataList') || [];
let isFound = false;
calendarViewDataList.forEach(item => {
if (item.id === viewId) {
isFound = true;
}
});
if (!isFound) {
this.mode = null;
}
if (this.options.userId) {
this.mode = null;
}
}
}
this.shortcutManager.add(this, this.shortcutKeys);
this.on('remove', () => {
this.shortcutManager.remove(this);
});
if (!this.mode || ~this.fullCalendarModeList.indexOf(this.mode) || this.mode.indexOf('view-') === 0) {
this.setupCalendar();
} else if (this.mode === 'timeline') {
this.setupTimeline();
}
this.initWebSocket();
}
/**
* @private
*/
initWebSocket() {
if (this.options.userId && this.getUser().id !== this.options.userId) {
return;
}
this.webSocketDebounceHelper = new DebounceHelper({
interval: this.webSocketDebounceInterval,
blockInterval: this.webSocketBlockInterval,
handler: () => this.handleWebSocketUpdate(),
});
if (!this.webSocketManager.isEnabled()) {
const testHandler = () => this.webSocketDebounceHelper.process();
this.on('remove', () => window.removeEventListener('calendar-update', testHandler));
// For testing purpose.
window.addEventListener('calendar-update', testHandler);
return;
}
this.webSocketManager.subscribe('calendarUpdate', () => this.webSocketDebounceHelper.process());
this.on('remove', () => this.webSocketManager.unsubscribe('calendarUpdate'));
}
/**
* @private
*/
handleWebSocketUpdate() {
this.getCalendarView()?.actionRefresh({suppressLoadingAlert: true});
}
/**
* @private
*/
onSave() {
if (!this.webSocketDebounceHelper) {
return;
}
this.webSocketDebounceHelper.block()
}
afterRender() {
this.$el.focus();
}
updateUrl(trigger) {
let url = '#Calendar/show';
if (this.mode || this.date) {
url += '/';
}
if (this.mode) {
url += 'mode=' + this.mode;
}
if (this.date) {
url += '&date=' + this.date;
}
if (this.options.userId) {
url += '&userId=' + this.options.userId;
if (this.options.userName) {
url += '&userName=' + encodeURIComponent(this.options.userName);
}
}
this.getRouter().navigate(url, {trigger: trigger});
}
/**
* @private
*/
setupCalendar() {
const viewName = this.getMetadata().get(['clientDefs', 'Calendar', 'calendarView']) ||
'crm:views/calendar/calendar';
this.createView('calendar', viewName, {
date: this.date,
userId: this.options.userId,
userName: this.options.userName,
mode: this.mode,
fullSelector: '#main > .calendar-container',
onSave: () => this.onSave(),
}, view => {
let initial = true;
this.listenTo(view, 'view', (date, mode) => {
this.date = date;
this.mode = mode;
if (!initial) {
this.updateUrl();
}
initial = false;
});
this.listenTo(view, 'change:mode', (mode, refresh) => {
this.mode = mode;
if (!this.options.userId) {
this.getStorage().set('state', 'calendarMode', mode);
}
if (refresh) {
this.updateUrl(true);
return;
}
if (!~this.fullCalendarModeList.indexOf(mode)) {
this.updateUrl(true);
}
this.$el.focus();
});
});
}
/**
* @private
*/
setupTimeline() {
const viewName = this.getMetadata().get(['clientDefs', 'Calendar', 'timelineView']) ||
'crm:views/calendar/timeline';
this.createView('calendar', viewName, {
date: this.date,
userId: this.options.userId,
userName: this.options.userName,
fullSelector: '#main > .calendar-container',
onSave: () => this.onSave(),
}, view => {
let initial = true;
this.listenTo(view, 'view', (date, mode) => {
this.date = date;
this.mode = mode;
if (!initial) {
this.updateUrl();
}
initial = false;
});
this.listenTo(view, 'change:mode', mode => {
this.mode = mode;
if (!this.options.userId) {
this.getStorage().set('state', 'calendarMode', mode);
}
this.updateUrl(true);
});
});
}
updatePageTitle() {
this.setPageTitle(this.translate('Calendar', 'scopeNames'));
}
async createCustomView() {
const view = new CalendarEditViewModal({
afterSave: data => {
this.mode = `view-${data.id}`;
this.date = null;
this.updateUrl(true);
},
});
await this.assignView('modal', view);
await view.render();
}
async editCustomView() {
const viewId = this.getCalendarView().viewId;
if (!viewId) {
return;
}
const view = new CalendarEditViewModal({
id: viewId,
afterSave: () => {
this.getCalendarView().setupMode();
this.getCalendarView().reRender();
},
afterRemove: () => {
this.mode = null;
this.date = null;
this.updateUrl(true);
},
});
await this.assignView('modal', view);
await view.render();
}
/**
* @private
* @return {import('./calendar').default|import('./timeline').default}
*/
getCalendarView() {
return this.getView('calendar');
}
/**
* @private
* @param {KeyboardEvent} e
*/
handleShortcutKeyHome(e) {
if (Utils.isKeyEventInTextInput(e)) {
return;
}
e.preventDefault();
this.getCalendarView().actionToday();
}
/**
* @private
* @param {KeyboardEvent} e
*/
handleShortcutKeyArrowLeft(e) {
if (Utils.isKeyEventInTextInput(e)) {
return;
}
if (
e.target instanceof HTMLElement &&
e.target.parentElement instanceof HTMLLIElement
) {
return;
}
e.preventDefault();
this.getCalendarView().actionPrevious();
}
/**
* @private
* @param {KeyboardEvent} e
*/
handleShortcutKeyArrowRight(e) {
if (Utils.isKeyEventInTextInput(e)) {
return;
}
if (
e.target instanceof HTMLElement &&
e.target.parentElement instanceof HTMLLIElement
) {
return;
}
e.preventDefault();
this.getCalendarView().actionNext();
}
/**
* @private
* @param {KeyboardEvent} e
*/
handleShortcutKeyMinus(e) {
if (Utils.isKeyEventInTextInput(e)) {
return;
}
if (!this.getCalendarView().actionZoomOut) {
return;
}
e.preventDefault();
this.getCalendarView().actionZoomOut();
}
/**
* @private
* @param {KeyboardEvent} e
*/
handleShortcutKeyPlus(e) {
if (Utils.isKeyEventInTextInput(e)) {
return;
}
if (!this.getCalendarView().actionZoomIn) {
return;
}
e.preventDefault();
this.getCalendarView().actionZoomIn();
}
/**
* @private
* @param {KeyboardEvent} e
* @param {Number} digit
*/
handleShortcutKeyDigit(e, digit) {
if (Utils.isKeyEventInTextInput(e)) {
return;
}
const modeList = this.getCalendarView().hasView('modeButtons') ?
this.getCalendarView()
.getModeButtonsView()
.getModeDataList(true)
.map(item => item.mode) :
this.getCalendarView().modeList;
const mode = modeList[digit - 1];
if (!mode) {
return;
}
e.preventDefault();
if (mode === this.mode) {
this.getCalendarView().actionRefresh();
return;
}
this.getCalendarView().selectMode(mode);
}
/**
* @private
* @param {KeyboardEvent} e
*/
handleShortcutKeyControlSpace(e) {
if (Utils.isKeyEventInTextInput(e)) {
return;
}
if (!this.getCalendarView().createEvent) {
return;
}
e.preventDefault();
this.getCalendarView().createEvent();
}
}
// noinspection JSUnusedGlobalSymbols
export default CalendarPage;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,40 @@
/************************************************************************
* 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 LinkMultipleFieldView from 'views/fields/link-multiple';
export default class CalendarSharedViewTeamsFieldView extends LinkMultipleFieldView {
foreignScope = 'Team'
getSelectBoolFilterList() {
if (this.getAcl().getPermissionLevel('userCalendar') === 'team') {
return ['onlyMy'];
}
}
}

View File

@@ -0,0 +1,47 @@
/************************************************************************
* 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 LinkMultipleFieldView from 'views/fields/link-multiple';
class CalendarUsersFieldView extends LinkMultipleFieldView {
foreignScope = 'User'
sortable = true
getSelectBoolFilterList() {
if (this.getAcl().getPermissionLevel('userCalendar') === 'team') {
return ['onlyMyTeam'];
}
}
getSelectPrimaryFilterName() {
return 'active';
}
}
export default CalendarUsersFieldView;

View File

@@ -0,0 +1,299 @@
/************************************************************************
* 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 ModalView from 'views/modal';
import Model from 'model';
import EditForModalRecordView from 'views/record/edit-for-modal';
import EnumFieldView from 'views/fields/enum';
import VarcharFieldView from 'views/fields/varchar';
import CalendarSharedViewTeamsFieldView from 'crm:views/calendar/fields/teams';
export default class CalendarEditViewModal extends ModalView {
// language=Handlebars
templateContent = `
<div class="record-container no-side-margin">{{{record}}}</div>
`
className = 'dialog dialog-record'
/**
* @private
* @type {EditForModalRecordView}
*/
recordView
/**
* @param {{
* afterSave?: function({id: string}): void,
* afterRemove?: function(): void,
* id?: string,
* }} options
*/
constructor(options) {
super();
this.options = options;
}
setup() {
const id = this.options.id;
this.buttonList = [
{
name: 'cancel',
label: 'Cancel',
onClick: () => this.actionCancel(),
},
];
this.isNew = !id;
const calendarViewDataList = this.getPreferences().get('calendarViewDataList') || [];
if (this.isNew) {
this.buttonList.unshift({
name: 'save',
label: 'Create',
style: 'danger',
onClick: () => this.actionSave(),
});
} else {
this.dropdownItemList.push({
name: 'remove',
label: 'Remove',
onClick: () => this.actionRemove(),
});
this.buttonList.unshift({
name: 'save',
label: 'Save',
style: 'primary',
onClick: () => this.actionSave(),
});
}
const model = new Model();
model.name = 'CalendarView';
const modelData = {};
if (!this.isNew) {
calendarViewDataList.forEach(item => {
if (id === item.id) {
modelData.teamsIds = item.teamIdList || [];
modelData.teamsNames = item.teamNames || {};
modelData.id = item.id;
modelData.name = item.name;
modelData.mode = item.mode;
}
});
} else {
modelData.name = this.translate('Shared', 'labels', 'Calendar');
let foundCount = 0;
calendarViewDataList.forEach(item => {
if (item.name.indexOf(modelData.name) === 0) {
foundCount++;
}
});
if (foundCount) {
modelData.name += ' ' + foundCount;
}
modelData.id = id;
modelData.teamsIds = this.getUser().get('teamsIds') || [];
modelData.teamsNames = this.getUser().get('teamsNames') || {};
}
model.set(modelData);
this.recordView = new EditForModalRecordView({
model: model,
detailLayout: [
{
rows: [
[
{
view: new VarcharFieldView({
name: 'name',
labelText: this.translate('name', 'fields'),
params: {
required: true,
},
})
},
{
view: new EnumFieldView({
name: 'mode',
labelText: this.translate('mode', 'fields', 'DashletOptions'),
params: {
translation: 'DashletOptions.options.mode',
options: this.getMetadata().get('clientDefs.Calendar.sharedViewModeList') || [],
},
})
}
],
[
{
view: new CalendarSharedViewTeamsFieldView({
name: 'teams',
labelText: this.translate('teams', 'fields'),
params: {
required: true
},
})
},
false
]
]
}
]
});
this.assignView('record', this.recordView);
if (this.isNew) {
this.headerText = this.translate('Create Shared View', 'labels', 'Calendar');
} else {
this.headerText = this.translate('Edit Shared View', 'labels', 'Calendar') + ' · ' +
modelData.name;
}
}
async actionSave() {
if (this.recordView.validate()) {
return;
}
const modelData = this.recordView.fetch();
const calendarViewDataList = this.getPreferences().get('calendarViewDataList') || [];
const data = {
name: modelData.name,
teamIdList: modelData.teamsIds,
teamNames: modelData.teamsNames,
mode: modelData.mode,
id: undefined,
};
if (this.isNew) {
data.id = Math.random().toString(36).substring(2, 12);
calendarViewDataList.push(data);
} else {
data.id = this.getView('record').model.id;
calendarViewDataList.forEach((item, i) => {
if (item.id === data.id) {
calendarViewDataList[i] = data;
}
});
}
Espo.Ui.notify(this.translate('saving', 'messages'));
this.disableButton('save');
this.disableButton('remove');
try {
await this.getPreferences().save(
{
calendarViewDataList: calendarViewDataList,
},
{patch: true}
)
} catch (e) {
this.enableButton('remove');
this.enableButton('save');
return;
}
Espo.Ui.notify();
this.trigger('after:save', data);
if (this.options.afterSave) {
this.options.afterSave(data);
}
this.close();
}
async actionRemove() {
await this.confirm(this.translate('confirmation', 'messages'));
this.disableButton('save');
this.disableButton('remove');
const id = this.options.id;
if (!id) {
return;
}
const newCalendarViewDataList = [];
const calendarViewDataList = this.getPreferences().get('calendarViewDataList') || [];
calendarViewDataList.forEach(item => {
if (item.id !== id) {
newCalendarViewDataList.push(item);
}
});
Espo.Ui.notifyWait();
try {
await this.getPreferences().save({
calendarViewDataList: newCalendarViewDataList,
}, {patch: true})
} catch (e) {
this.enableButton('remove');
this.enableButton('save');
return;
}
Espo.Ui.notify();
this.trigger('after:remove');
if (this.options.afterRemove) {
this.options.afterRemove();
}
this.close();
}
}

View File

@@ -0,0 +1,280 @@
/************************************************************************
* 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 EditModalView from 'views/modals/edit';
class CalenderEditModalView extends EditModalView {
template = 'crm:calendar/modals/edit'
scopeList = [
'Meeting',
'Call',
'Task',
]
data() {
return {
scopeList: this.scopeList,
scope: this.scope,
isNew: !(this.id),
};
}
additionalEvents = {
/** @this CalenderEditModalView */
'change .scope-switcher input[name="scope"]': function () {
Espo.Ui.notifyWait();
const prevScope = this.scope;
const scope = $('.scope-switcher input[name="scope"]:checked').val();
this.scope = scope;
this.getModelFactory().create(this.scope, model => {
model.populateDefaults();
let attributes = this.getRecordView().fetch();
attributes = {...attributes, ...this.getRecordView().model.getClonedAttributes()};
this.filterAttributesForEntityType(attributes, scope, prevScope);
model.set(attributes);
this.model = model;
this.createRecordView(model, (view) => {
view.render();
view.notify(false);
});
this.handleAccess(model);
});
},
}
/**
* @param {Record} attributes
* @param {string} entityType
* @param {string} previousEntityType
*/
filterAttributesForEntityType(attributes, entityType, previousEntityType) {
if (entityType === 'Task' || previousEntityType === 'Task') {
delete attributes.reminders;
}
this.getHelper()
.fieldManager
.getEntityTypeFieldList(entityType, {type: 'enum'})
.forEach(field => {
if (!(field in attributes)) {
return;
}
const options = this.getMetadata().get(['entityDefs', entityType, 'fields', field, 'options']) || [];
const value = attributes[field];
if (!~options.indexOf(value)) {
delete attributes[field];
}
});
}
createRecordView(model, callback) {
if (!this.id && !this.dateIsChanged) {
if (this.options.dateStart && this.options.dateEnd) {
this.model.set('dateStart', this.options.dateStart);
this.model.set('dateEnd', this.options.dateEnd);
}
if (this.options.allDay) {
const allDayScopeList = this.getMetadata().get('clientDefs.Calendar.allDayScopeList') || [];
if (~allDayScopeList.indexOf(this.scope)) {
this.model.set('dateStart', null);
this.model.set('dateEnd', null);
this.model.set('dateStartDate', null);
this.model.set('dateEndDate', this.options.dateEndDate);
if (this.options.dateEndDate !== this.options.dateStartDate) {
this.model.set('dateStartDate', this.options.dateStartDate);
}
}
else if (this.getMetadata().get(['entityDefs', this.scope, 'fields', 'dateStartDate'])) {
this.model.set('dateStart', null);
this.model.set('dateEnd', null);
this.model.set('dateStartDate', this.options.dateStartDate);
this.model.set('dateEndDate', this.options.dateEndDate);
this.model.set('isAllDay', true);
}
else {
this.model.set('isAllDay', false);
this.model.set('dateStartDate', null);
this.model.set('dateEndDate', null);
}
}
}
this.listenTo(this.model, 'change:dateStart', (m, value, o) => {
if (o.ui) {
this.dateIsChanged = true;
}
});
this.listenTo(this.model, 'change:dateEnd', (m, value, o) => {
if (o.ui || o.updatedByDuration) {
this.dateIsChanged = true;
}
});
super.createRecordView(model, callback);
}
handleAccess(model) {
if (
this.id &&
!this.getAcl().checkModel(model, 'edit') || !this.id &&
!this.getAcl().checkModel(model, 'create')
) {
this.hideButton('save');
this.hideButton('fullForm');
this.$el.find('button[data-name="save"]').addClass('hidden');
this.$el.find('button[data-name="fullForm"]').addClass('hidden');
}
else {
this.showButton('save');
this.showButton('fullForm');
}
if (!this.getAcl().checkModel(model, 'delete')) {
this.hideButton('remove');
} else {
this.showButton('remove');
}
}
afterRender() {
super.afterRender();
if (this.hasView('edit')) {
const model = this.getView('edit').model;
if (model) {
this.handleAccess(model);
}
}
}
setup() {
this.events = {
...this.additionalEvents,
...this.events,
};
this.scopeList = Espo.Utils.clone(this.options.scopeList || this.scopeList);
this.enabledScopeList = this.options.enabledScopeList || this.scopeList;
if (!this.options.id && !this.options.scope) {
const scopeList = [];
this.scopeList.forEach((scope) => {
if (this.getAcl().check(scope, 'create')) {
if (~this.enabledScopeList.indexOf(scope)) {
scopeList.push(scope);
}
}
});
this.scopeList = scopeList;
const calendarDefaultEntity = scopeList[0];
if (calendarDefaultEntity && ~this.scopeList.indexOf(calendarDefaultEntity)) {
this.options.scope = calendarDefaultEntity;
} else {
this.options.scope = this.scopeList[0] || null;
}
if (this.scopeList.length === 0) {
this.remove();
return;
}
}
super.setup();
if (!this.id) {
this.$header = $('<a>')
.attr('title', this.translate('Full Form'))
.attr('role', 'button')
.attr('data-action', 'fullForm')
.addClass('action')
.text(this.translate('Create', 'labels', 'Calendar'));
}
if (this.id) {
this.buttonList.splice(1, 0, {
name: 'remove',
text: this.translate('Remove'),
onClick: () => this.actionRemove(),
});
}
this.once('after:save', () => {
this.$el.find('.scope-switcher').remove();
})
}
actionRemove() {
const model = this.getView('edit').model;
this.confirm(this.translate('removeRecordConfirmation', 'messages'), () => {
const $buttons = this.dialog.$el.find('.modal-footer button');
$buttons.addClass('disabled');
model.destroy()
.then(() => {
this.trigger('after:delete', model);
this.dialog.close();
})
.catch(() => {
$buttons.removeClass('disabled');
});
});
}
setupActionItems() {}
}
export default CalenderEditModalView;

View File

@@ -0,0 +1,144 @@
/************************************************************************
* 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 ModalView from 'views/modal';
import Model from 'model';
import EditForModalRecordView from 'views/record/edit-for-modal';
import CalendarUsersFieldView from 'crm:views/calendar/fields/users';
export default class TimelineSharedOptionsModalView extends ModalView {
className = 'dialog dialog-record'
templateContent = `
<div class="record-container no-side-margin">{{{record}}}</div>
`
/**
* @private
* @type {EditForModalRecordView}
*/
recordView
/**
*
* @param {{
* users: {id: string, name: string}[],
* onApply: function({
* users: {id: string, name: string}[],
* }),
* }} options
*/
constructor(options) {
super(options);
this.options = options;
}
setup() {
this.buttonList = [
{
name: 'save',
label: 'Save',
style: 'primary',
onClick: () => this.actionSave(),
},
{
name: 'cancel',
label: 'Cancel',
onClick: () => this.actionClose(),
},
];
this.headerText = this.translate('timeline', 'modes', 'Calendar') + ' · ' +
this.translate('Shared Mode Options', 'labels', 'Calendar')
const users = this.options.users;
const userIdList = [];
const userNames = {};
users.forEach(item => {
userIdList.push(item.id);
userNames[item.id] = item.name;
});
this.model = new Model({
usersIds: userIdList,
usersNames: userNames,
});
this.recordView = new EditForModalRecordView({
model: this.model,
detailLayout: [
{
rows: [
[
{
view: new CalendarUsersFieldView({
name: 'users',
}),
},
false
]
]
}
]
});
this.assignView('record', this.recordView);
}
/**
* @private
*/
actionSave() {
const data = this.recordView.processFetch();
if (this.recordView.validate()) {
return;
}
/** @type {{id: string, name: string}[]} */
const users = [];
const userIds = this.model.attributes.usersIds || [];
userIds.forEach(id => {
users.push({
id: id,
name: (data.usersNames || {})[id] || id
});
});
this.options.onApply({users: users})
this.close();
}
}

View File

@@ -0,0 +1,158 @@
/************************************************************************
* 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 View from 'view';
/** @module modules/crm/views/calendar/mode-buttons */
class CalendarModeButtons extends View {
template = 'crm:calendar/mode-buttons'
visibleModeListCount = 3
data() {
const scopeFilterList = Espo.Utils.clone(this.scopeList);
scopeFilterList.unshift('all');
const scopeFilterDataList = [];
this.scopeList.forEach(scope => {
const o = {scope: scope};
if (!this.getCalendarParentView().enabledScopeList.includes(scope)) {
o.disabled = true;
}
scopeFilterDataList.push(o);
});
return {
mode: this.mode,
visibleModeDataList: this.getVisibleModeDataList(),
hiddenModeDataList: this.getHiddenModeDataList(),
scopeFilterDataList: scopeFilterDataList,
isCustomViewAvailable: this.isCustomViewAvailable,
hasMoreItems: this.isCustomViewAvailable,
hasWorkingTimeCalendarLink: this.getAcl().checkScope('WorkingTimeCalendar'),
};
}
/**
* @return {
* import('modules/crm/views/calendar/calendar').default|
* import('modules/crm/views/calendar/timeline').default
* }
*/
getCalendarParentView() {
// noinspection JSValidateTypes
return this.getParentView();
}
setup() {
this.isCustomViewAvailable = this.options.isCustomViewAvailable;
this.modeList = this.options.modeList;
this.scopeList = this.options.scopeList;
this.mode = this.options.mode;
}
/**
* @param {boolean} [originalOrder]
* @return {Object.<string, *>[]}
*/
getModeDataList(originalOrder) {
const list = [];
this.modeList.forEach(name => {
const o = {
mode: name,
label: this.translate(name, 'modes', 'Calendar'),
labelShort: this.translate(name, 'modes', 'Calendar').substring(0, 2),
};
list.push(o);
});
if (this.isCustomViewAvailable) {
(this.getPreferences().get('calendarViewDataList') || []).forEach(item => {
item = Espo.Utils.clone(item);
item.mode = 'view-' + item.id;
item.label = item.name;
item.labelShort = (item.name || '').substring(0, 2);
list.push(item);
});
}
if (originalOrder) {
return list;
}
let currentIndex = -1;
list.forEach((item, i) => {
if (item.mode === this.mode) {
currentIndex = i;
}
});
return list;
}
getVisibleModeDataList() {
const fullList = this.getModeDataList();
const current = fullList.find(it => it.mode === this.mode);
const list = fullList.slice(0, this.visibleModeListCount);
if (current && !list.find(it => it.mode === this.mode)) {
list.push(current);
}
return list;
}
getHiddenModeDataList() {
const fullList = this.getModeDataList();
const list = [];
fullList.forEach((o, i) => {
if (i < this.visibleModeListCount) {
return;
}
list.push(o);
});
return list;
}
}
export default CalendarModeButtons;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,31 @@
/************************************************************************
* 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 MeetingDetailView from 'modules/crm/views/meeting/detail';
export default class extends MeetingDetailView {}

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.
************************************************************************/
import AttendeesFieldView from 'modules/crm/views/meeting/fields/attendees';
export default class extends AttendeesFieldView {
getAttributeList() {
return [
...super.getAttributeList(),
'phoneNumbersMap',
]
}
getDetailLinkHtml(id, name) {
const html = super.getDetailLinkHtml(id, name);
const key = this.foreignScope + '_' + id;
const phoneNumbersMap = this.model.get('phoneNumbersMap') || {};
if (!(key in phoneNumbersMap)) {
return html;
}
const number = phoneNumbersMap[key];
const $item = $(html);
// @todo Format phone number.
$item
.append(
' ',
$('<span>').addClass('text-muted middle-dot'),
' ',
$('<a>')
.attr('href', 'tel:' + number)
.attr('data-phone-number', number)
.attr('data-action', 'dial')
.addClass('small')
.text(number)
)
return $('<div>')
.append($item)
.get(0).outerHTML;
}
}

View File

@@ -0,0 +1,96 @@
/************************************************************************
* 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 DatetimeFieldView from 'views/fields/datetime';
import moment from 'moment';
class DateStartCallFieldView extends DatetimeFieldView {
setup() {
super.setup();
this.notActualStatusList = [
...(this.getMetadata().get(`scopes.${this.entityType}.completedStatusList`) || []),
...(this.getMetadata().get(`scopes.${this.entityType}.canceledStatusList`) || []),
];
}
getAttributeList() {
return [
...super.getAttributeList(),
'dateEnd',
'status',
];
}
data() {
let style;
const status = this.model.get('status');
if (
status &&
!this.notActualStatusList.includes(status) &&
(this.mode === this.MODE_DETAIL || this.mode === this.MODE_LIST)
) {
if (this.isDateInPast('dateEnd')) {
style = 'danger';
} else if (this.isDateInPast('dateStart')) {
style = 'warning';
}
}
// noinspection JSValidateTypes
return {
...super.data(),
style: style,
};
}
/**
* @private
* @param {string} field
* @return {boolean}
*/
isDateInPast(field) {
const value = this.model.get(field);
if (value) {
const d = this.getDateTime().toMoment(value);
const now = moment().tz(this.getDateTime().timeZone || 'UTC');
if (d.unix() < now.unix()) {
return true;
}
}
return false;
}
}
export default DateStartCallFieldView;

View File

@@ -0,0 +1,31 @@
/************************************************************************
* 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 ContactsFieldView from 'modules/crm/views/call/fields/contacts';
export default class extends ContactsFieldView {}

View File

@@ -0,0 +1,109 @@
/************************************************************************
* 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 DetailRecordView from 'views/record/detail';
export default class extends DetailRecordView {
duplicateAction = true
setupActionItems() {
super.setupActionItems();
if (!this.getAcl().checkModel(this.model, 'edit')) {
return;
}
const historyStatusList = this.getMetadata().get(`scopes.${this.entityType}.historyStatusList`) || [];
if (
!historyStatusList.includes('Held') ||
!historyStatusList.includes('Not Held')
) {
return;
}
this.dropdownItemList.push({
'label': 'Set Held',
'name': 'setHeld',
onClick: () => this.actionSetHeld(),
iconClass: 'fas fa-check',
});
this.dropdownItemList.push({
'label': 'Set Not Held',
'name': 'setNotHeld',
onClick: () => this.actionSetNotHeld(),
});
const control = () => {
if (
historyStatusList.includes(this.model.attributes.status) ||
!this.getAcl().checkModel(this.model, 'edit')
) {
this.hideActionItem('setHeld');
this.hideActionItem('setNotHeld');
} else {
this.showActionItem('setHeld');
this.showActionItem('setNotHeld');
}
};
control();
this.model.onSync({
owner: this,
callback: () => control(),
});
}
manageAccessEdit(second) {
super.manageAccessEdit(second);
if (second) {
if (!this.getAcl().checkModel(this.model, 'edit', true)) {
this.hideActionItem('setHeld');
this.hideActionItem('setNotHeld');
}
}
}
actionSetHeld() {
this.model.save({status: 'Held'}, {patch: true})
.then(() => {
Espo.Ui.success(this.translate('Saved'));
});
}
actionSetNotHeld() {
this.model.save({status: 'Not Held'}, {patch: true})
.then(() => {
Espo.Ui.success(this.translate('Saved'));
});
}
}

View File

@@ -0,0 +1,31 @@
/************************************************************************
* 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 EditRecordView from 'views/record/edit';
export default class extends EditRecordView {}

View File

@@ -0,0 +1,42 @@
/************************************************************************
* 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.
************************************************************************/
define('crm:views/call/record/list-expanded', ['views/record/list-expanded', 'crm:views/call/record/list'],
function (Dep, List) {
return Dep.extend({
actionSetHeld: function (data) {
List.prototype.actionSetHeld.call(this, data);
},
actionSetNotHeld: function (data) {
List.prototype.actionSetNotHeld.call(this, data);
},
});
});

View File

@@ -0,0 +1,136 @@
/************************************************************************
* 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 ListRecordView from 'views/record/list';
export default class extends ListRecordView {
rowActionsView = 'modules/crm/views/call/record/row-actions/default'
setup() {
super.setup();
if (
this.getAcl().checkScope(this.entityType, 'edit') &&
this.getAcl().checkField(this.entityType, 'status', 'edit')
) {
this.massActionList.push('setHeld');
this.massActionList.push('setNotHeld');
}
}
/**
* @protected
* @param {Record} data
*/
async actionSetHeld(data) {
const id = data.id;
if (!id) {
return;
}
const model = this.collection.get(id);
if (!model) {
return;
}
Espo.Ui.notify(this.translate('saving', 'messages'));
await model.save({status: 'Held'}, {patch: true});
Espo.Ui.success(this.translate('Saved'));
}
/**
* @protected
* @param {Record} data
*/
async actionSetNotHeld(data) {
const id = data.id;
if (!id) {
return;
}
const model = this.collection.get(id);
if (!model) {
return;
}
Espo.Ui.notify(this.translate('saving', 'messages'));
await model.save({status: 'Not Held'}, {patch: true});
Espo.Ui.success(this.translate('Saved'));
}
// noinspection JSUnusedGlobalSymbols
async massActionSetHeld() {
const data = {};
data.ids = this.checkedList;
Espo.Ui.notify(this.translate('saving', 'messages'));
await Espo.Ajax.postRequest(`${this.collection.entityType}/action/massSetHeld`, data);
Espo.Ui.success(this.translate('Saved'));
await this.collection.fetch();
data.ids.forEach(id => {
if (this.collection.get(id)) {
this.checkRecord(id);
}
});
}
// noinspection JSUnusedGlobalSymbols
async massActionSetNotHeld() {
const data = {};
data.ids = this.checkedList;
Espo.Ui.notify(this.translate('saving', 'messages'));
await Espo.Ajax.postRequest(`${this.collection.entityType}/action/massSetNotHeld`, data);
Espo.Ui.success(this.translate('Saved'));
await this.collection.fetch();
data.ids.forEach(id => {
if (this.collection.get(id)) {
this.checkRecord(id);
}
});
}
}

View File

@@ -0,0 +1,31 @@
/************************************************************************
* 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 DashletMeetingRowActionsView from 'modules/crm/views/meeting/record/row-actions/dashlet';
export default class extends DashletMeetingRowActionsView {}

View File

@@ -0,0 +1,31 @@
/************************************************************************
* 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 MeetingDefaultRowActionsView from 'modules/crm/views/meeting/record/row-actions/default';
export default class extends MeetingDefaultRowActionsView {}

View File

@@ -0,0 +1,102 @@
/************************************************************************
* 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.
************************************************************************/
define('crm:views/campaign-log-record/fields/data', ['views/fields/base'], function (Dep) {
return Dep.extend({
listTemplate: 'crm:campaign-log-record/fields/data/detail',
getValueForDisplay: function () {
let action = this.model.get('action');
switch (action) {
case 'Sent':
case 'Opened':
if (
this.model.get('objectId') &&
this.model.get('objectType') &&
this.model.get('objectName')
) {
return $('<a>')
.attr('href', '#' + this.model.get('objectType') + '/view/' + this.model.get('objectId'))
.text(this.model.get('objectName'))
.get(0).outerHTML;
}
return $('<span>')
.text(this.model.get('stringData') || '')
.get(0).outerHTML;
case 'Clicked':
if (
this.model.get('objectId') &&
this.model.get('objectType') &&
this.model.get('objectName')
) {
return $('<a>')
.attr('href', '#' + this.model.get('objectType') + '/view/' + this.model.get('objectId'))
.text(this.model.get('objectName'))
.get(0).outerHTML;
}
return $('<span>')
.text(this.model.get('stringData') || '')
.get(0).outerHTML;
case 'Opted Out':
return $('<span>')
.text(this.model.get('stringData') || '')
.addClass('text-danger')
.get(0).outerHTML;
case 'Bounced':
let emailAddress = this.model.get('stringData');
let type = this.model.get('stringAdditionalData');
let typeLabel = type === 'Hard' ?
this.translate('hard', 'labels', 'Campaign') :
this.translate('soft', 'labels', 'Campaign')
return $('<span>')
.append(
$('<span>')
.addClass('label label-default')
.text(typeLabel),
' ',
$('<s>')
.text(emailAddress)
.addClass(type === 'Hard' ? 'text-danger' : '')
)
.get(0).outerHTML;
}
return '';
},
});
});

View File

@@ -0,0 +1,34 @@
/************************************************************************
* 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.
************************************************************************/
define('crm:views/campaign-tracking-url/record/edit-small', ['views/record/edit-small'], function (Dep) {
return Dep.extend({
});
});

View File

@@ -0,0 +1,33 @@
/************************************************************************
* 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.
************************************************************************/
define('crm:views/campaign-tracking-url/record/edit', ['views/record/edit'], function (Dep) {
return Dep.extend({
});
});

View File

@@ -0,0 +1,31 @@
/************************************************************************
* 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 DetailView from 'views/detail';
export default class extends DetailView {}

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 IntFieldView from 'views/fields/int';
// noinspection JSUnusedGlobalSymbols
export default class extends IntFieldView {
setup() {
this.percentageField = this.name.substr(0, this.name.length - 5) + 'Percentage';
super.setup();
}
getAttributeList() {
const list = super.getAttributeList();
if (this.model.hasField(this.percentageField)) {
list.push(this.percentageField);
}
return list;
}
getValueForDisplay() {
const percentageFieldName = this.percentageField;
let value = this.model.get(this.name);
const percentageValue = this.model.get(percentageFieldName);
if (
percentageValue != null &&
percentageValue
) {
value += ' ' + '(' + this.model.get(percentageFieldName) + '%)';
}
return value;
}
}

View File

@@ -0,0 +1,47 @@
/************************************************************************
* 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.
************************************************************************/
define('crm:views/campaign/fields/template', ['views/fields/link'], function (Dep) {
return Dep.extend({
createDisabled: true,
getSelectFilters: function () {
return {
entityType: {
type: 'in',
attribute: 'entityType',
value: [
this.getMetadata().get(['entityDefs', 'Campaign', 'fields', this.name, 'targetEntityType'])
],
}
};
}
});
});

View File

@@ -0,0 +1,87 @@
/************************************************************************
* 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.
************************************************************************/
define('crm:views/campaign/modals/mail-merge-pdf', ['views/modal', 'ui/select'],
function (Dep, /** module:ui/select */ Select) {
return Dep.extend({
template: 'crm:campaign/modals/mail-merge-pdf',
data: function () {
return {
linkList: this.linkList,
};
},
setup: function () {
Dep.prototype.setup.call(this);
this.headerText = this.translate('Generate Mail Merge PDF', 'labels', 'Campaign');
var linkList = ['contacts', 'leads', 'accounts', 'users'];
this.linkList = [];
linkList.forEach(link => {
if (!this.model.get(link + 'TemplateId')) {
return;
}
let targetEntityType = this.getMetadata()
.get(['entityDefs', 'TargetList', 'links', link, 'entity']);
if (!this.getAcl().checkScope(targetEntityType)) {
return;
}
this.linkList.push(link);
});
this.buttonList.push({
name: 'proceed',
label: 'Proceed',
style: 'danger'
});
this.buttonList.push({
name: 'cancel',
label: 'Cancel'
});
},
afterRender: function () {
Select.init(this.$el.find('.field[data-name="link"] select'));
},
actionProceed: function () {
let link = this.$el.find('.field[data-name="link"] select').val();
this.trigger('proceed', link);
},
});
});

View File

@@ -0,0 +1,98 @@
/************************************************************************
* 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.
************************************************************************/
define('crm:views/campaign/record/detail', ['views/record/detail'], function (Dep) {
return Dep.extend({
duplicateAction: true,
setupActionItems: function () {
Dep.prototype.setupActionItems.call(this);
this.dropdownItemList.push({
'label': 'Generate Mail Merge PDF',
'name': 'generateMailMergePdf',
'hidden': !this.isMailMergeAvailable()
});
this.listenTo(this.model, 'change', function () {
if (this.isMailMergeAvailable()) {
this.showActionItem('generateMailMergePdf');
} else {
this.hideActionItem('generateMailMergePdf');
}
}, this);
},
afterRender: function () {
Dep.prototype.afterRender.call(this);
},
isMailMergeAvailable: function () {
if (this.model.get('type') !== 'Mail') {
return false;
}
if (!this.model.get('targetListsIds') || !this.model.get('targetListsIds').length) {
return false;
}
if (
!this.model.get('leadsTemplateId') &&
!this.model.get('contactsTemplateId') &&
!this.model.get('accountsTemplateId') &&
!this.model.get('usersTemplateId')
) {
return false;
}
return true;
},
actionGenerateMailMergePdf: function () {
this.createView('dialog', 'crm:views/campaign/modals/mail-merge-pdf', {
model: this.model,
}, function (view) {
view.render();
this.listenToOnce(view, 'proceed', (link) => {
this.clearView('dialog');
Espo.Ui.notifyWait();
Espo.Ajax.postRequest(`Campaign/${this.model.id}/generateMailMerge`, {link: link})
.then(response => {
Espo.Ui.notify(false);
window.open('?entryPoint=download&id=' + response.id, '_blank');
});
});
});
},
});
});

View File

@@ -0,0 +1,97 @@
/************************************************************************
* 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 RelationshipPanelView from 'views/record/panels/relationship';
import RecordModal from 'helpers/record-modal';
// noinspection JSUnusedGlobalSymbols
export default class CampaignLogRecordsPanelView extends RelationshipPanelView {
filterList = [
"all",
"sent",
"opened",
"optedOut",
"bounced",
"clicked",
"optedIn",
"leadCreated",
]
setup() {
if (this.getAcl().checkScope('TargetList', 'create')) {
this.actionList.push({
action: 'createTargetList',
label: 'Create Target List',
});
}
this.filterList = Espo.Utils.clone(this.filterList);
if (!this.getConfig().get('massEmailOpenTracking')) {
const i = this.filterList.indexOf('opened');
if (i >= 0) {
this.filterList.splice(i, 1);
}
}
super.setup();
}
actionCreateTargetList() {
const attributes = {
sourceCampaignId: this.model.id,
sourceCampaignName: this.model.attributes.name,
};
if (!this.collection.data.primaryFilter) {
attributes.includingActionList = [];
} else {
const status = Espo.Utils.upperCaseFirst(this.collection.data.primaryFilter)
.replace(/([A-Z])/g, ' $1');
attributes.includingActionList = [status];
}
const helper = new RecordModal();
helper.showCreate(this, {
entityType: 'TargetList',
attributes: attributes,
fullFormDisabled: true,
layoutName: 'createFromCampaignLog',
afterSave: () => {
Espo.Ui.success(this.translate('Done'));
},
beforeRender: view => {
view.getRecordView().setFieldRequired('includingActionList')
},
});
}
}

View File

@@ -0,0 +1,130 @@
/************************************************************************
* 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 SidePanelView from 'views/record/panels/side';
// noinspection JSUnusedGlobalSymbols
export default class extends SidePanelView {
controlStatsFields() {
const type = this.model.attributes.type;
let fieldList;
switch (type) {
case 'Email':
case 'Newsletter':
fieldList = [
'sentCount',
'openedCount',
'clickedCount',
'optedOutCount',
'bouncedCount',
'leadCreatedCount',
'optedInCount',
'revenue',
];
break;
case 'Informational Email':
fieldList = [
'sentCount',
'bouncedCount',
];
break;
case 'Web':
fieldList = ['leadCreatedCount', 'optedInCount', 'revenue'];
break;
case 'Television':
case 'Radio':
fieldList = ['leadCreatedCount', 'revenue'];
break;
case 'Mail':
fieldList = ['sentCount', 'leadCreatedCount', 'optedInCount', 'revenue'];
break;
default:
fieldList = ['leadCreatedCount', 'revenue'];
}
if (!this.getConfig().get('massEmailOpenTracking')) {
const i = fieldList.indexOf('openedCount');
if (i > -1) {
fieldList.splice(i, 1);
}
}
this.statsFieldList.forEach(item => {
this.options.recordViewObject.hideField(item);
});
fieldList.forEach(item => {
this.options.recordViewObject.showField(item);
});
if (!this.getAcl().checkScope('Lead')) {
this.options.recordViewObject.hideField('leadCreatedCount', true);
}
if (!this.getAcl().checkScope('Opportunity')) {
this.options.recordViewObject.hideField('revenue', true);
}
}
setupFields() {
this.fieldList = [
'sentCount',
'openedCount',
'clickedCount',
'optedOutCount',
'bouncedCount',
'leadCreatedCount',
'optedInCount',
'revenue',
];
this.statsFieldList = this.fieldList;
}
setup() {
super.setup();
this.controlStatsFields();
this.listenTo(this.model, 'change:type', () => this.controlStatsFields());
}
}

View File

@@ -0,0 +1,52 @@
/************************************************************************
* 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 View from 'view';
export default class extends View {
// language=Handlebars
templateContent = `
<div class="container content">
<div class="block-center-md">
<div class="panel panel-default">
<div class="panel-body">
<div class="complex-text">{{complexText message}}</div>
</div>
</div>
</div>
</div>
`
data() {
// noinspection JSUnresolvedReference
return {
message: this.options.message,
};
}
}

View File

@@ -0,0 +1,104 @@
/************************************************************************
* 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 View from 'view';
class CampaignUnsubscribeView extends View {
template = 'crm:campaign/unsubscribe'
data() {
return {
isSubscribed: this.isSubscribed,
inProcess: this.inProcess,
};
}
setup() {
super.setup();
this.actionData = /** @type {Record} */this.options.actionData;
this.isSubscribed = this.actionData.isSubscribed;
this.inProcess = false;
const endpointUrl = this.actionData.hash && this.actionData.emailAddress ?
`Campaign/unsubscribe/${this.actionData.emailAddress}/${this.actionData.hash}`:
`Campaign/unsubscribe/${this.actionData.queueItemId}`;
this.addActionHandler('subscribe', () => {
Espo.Ui.notifyWait();
this.inProcess = true;
this.reRender();
Espo.Ajax.deleteRequest(endpointUrl)
.then(() => {
this.isSubscribed = true;
this.inProcess = false;
this.reRender().then(() => {
const message = this.translate('subscribedAgain', 'messages', 'Campaign');
Espo.Ui.notify(message, 'success', 0, {closeButton: true});
});
})
.catch(() => {
this.inProcess = false;
this.reRender();
});
});
this.addActionHandler('unsubscribe', () => {
Espo.Ui.notifyWait();
this.inProcess = true;
this.reRender();
Espo.Ajax.postRequest(endpointUrl)
.then(() => {
Espo.Ui.success(this.translate('unsubscribed', 'messages', 'Campaign'), {closeButton: true});
this.isSubscribed = false;
this.inProcess = false;
this.reRender().then(() => {
const message = this.translate('unsubscribed', 'messages', 'Campaign');
Espo.Ui.notify(message, 'success', 0, {closeButton: true});
});
})
.catch(() => {
this.inProcess = false;
this.reRender();
});
});
}
}
export default CampaignUnsubscribeView;

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 DetailRecordView from 'views/record/detail';
export default class extends DetailRecordView {
selfAssignAction = true
getSelfAssignAttributes() {
if (this.model.attributes.status === 'New') {
const options = this.getMetadata().get(['entityDefs', 'Case', 'fields', 'status', 'options']) || [];
if (options.includes('Assigned')) {
return {
status: 'Assigned',
};
}
}
return {};
}
}

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.
************************************************************************/
define('crm:views/case/record/panels/activities', ['crm:views/record/panels/activities'], function (Dep) {
return Dep.extend({
getComposeEmailAttributes: function (scope, data, callback) {
data = data || {};
Espo.Ui.notifyWait();
Dep.prototype.getComposeEmailAttributes.call(this, scope, data, attributes => {
attributes.name = '[#' + this.model.get('number') + '] ' + this.model.get('name');
Espo.Ajax.getRequest('Case/action/emailAddressList?id=' + this.model.id).then(list => {
attributes.to = '';
attributes.cc = '';
attributes.nameHash = {};
list.forEach((item, i) => {
if (i === 0) {
attributes.to += item.emailAddress + ';';
} else {
attributes.cc += item.emailAddress + ';';
}
attributes.nameHash[item.emailAddress] = item.name;
});
Espo.Ui.notify(false);
callback.call(this, attributes);
});
})
},
});
});

View File

@@ -0,0 +1,32 @@
/************************************************************************
* 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 DetailView from 'views/detail';
/** Left for bc. */
export default class extends DetailView {}

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.
************************************************************************/
define('crm:views/contact/fields/account-role', ['views/fields/varchar'], function (Dep) {
return Dep.extend({
detailTemplate: 'crm:contact/fields/account-role/detail',
listTemplate: 'crm:contact/fields/account-role/detail',
setup: function () {
Dep.prototype.setup.call(this);
this.listenTo(this.model, 'change:title', () => {
this.model.set('accountRole', this.model.get('title'));
});
},
getAttributeList: function () {
var list = Dep.prototype.getAttributeList.call(this);
list.push('title');
list.push('accountIsInactive');
return list;
},
data: function () {
var data = Dep.prototype.data.call(this);
if (this.model.has('accountIsInactive')) {
data.accountIsInactive = this.model.get('accountIsInactive');
}
return data;
}
});
});

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.
************************************************************************/
define('crm:views/contact/fields/account', ['views/fields/link'], function (Dep) {
return Dep.extend({
getAttributeList: function () {
var list = Dep.prototype.getAttributeList.call(this);
list.push('accountIsInactive');
return list;
},
afterRender: function () {
Dep.prototype.afterRender.call(this);
if (this.mode === 'list' || this.mode === 'detail') {
if (this.model.get('accountIsInactive')) {
this.$el.find('a').css('textDecoration', 'line-through');
}
}
},
});
});

View File

@@ -0,0 +1,239 @@
/************************************************************************
* 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 LinkMultipleWithColumnsFieldView from 'views/fields/link-multiple-with-columns';
class AccountsFieldView extends LinkMultipleWithColumnsFieldView {
getAttributeList() {
const list = super.getAttributeList();
list.push('accountId');
list.push('accountName');
list.push('title');
return list;
}
setup() {
super.setup();
this.events['click [data-action="switchPrimary"]'] = e => {
const $target = $(e.currentTarget);
const id = $target.data('id');
if (!$target.hasClass('active')) {
this.$el.find('button[data-action="switchPrimary"]')
.removeClass('active')
.children()
.addClass('text-muted');
$target.addClass('active')
.children()
.removeClass('text-muted');
this.setPrimaryId(id);
}
};
this.primaryIdFieldName = 'accountId';
this.primaryNameFieldName = 'accountName';
this.primaryRoleFieldName = 'title';
this.primaryId = this.model.get(this.primaryIdFieldName);
this.primaryName = this.model.get(this.primaryNameFieldName);
this.listenTo(this.model, 'change:' + this.primaryIdFieldName, () => {
this.primaryId = this.model.get(this.primaryIdFieldName);
this.primaryName = this.model.get(this.primaryNameFieldName);
});
if (this.isEditMode() || this.isDetailMode()) {
this.events['click a[data-action="setPrimary"]'] = (e) => {
const id = $(e.currentTarget).data('id');
this.setPrimaryId(id);
this.reRender();
}
}
}
setPrimaryId(id) {
this.primaryId = id;
if (id) {
this.primaryName = this.nameHash[id];
} else {
this.primaryName = null;
}
this.trigger('change');
}
renderLinks() {
if (this.primaryId) {
this.addLinkHtml(this.primaryId, this.primaryName);
}
this.ids.forEach(id => {
if (id !== this.primaryId) {
this.addLinkHtml(id, this.nameHash[id]);
}
});
}
getValueForDisplay() {
if (this.isDetailMode() || this.isListMode()) {
const itemList = [];
if (this.primaryId) {
itemList.push(this.getDetailLinkHtml(this.primaryId, this.primaryName));
}
this.ids.forEach(id => {
if (id !== this.primaryId) {
itemList.push(this.getDetailLinkHtml(id));
}
});
return itemList
.map(item => {
return $('<div>')
.addClass('link-multiple-item')
.html(item)
.get(0).outerHTML;
})
.join('');
}
}
getDetailLinkHtml(id, name) {
const html = super.getDetailLinkHtml(id, name);
if (this.getColumnValue(id, 'isInactive')) {
const $el = $('<div>').html(html);
$el.find('a').css('text-decoration', 'line-through');
return $el.get(0).innerHTML;
}
return html;
}
afterAddLink(id) {
super.afterAddLink(id);
if (this.ids.length === 1) {
this.primaryId = id;
this.primaryName = this.nameHash[id];
}
this.controlPrimaryAppearance();
}
afterDeleteLink(id) {
super.afterDeleteLink(id);
if (this.ids.length === 0) {
this.primaryId = null;
this.primaryName = null;
return;
}
if (id === this.primaryId) {
this.primaryId = this.ids[0];
this.primaryName = this.nameHash[this.primaryId];
}
this.controlPrimaryAppearance();
}
controlPrimaryAppearance() {
this.$el.find('li.set-primary-list-item').removeClass('hidden');
if (this.primaryId) {
this.$el.find('li.set-primary-list-item[data-id="'+this.primaryId+'"]').addClass('hidden');
}
}
addLinkHtml(id, name) {
name = name || id;
if (this.isSearchMode()) {
return super.addLinkHtml(id, name);
}
const $el = super.addLinkHtml(id, name);
const isPrimary = id === this.primaryId;
const $a = $('<a>')
.attr('role', 'button')
.attr('tabindex', '0')
.attr('data-action', 'setPrimary')
.attr('data-id', id)
.text(this.translate('Set Primary', 'labels', 'Account'));
const $li = $('<li>')
.addClass('set-primary-list-item')
.attr('data-id', id)
.append($a);
if (isPrimary || this.ids.length === 1) {
$li.addClass('hidden');
}
$el.find('ul.dropdown-menu').append($li);
if (this.getColumnValue(id, 'isInactive')) {
$el.find('div.link-item-name').css('text-decoration', 'line-through');
}
}
fetch() {
const data = super.fetch();
data[this.primaryIdFieldName] = this.primaryId;
data[this.primaryNameFieldName] = this.primaryName;
data[this.primaryRoleFieldName] = (this.columns[this.primaryId] || {}).role || null;
// noinspection JSUnresolvedReference
data.accountIsInactive = (this.columns[this.primaryId] || {}).isInactive || false;
if (!this.primaryId) {
data[this.primaryRoleFieldName] = null;
data.accountIsInactive = null;
}
return data;
}
}
export default AccountsFieldView;

View File

@@ -0,0 +1,47 @@
/************************************************************************
* 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.
************************************************************************/
define('crm:views/contact/fields/name-for-account', ['views/fields/person-name'], function (Dep) {
return Dep.extend({
afterRender: function () {
Dep.prototype.afterRender.call(this);
if (this.mode === 'listLink') {
if (this.model.get('accountIsInactive')) {
this.$el.find('a').css('text-decoration', 'line-through');
}
}
},
getAttributeList: function () {
return ['name', 'accountIsInactive'];
},
});
});

View File

@@ -0,0 +1,35 @@
/************************************************************************
* 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.
************************************************************************/
define('crm:views/contact/fields/opportunity-role', ['views/fields/enum'], function (Dep) {
return Dep.extend({
searchTypeList: ['anyOf', 'noneOf'],
});
});

View File

@@ -0,0 +1,39 @@
/************************************************************************
* 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.
************************************************************************/
define('crm:views/contact/fields/title', ['views/fields/varchar'], function (Dep) {
return Dep.extend({
setupOptions: function () {
this.params.options = Espo.Utils.clone(
this.getMetadata().get('entityDefs.Account.fields.contactRole.options') || []
);
},
});
});

View File

@@ -0,0 +1,60 @@
/************************************************************************
* 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 SelectRecordsModalView from 'views/modals/select-records';
export default class SelectForPortalUserModalView extends SelectRecordsModalView {
/**
* @param {
* module:views/modals/select-records~Options &
* {onSkip: function()}
* } options
*/
constructor(options) {
super(options);
this.options = options;
}
setup() {
super.setup();
this.buttonList.unshift({
name: 'skip',
text: this.translate('Proceed w/o Contact', 'labels', 'User'),
onClick: () => this.actionSkip(),
});
}
actionSkip() {
this.options.onSkip();
this.close();
}
}

View File

@@ -0,0 +1,34 @@
/************************************************************************
* 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.
************************************************************************/
define('crm:views/contact/record/detail-small', ['views/record/detail-small', 'crm:views/contact/record/detail'], function (Dep, Detail) {
return Dep.extend({
});
});

View File

@@ -0,0 +1,32 @@
/************************************************************************
* 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.
************************************************************************/
define('crm:views/contact/record/detail', ['views/record/detail'], function (Dep) {
return Dep.extend({});
});

View File

@@ -0,0 +1,320 @@
/************************************************************************
* 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.
************************************************************************/
define('crm:views/dashlets/abstract/chart', ['views/dashlets/abstract/base','lib!flotr2'], function (Dep, Flotr) {
return Dep.extend({
templateContent: '<div class="chart-container"></div><div class="legend-container"></div>',
decimalMark: '.',
thousandSeparator: ',',
defaultColorList: ['#6FA8D6', '#4E6CAD', '#EDC555', '#ED8F42', '#DE6666', '#7CC4A4', '#8A7CC2', '#D4729B'],
successColor: '#85b75f',
gridColor: '#ddd',
tickColor: '#e8eced',
textColor: '#333',
hoverColor: '#FF3F19',
legendColumnWidth: 110,
legendColumnNumber: 6,
labelFormatter: function (v) {
return '<span style="color:'+this.textColor+'">' + v + '</span>';
},
init: function () {
Dep.prototype.init.call(this);
this.fontSizeFactor = this.getThemeManager().getFontSizeFactor();
this.flotr = Flotr;
this.successColor = this.getThemeManager().getParam('chartSuccessColor') || this.successColor;
this.colorList = this.getThemeManager().getParam('chartColorList') || this.defaultColorList;
this.tickColor = this.getThemeManager().getParam('chartTickColor') || this.tickColor;
this.gridColor = this.getThemeManager().getParam('chartGridColor') || this.gridColor;
this.textColor = this.getThemeManager().getParam('textColor') || this.textColor;
this.hoverColor = this.getThemeManager().getParam('hoverColor') || this.hoverColor;
if (this.getPreferences().has('decimalMark')) {
this.decimalMark = this.getPreferences().get('decimalMark')
} else {
if (this.getConfig().has('decimalMark')) {
this.decimalMark = this.getConfig().get('decimalMark')
}
}
if (this.getPreferences().has('thousandSeparator')) {
this.thousandSeparator = this.getPreferences().get('thousandSeparator')
} else {
if (this.getConfig().has('thousandSeparator')) {
this.thousandSeparator = this.getConfig().get('thousandSeparator')
}
}
this.on('resize', () => {
if (!this.isRendered()) {
return;
}
setTimeout(() => {
this.adjustContainer();
if (this.isNoData()) {
this.showNoData();
return;
}
this.draw();
}, 50);
});
$(window).on('resize.chart' + this.id, () => {
this.adjustContainer();
if (this.isNoData()) {
this.showNoData();
return;
}
this.draw();
});
this.once('remove', () => {
$(window).off('resize.chart' + this.id)
});
},
formatNumber: function (value, isCurrency, useSiMultiplier) {
if (value !== null) {
let maxDecimalPlaces = 2;
var currencyDecimalPlaces = this.getConfig().get('currencyDecimalPlaces');
var siSuffix = '';
if (useSiMultiplier) {
if (value >= 1000000) {
siSuffix = 'M';
value = value / 1000000;
} else if (value >= 1000) {
siSuffix = 'k';
value = value / 1000;
}
}
if (isCurrency) {
if (currencyDecimalPlaces === 0) {
value = Math.round(value);
} else if (currencyDecimalPlaces) {
value = Math.round(value * Math.pow(10, currencyDecimalPlaces)) /
(Math.pow(10, currencyDecimalPlaces));
} else {
value = Math.round(value * Math.pow(10, maxDecimalPlaces)) / (Math.pow(10, maxDecimalPlaces));
}
} else {
let maxDecimalPlaces = 4;
if (useSiMultiplier) {
maxDecimalPlaces = 2;
}
value = Math.round(value * Math.pow(10, maxDecimalPlaces)) / (Math.pow(10, maxDecimalPlaces));
}
var parts = value.toString().split(".");
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, this.thousandSeparator);
if (isCurrency) {
if (currencyDecimalPlaces === 0) {
delete parts[1];
}
else if (currencyDecimalPlaces) {
var decimalPartLength = 0;
if (parts.length > 1) {
decimalPartLength = parts[1].length;
} else {
parts[1] = '';
}
if (currencyDecimalPlaces && decimalPartLength < currencyDecimalPlaces) {
var limit = currencyDecimalPlaces - decimalPartLength;
for (let i = 0; i < limit; i++) {
parts[1] += '0';
}
}
}
}
return parts.join(this.decimalMark) + siSuffix;
}
return '';
},
getLegendColumnNumber: function () {
const width = this.$el.closest('.panel-body').width();
const legendColumnNumber = Math.floor(width / (this.legendColumnWidth * this.fontSizeFactor));
return legendColumnNumber || this.legendColumnNumber;
},
getLegendHeight: function () {
const lineNumber = Math.ceil(this.chartData.length / this.getLegendColumnNumber());
let legendHeight = 0;
const lineHeight = (this.getThemeManager().getParam('dashletChartLegendRowHeight') || 19) *
this.fontSizeFactor;
const paddingTopHeight = (this.getThemeManager().getParam('dashletChartLegendPaddingTopHeight') || 7) *
this.fontSizeFactor;
if (lineNumber > 0) {
legendHeight = lineHeight * lineNumber + paddingTopHeight;
}
return legendHeight;
},
adjustContainer: function () {
const legendHeight = this.getLegendHeight();
const heightCss = `calc(100% - ${legendHeight.toString()}px)`;
this.$container.css('height', heightCss);
},
adjustLegend: function () {
const number = this.getLegendColumnNumber();
if (!number) {
return;
}
const dashletChartLegendBoxWidth = (this.getThemeManager().getParam('dashletChartLegendBoxWidth') || 21) *
this.fontSizeFactor;
const containerWidth = this.$legendContainer.width();
const width = Math.floor((containerWidth - dashletChartLegendBoxWidth * number) / number);
const columnNumber = this.$legendContainer.find('> table tr:first-child > td').length / 2;
const tableWidth = (width + dashletChartLegendBoxWidth) * columnNumber;
this.$legendContainer.find('> table')
.css('table-layout', 'fixed')
.attr('width', tableWidth);
this.$legendContainer.find('td.flotr-legend-label').attr('width', width);
this.$legendContainer.find('td.flotr-legend-color-box').attr('width', dashletChartLegendBoxWidth);
this.$legendContainer.find('td.flotr-legend-label > span').each((i, span) => {
span.setAttribute('title', span.textContent);
});
},
afterRender: function () {
this.$el.closest('.panel-body').css({
'overflow-y': 'visible',
'overflow-x': 'visible',
});
this.$legendContainer = this.$el.find('.legend-container');
this.$container = this.$el.find('.chart-container');
this.fetch(function (data) {
this.chartData = this.prepareData(data);
this.adjustContainer();
if (this.isNoData()) {
this.showNoData();
return;
}
setTimeout(() => {
if (!this.$container.length || !this.$container.is(":visible")) {
return;
}
this.draw();
}, 1);
});
},
isNoData: function () {
return false;
},
url: function () {},
prepareData: function (response) {
return response;
},
fetch: function (callback) {
Espo.Ajax.getRequest(this.url())
.then(response => {
callback.call(this, response);
});
},
getDateFilter: function () {
return this.getOption('dateFilter') || 'currentYear';
},
showNoData: function () {
this.$container.empty();
const $text = $('<span>').html(this.translate('No Data')).addClass('text-muted');
const $div = $('<div>')
.css('text-align', 'center')
.css('font-size', 'calc(var(--font-size-base) * 1.2)')
.css('display', 'table')
.css('width', '100%')
.css('height', '100%')
.css('user-select', 'none');
$text
.css('display', 'table-cell')
.css('vertical-align', 'middle')
.css('padding-bottom', 'calc(var(--font-size-base) * 1.5)');
$div.append($text);
this.$container.append($div);
},
});
});

View File

@@ -0,0 +1,276 @@
/************************************************************************
* 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 BaseDashletView from 'views/dashlets/abstract/base';
import MultiCollection from 'multi-collection';
import RecordModalHelper from 'helpers/record-modal';
class ActivitiesDashletView extends BaseDashletView {
name = 'Activities'
// language=Handlebars
templateContent = '<div class="list-container">{{{list}}}</div>'
rowActionsView = 'crm:views/record/row-actions/activities-dashlet'
defaultListLayout = {
rows: [
[
{
name: 'ico',
view: 'crm:views/fields/ico',
params: {
notRelationship: true,
},
},
{
name: 'name',
link: true,
},
],
[
{
name: 'dateStart',
soft: true
},
{
name: 'parent',
},
],
],
}
listLayoutEntityTypeMap = {
Task: {
rows: [
[
{
name: 'ico',
view: 'crm:views/fields/ico',
params: {
notRelationship: true
},
},
{
name: 'name',
link: true,
},
],
[
{
name: 'status',
},
{
name: 'dateEnd',
soft: true
},
{
name: 'priority',
view: 'crm:views/task/fields/priority-for-dashlet',
},
{
name: 'parent',
},
],
]
}
}
setup() {
this.seeds = {};
this.scopeList = this.getOption('enabledScopeList') || [];
this.listLayout = {};
this.scopeList.forEach((item) => {
if (item in this.listLayoutEntityTypeMap) {
this.listLayout[item] = this.listLayoutEntityTypeMap[item];
return;
}
this.listLayout[item] = this.defaultListLayout;
});
this.wait(true);
let i = 0;
this.scopeList.forEach(scope => {
this.getModelFactory().create(scope, seed => {
this.seeds[scope] = seed;
i++;
if (i === this.scopeList.length) {
this.wait(false);
}
});
});
this.scopeList.slice(0).reverse().forEach(scope => {
if (this.getAcl().checkScope(scope, 'create')) {
this.actionList.unshift({
name: 'createActivity',
text: this.translate('Create ' + scope, 'labels', scope),
iconClass: 'fas fa-plus',
url: '#' + scope + '/create',
data: {
scope: scope,
},
});
}
});
}
afterRender() {
this.collection = new MultiCollection();
this.collection.seeds = this.seeds;
this.collection.url = 'Activities/upcoming';
this.collection.maxSize = this.getOption('displayRecords') ||
this.getConfig().get('recordsPerPageSmall') || 5;
this.collection.data.entityTypeList = this.scopeList;
this.collection.data.futureDays = this.getOption('futureDays');
if (this.getOption('includeShared')) {
this.collection.data.includeShared = true;
}
this.listenToOnce(this.collection, 'sync', () => {
this.createView('list', 'crm:views/record/list-activities-dashlet', {
selector: '> .list-container',
pagination: false,
type: 'list',
rowActionsView: this.rowActionsView,
checkboxes: false,
collection: this.collection,
multiListLayout: this.listLayout,
}, view => {
view.render();
});
});
this.collection.fetch();
}
actionRefresh() {
this.refreshInternal();
}
autoRefresh() {
this.refreshInternal({skipNotify: true});
}
/**
* @private
* @param {{skipNotify?: boolean}} [options]
* @return {Promise<void>}
*/
async refreshInternal(options = {}) {
if (!options.skipNotify) {
Espo.Ui.notifyWait();
}
await this.collection.fetch({
previousTotal: this.collection.total,
previousDataList: this.collection.models.map(model => {
return Espo.Utils.cloneDeep(model.attributes);
}),
});
if (!options.skipNotify) {
Espo.Ui.notify();
}
}
// noinspection JSUnusedGlobalSymbols
actionCreateActivity(data) {
const scope = data.scope;
const attributes = {};
this.populateAttributesAssignedUser(scope, attributes);
const helper = new RecordModalHelper();
helper.showCreate(this, {
entityType: scope,
attributes: attributes,
afterSave: () => {
this.actionRefresh();
},
});
}
// noinspection JSUnusedGlobalSymbols
actionCreateMeeting() {
const attributes = {};
this.populateAttributesAssignedUser('Meeting', attributes);
const helper = new RecordModalHelper();
helper.showCreate(this, {
entityType: 'Meeting',
attributes: attributes,
afterSave: () => {
this.actionRefresh();
},
});
}
// noinspection JSUnusedGlobalSymbols
actionCreateCall() {
const attributes = {};
this.populateAttributesAssignedUser('Call', attributes);
const helper = new RecordModalHelper();
helper.showCreate(this, {
entityType: 'Call',
attributes: attributes,
afterSave: () => {
this.actionRefresh();
},
});
}
populateAttributesAssignedUser(scope, attributes) {
if (this.getMetadata().get(['entityDefs', scope, 'fields', 'assignedUsers'])) {
attributes['assignedUsersIds'] = [this.getUser().id];
attributes['assignedUsersNames'] = {};
attributes['assignedUsersNames'][this.getUser().id] = this.getUser().get('name');
} else {
attributes['assignedUserId'] = this.getUser().id;
attributes['assignedUserName'] = this.getUser().get('name');
}
}
}
export default ActivitiesDashletView;

View File

@@ -0,0 +1,195 @@
/************************************************************************
* 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 BaseDashletView from 'views/dashlets/abstract/base';
class CalendarDashletView extends BaseDashletView {
name = 'Calendar'
noPadding = true
templateContent =`<div class="calendar-container">{{{calendar}}}</div>`
afterRender() {
const mode = this.getOption('mode');
if (mode === 'timeline') {
const userList = [];
const userIdList = this.getOption('usersIds') || [];
const userNames = this.getOption('usersNames') || {};
userIdList.forEach(id => {
userList.push({
id: id,
name: userNames[id] || id,
});
});
const viewName = this.getMetadata().get(['clientDefs', 'Calendar', 'timelineView']) ||
'crm:views/calendar/timeline';
this.createView('calendar', viewName, {
selector: '> .calendar-container',
header: false,
calendarType: 'shared',
userList: userList,
enabledScopeList: this.getOption('enabledScopeList'),
suppressLoadingAlert: true,
}, view => {
view.render();
});
return;
}
let teamIdList = null;
if (['basicWeek', 'month', 'basicDay'].includes(mode)) {
teamIdList = this.getOption('teamsIds');
}
const viewName = this.getMetadata().get(['clientDefs', 'Calendar', 'calendarView']) ||
'crm:views/calendar/calendar';
this.createView('calendar', viewName, {
mode: mode,
selector: '> .calendar-container',
header: false,
enabledScopeList: this.getOption('enabledScopeList'),
containerSelector: this.getSelector(),
teamIdList: teamIdList,
scrollToNowSlots: 3,
suppressLoadingAlert: true,
}, view => {
this.listenTo(view, 'view', () => {
if (this.getOption('mode') === 'month') {
const title = this.getOption('title');
const $container = $('<span>')
.append(
$('<span>').text(title),
' <span class="chevron-right"></span> ',
$('<span>').text(view.getTitle())
);
const $headerSpan = this.$el.closest('.panel').find('.panel-heading > .panel-title > span');
$headerSpan.html($container.get(0).innerHTML);
}
});
view.render();
this.on('resize', () => {
setTimeout(() => view.adjustSize(), 50);
});
});
}
setupActionList() {
this.actionList.unshift({
name: 'viewCalendar',
text: this.translate('View Calendar', 'labels', 'Calendar'),
url: '#Calendar',
iconClass: 'far fa-calendar-alt',
onClick: () => this.actionViewCalendar(),
});
}
setupButtonList() {
if (this.getOption('mode') !== 'timeline') {
this.buttonList.push({
name: 'previous',
html: '<span class="fas fa-chevron-left"></span>',
onClick: () => this.actionPrevious(),
});
this.buttonList.push({
name: 'next',
html: '<span class="fas fa-chevron-right"></span>',
onClick: () => this.actionNext(),
});
}
}
/**
* @return {
* import('modules/crm/views/calendar/calendar').default |
* import('modules/crm/views/calendar/timeline').default
* }
*/
getCalendarView() {
return this.getView('calendar');
}
actionRefresh() {
const view = this.getCalendarView();
if (!view) {
return;
}
view.actionRefresh();
}
autoRefresh() {
const view = this.getCalendarView();
if (!view) {
return;
}
view.actionRefresh({suppressLoadingAlert: true});
}
actionNext() {
const view = this.getCalendarView();
if (!view) {
return;
}
view.actionNext();
}
actionPrevious() {
const view = this.getCalendarView();
if (!view) {
return;
}
view.actionPrevious();
}
actionViewCalendar() {
this.getRouter().navigate('#Calendar', {trigger: true});
}
}
export default CalendarDashletView;

View File

@@ -0,0 +1,39 @@
/************************************************************************
* 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.
************************************************************************/
define('crm:views/dashlets/calls', ['views/dashlets/abstract/record-list'], function (Dep) {
return Dep.extend({
name: 'Calls',
scope: 'Call',
listView: 'crm:views/call/record/list-expanded',
rowActionsView: 'crm:views/call/record/row-actions/dashlet',
});
});

View File

@@ -0,0 +1,38 @@
/************************************************************************
* 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.
************************************************************************/
define('crm:views/dashlets/meetings', ['views/dashlets/abstract/record-list'], function (Dep) {
return Dep.extend({
name: 'Meetings',
scope: 'Meeting',
listView: 'crm:views/meeting/record/list-expanded',
rowActionsView: 'crm:views/meeting/record/row-actions/dashlet',
});
});

View File

@@ -0,0 +1,140 @@
/************************************************************************
* 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.
************************************************************************/
define('crm:views/dashlets/opportunities-by-lead-source', ['crm:views/dashlets/abstract/chart'], function (Dep) {
return Dep.extend({
name: 'OpportunitiesByLeadSource',
url: function () {
let url = 'Opportunity/action/reportByLeadSource?dateFilter=' + this.getDateFilter();
if (this.getDateFilter() === 'between') {
url += '&dateFrom=' + this.getOption('dateFrom') + '&dateTo=' + this.getOption('dateTo');
}
return url;
},
prepareData: function (response) {
var data = [];
for (var label in response) {
var value = response[label];
data.push({
label: this.getLanguage().translateOption(label, 'source', 'Lead'),
data: [[0, value]]
});
}
return data;
},
isNoData: function () {
return !this.chartData.length;
},
setupDefaultOptions: function () {
this.defaultOptions['dateFrom'] = this.defaultOptions['dateFrom'] || moment().format('YYYY') + '-01-01';
this.defaultOptions['dateTo'] = this.defaultOptions['dateTo'] || moment().format('YYYY') + '-12-31';
},
setup: function () {
this.currency = this.getConfig().get('defaultCurrency');
this.currencySymbol = this.getMetadata().get(['app', 'currency', 'symbolMap', this.currency]) || '';
},
draw: function () {
this.flotr.draw(this.$container.get(0), this.chartData, {
colors: this.colorList,
shadowSize: false,
pie: {
show: true,
explode: 0,
lineWidth: 1 * this.fontSizeFactor,
fillOpacity: 1,
sizeRatio: 0.8,
labelFormatter: (total, value) => {
const percentage = Math.round(100 * value / total);
if (percentage < 5) {
return '';
}
return '<span class="small numeric-text" style="font-size: 0.8em;color:'+this.textColor+'">' +
percentage.toString() +'%' + '</span>';
},
},
grid: {
horizontalLines: false,
verticalLines: false,
outline: '',
tickColor: this.tickColor,
},
yaxis: {
showLabels: false,
color: this.textColor,
},
xaxis: {
showLabels: false,
color: this.textColor,
},
mouse: {
track: true,
relative: true,
lineColor: this.hoverColor,
trackFormatter: (obj) => {
const value = this.currencySymbol +
'<span class="numeric-text">' + this.formatNumber(obj.y, true) + '</span>';
const fraction = obj.fraction || 0;
const percentage = '<span class="numeric-text">' +
(100 * fraction).toFixed(2).toString() +'</span>';
const label = this.getHelper().escapeString(obj.series.label || this.translate('None'));
return label + '<br>' + value + ' / ' + percentage + '%';
},
},
legend: {
show: true,
noColumns: this.getLegendColumnNumber(),
container: this.$el.find('.legend-container'),
labelBoxMargin: 0,
labelFormatter: this.labelFormatter.bind(this),
labelBoxBorderColor: 'transparent',
backgroundOpacity: 0,
},
});
this.adjustLegend();
},
});
});

Some files were not shown because too many files have changed in this diff Show More