diff --git a/application/Espo/Core/SelectManagers/Base.php b/application/Espo/Core/SelectManagers/Base.php index 7896e325be..3b7e32f82b 100644 --- a/application/Espo/Core/SelectManagers/Base.php +++ b/application/Espo/Core/SelectManagers/Base.php @@ -1962,8 +1962,8 @@ class Base if (!isset($selectParams1['additionalSelectColumns'])) { $selectParams1['additionalSelectColumns'] = []; } - foreach ($selectParams2['additionalSelectColumns'] as $item) { - $selectParams1['additionalSelectColumns'][] = $item; + foreach ($selectParams2['additionalSelectColumns'] as $key => $item) { + $selectParams1['additionalSelectColumns'][$key] = $item; } } @@ -1983,6 +1983,10 @@ class Base $selectParams1['order'] = $selectParams2['order']; } + if (!empty($selectParams2['distinct'])) { + $selectParams1['distinct'] = true; + } + return $selectParams1; } } diff --git a/application/Espo/Modules/Crm/Services/Activities.php b/application/Espo/Modules/Crm/Services/Activities.php index 5b70a96e29..31bd55ca4e 100644 --- a/application/Espo/Modules/Crm/Services/Activities.php +++ b/application/Espo/Modules/Crm/Services/Activities.php @@ -129,6 +129,7 @@ class Activities extends \Espo\Core\Services\Base if ($entity->get('isPortalUser') && $entity->get('contactId')) { $selectParams['leftJoins'][] = ['contacts', 'contactsLeft']; + $selectParams['distinct'] = true; $where['contactsLeftMiddle.contactId'] = $entity->get('contactId'); $selectParams['whereClause'][] = array( 'OR' => $where @@ -183,6 +184,7 @@ class Activities extends \Espo\Core\Services\Base if ($entity->get('isPortalUser') && $entity->get('contactId')) { $selectParams['leftJoins'][] = ['contacts', 'contactsLeft']; + $selectParams['distinct'] = true; $where['contactsLeftMiddle.contactId'] = $entity->get('contactId'); $selectParams['whereClause'][] = array( 'OR' => $where @@ -719,6 +721,8 @@ class Activities extends \Espo\Core\Services\Base $selectParams['select'] = $selectAttributeList; } + $this->getEntityManager()->getRepository($entityType)->handleSelectParams($selectParams); + $offset = $selectParams['offset']; $limit = $selectParams['limit']; diff --git a/application/Espo/Resources/metadata/clientDefs/Email.json b/application/Espo/Resources/metadata/clientDefs/Email.json index be47c6cc1f..3f2ff0d1e9 100644 --- a/application/Espo/Resources/metadata/clientDefs/Email.json +++ b/application/Espo/Resources/metadata/clientDefs/Email.json @@ -12,7 +12,8 @@ "edit": "views/email/record/edit", "editQuick":"views/email/record/edit-quick", "detailQuick":"views/email/record/detail-quick", - "compose": "views/email/record/compose" + "compose": "views/email/record/compose", + "listRelated": "views/email/record/list-related" }, "modalViews": { "detail": "views/email/modals/detail", diff --git a/client/modules/crm/src/views/record/panels/activities.js b/client/modules/crm/src/views/record/panels/activities.js index 5b5bf097c5..6b6ccf7337 100644 --- a/client/modules/crm/src/views/record/panels/activities.js +++ b/client/modules/crm/src/views/record/panels/activities.js @@ -110,6 +110,11 @@ Espo.define('crm:views/record/panels/activities', ['views/record/panels/relation this.listLayout = Espo.Utils.cloneDeep(this.listLayout); this.actionList = Espo.Utils.cloneDeep(this.actionList); + this.defs.create = true; + + this.createAvailabilityHash = {}; + this.entityTypeLinkMap = {}; + this.setupActionList(); this.setupFinalActionList(); @@ -163,6 +168,10 @@ Espo.define('crm:views/record/panels/activities', ['views/record/panels/relation }, this); }, + isCreateAvailable: function (scope) { + return this.createAvailabilityHash[scope]; + }, + setupActionList: function () { this.scopeList.forEach(function (scope) { if (!this.getMetadata().get(['clientDefs', scope, 'activityDefs', this.name + 'Create'])) return; @@ -180,7 +189,10 @@ Espo.define('crm:views/record/panels/activities', ['views/record/panels/relation var link = this.getMetadata().get(['clientDefs', scope, 'activityDefs', 'link']) if (link) { o.data.link = link; + + this.entityTypeLinkMap[scope] = link; if (!this.model.hasLink(link)) return; + } else { o.data.scope = scope; if ( @@ -192,6 +204,8 @@ Espo.define('crm:views/record/panels/activities', ['views/record/panels/relation } } + this.createAvailabilityHash[scope] = true; + o.data = o.data || {}; if (!o.data.status) { var statusList = this.getMetadata().get(['scopes', scope, this.name + 'StatusList']); @@ -212,7 +226,7 @@ Espo.define('crm:views/record/panels/activities', ['views/record/panels/relation if (!this.getAcl().checkScope(scope, 'read')) return; var o = { action: 'viewRelatedList', - html: this.translate('View List') + ' :: ' + this.translate(scope, 'scopeNamesPlural'), + html: this.translate('View List') + ' · ' + this.translate(scope, 'scopeNamesPlural') + '', data: { scope: scope }, @@ -341,6 +355,11 @@ Espo.define('crm:views/record/panels/activities', ['views/record/panels/relation return ~(this.getMetadata().get(['entityDefs', scope, 'fields', 'parent', 'entityList']) || []).indexOf(parentType); }, + actionCreateRelated: function (data) { + data.link = this.entityTypeLinkMap[data.scope]; + this.actionCreateActivity(data); + }, + actionCreateActivity: function (data) { var link = data.link; var foreignLink; diff --git a/client/modules/crm/src/views/record/panels/history.js b/client/modules/crm/src/views/record/panels/history.js index 5d232bab89..68f08207f2 100644 --- a/client/modules/crm/src/views/record/panels/history.js +++ b/client/modules/crm/src/views/record/panels/history.js @@ -73,6 +73,10 @@ Espo.define('crm:views/record/panels/history', 'crm:views/record/panels/activiti }); }, + isCreateAvailable: function (scope) { + return false; + }, + getArchiveEmailAttributes: function (scope, data, callback) { data = data || {}; var attributes = { @@ -189,4 +193,3 @@ Espo.define('crm:views/record/panels/history', 'crm:views/record/panels/activiti } }); }); - diff --git a/client/src/views/email/fields/subject.js b/client/src/views/email/fields/subject.js index 09dbc60afe..22eb9867e0 100644 --- a/client/src/views/email/fields/subject.js +++ b/client/src/views/email/fields/subject.js @@ -35,9 +35,13 @@ Espo.define('views/email/fields/subject', 'views/fields/varchar', function (Dep) var data = Dep.prototype.data.call(this); data.isRead = (this.model.get('sentById') === this.getUser().id) || this.model.get('isRead'); - data.isImportant = this.model.get('isImportant'); - data.hasAttachment = this.model.get('hasAttachment'); - data.isReplied = this.model.get('isReplied'); + data.isImportant = this.model.has('isImportant') && this.model.get('isImportant'); + data.hasAttachment = this.model.has('hasAttachment') && this.model.get('hasAttachment'); + data.isReplied = this.model.has('isReplied') && this.model.get('isReplied'); + + if (!data.isRead && !this.model.has('isRead')) { + data.isRead = true; + } if (!data.isNotEmpty) { if ( @@ -58,7 +62,7 @@ Espo.define('views/email/fields/subject', 'views/fields/varchar', function (Dep) }, getAttributeList: function () { - return ['name', 'isRead', 'isImportant']; + return ['name', 'isRead', 'isImportant', 'hasAttachment']; }, setup: function () { diff --git a/client/src/views/email/record/list-related.js b/client/src/views/email/record/list-related.js new file mode 100644 index 0000000000..b3f209c862 --- /dev/null +++ b/client/src/views/email/record/list-related.js @@ -0,0 +1,40 @@ +/************************************************************************ + * This file is part of EspoCRM. + * + * EspoCRM - Open Source CRM application. + * Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko + * Website: http://www.espocrm.com + * + * EspoCRM is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EspoCRM 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EspoCRM. If not, see http://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 General Public License version 3. + * + * In accordance with Section 7(b) of the GNU General Public License version 3, + * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. + ************************************************************************/ + +Espo.define('views/email/record/list-related', 'views/record/list', function (Dep) { + + return Dep.extend({ + + massActionList: ['remove', 'massUpdate'], + + setup: function () { + Dep.prototype.setup.call(this); + } + + }); +}); diff --git a/client/src/views/modals/related-list.js b/client/src/views/modals/related-list.js index cef454653e..016cf82c3a 100644 --- a/client/src/views/modals/related-list.js +++ b/client/src/views/modals/related-list.js @@ -91,7 +91,7 @@ Espo.define('views/modals/related-list', ['views/modal', 'search-manager'], func this.panelCollection = this.options.panelCollection; - this.listenTo(this.panelCollection, 'update', function () { + this.listenTo(this.panelCollection, 'sync', function () { this.collection.fetch(); }, this) @@ -219,9 +219,11 @@ Espo.define('views/modals/related-list', ['views/modal', 'search-manager'], func }, loadList: function () { - var viewName = this.listViewName || - this.getMetadata().get(['clientDefs', this.scope, 'recordViews', 'list']) || - 'views/record/list'; + var viewName = + this.listViewName || + this.getMetadata().get(['clientDefs', this.scope, 'recordViews', 'listRelated']) || + this.getMetadata().get(['clientDefs', this.scope, 'recordViews', 'list']) || + 'views/record/list'; this.createView('list', viewName, { collection: this.collection, @@ -292,7 +294,8 @@ Espo.define('views/modals/related-list', ['views/modal', 'search-manager'], func } p.actionCreateRelated({ - link: this.link + link: this.link, + scope: this.scope }); }, diff --git a/client/src/views/record/panels/relationship.js b/client/src/views/record/panels/relationship.js index 1310026810..4342be4b48 100644 --- a/client/src/views/record/panels/relationship.js +++ b/client/src/views/record/panels/relationship.js @@ -199,7 +199,11 @@ Espo.define('views/record/panels/relationship', ['views/record/panels/bottom', ' collection.fetch(); }, this); - var viewName = this.defs.recordListView || this.getMetadata().get('clientDefs.' + this.scope + '.recordViews.list') || 'views/record/list'; + var viewName = + this.defs.recordListView || + this.getMetadata().get(['clientDefs', this.scope, 'recordViews', 'listRelated']) || + this.getMetadata().get(['clientDefs', this.scope, 'recordViews', 'list']) || + 'views/record/list'; this.listViewName = viewName; this.rowActionsView = this.defs.readOnly ? false : (this.defs.rowActionsView || this.rowActionsView); @@ -328,12 +332,14 @@ Espo.define('views/record/panels/relationship', ['views/record/panels/bottom', ' this.viewModalView || 'views/modals/related-list'; + var scope = data.scope || this.scope; + Espo.Ui.notify(this.translate('loading', 'messages')); this.createView('modalRelatedList', viewName, { model: this.model, panelName: this.panelName, link: this.link, - scope: data.scope || this.scope, + scope: scope, defs: this.defs, title: data.title || this.title, filterList: this.filterList, @@ -344,8 +350,8 @@ Espo.define('views/record/panels/relationship', ['views/record/panels/bottom', ' defaultSortBy: this.defaultSortBy, url: data.url || this.url, listViewName: this.listViewName, - createDisabled: !this.defs.create, - selectDisabled: !this.defs.select, + createDisabled: !this.isCreateAvailable(scope), + selectDisabled: !this.isSelectAvailable(scope), rowActionsView: this.rowActionsView, panelCollection: this.collection, filtersDisabled: this.filtersDisabled @@ -367,6 +373,14 @@ Espo.define('views/record/panels/relationship', ['views/record/panels/bottom', ' }); }, + isCreateAvailable: function (scope) { + return this.defs.create; + }, + + isSelectAvailable: function (scope) { + return this.defs.select; + }, + actionViewRelated: function (data) { var id = data.id; var scope = this.collection.get(id).name;