activities dashlet

This commit is contained in:
yuri
2015-09-18 15:44:19 +03:00
parent 4584211c04
commit 8074baef56
15 changed files with 386 additions and 83 deletions
+1 -1
View File
@@ -99,7 +99,7 @@ class ControllerManager
}
if (!method_exists($controller, $primaryActionMethodName)) {
throw new NotFound("Action '$actionName' (".$request->getMethod().") does not exist in controller '$controller'");
throw new NotFound("Action '$actionName' (".$request->getMethod().") does not exist in controller '$controllerName'");
}
if (method_exists($controller, $beforeMethodName)) {
@@ -124,6 +124,9 @@ class Record extends Base
$asc = $request->get('asc') === 'true';
$sortBy = $request->get('sortBy');
$q = $request->get('q');
$primaryFilter = $request->get('primaryFilter');
$textFilter = $request->get('textFilter');
$boolFilterList = $request->get('boolFilterList');
if (empty($maxSize)) {
$maxSize = self::MAX_SIZE_LIMIT;
@@ -139,6 +142,9 @@ class Record extends Base
'asc' => $asc,
'sortBy' => $sortBy,
'q' => $q,
'primaryFilter' => $primaryFilter,
'textFilter' => $textFilter,
'boolFilterList' => $boolFilterList
);
if ($request->get('filter')) {
$params['filter'] = $request->get('filter');
@@ -754,12 +754,6 @@ class Base
}
}
public function applyAccess(&$result)
{
$this->prepareResult($result);
$this->access($result);
}
public function applyTextFilter($textFilter, &$result)
{
$this->prepareResult($result);
@@ -784,6 +778,12 @@ class Base
);
}
public function applyAccess(&$result)
{
$this->prepareResult($result);
$this->access($result);
}
protected function boolFilters($params, &$result)
{
if (!empty($params['boolFilterList']) && is_array($params['boolFilterList'])) {
@@ -795,8 +795,8 @@ class Base
protected function primaryFilter($params, &$result)
{
if (!empty($params['filter'])) {
$this->applyPrimaryFilter($params['filter'], $result);
if (!empty($params['primaryFilter'])) {
$this->applyPrimaryFilter($params['primaryFilter'], $result);
}
}
@@ -52,6 +52,24 @@ class Activities extends \Espo\Core\Controllers\Base
return $service->getEvents($userId, $from, $to);
}
public function actionListUpcoming($params, $data, $request)
{
$service = $this->getService('Activities');
$userId = $request->get('userId');
if (!$userId) {
$userId = $this->getUser()->id;
}
$offset = intval($request->get('offset'));
$maxSize = intval($request->get('maxSize'));
return $service->getUpcomingActivities($userId, array(
'offset' => $offset,
'maxSize' => $maxSize
));
}
public function actionPopupNotifications()
{
$userId = $this->getUser()->id;
@@ -48,7 +48,8 @@
"OpportunitiesByStage": "Opportunities by Stage",
"OpportunitiesByLeadSource": "Opportunities by Lead Source",
"SalesByMonth": "Sales by Month",
"SalesPipeline": "Sales Pipeline"
"SalesPipeline": "Sales Pipeline",
"Activities": "My Activities"
},
"labels": {
"Create InboundEmail": "Create Inbound Email",
@@ -0,0 +1,3 @@
{
"view":"crm:views/dashlets/activities"
}
@@ -34,7 +34,8 @@ class Activities extends \Espo\Core\Services\Base
'entityManager',
'user',
'metadata',
'acl'
'acl',
'selectManagerFactory'
);
protected function getPDO()
@@ -62,6 +63,11 @@ class Activities extends \Espo\Core\Services\Base
return $this->injections['metadata'];
}
protected function getSelectManagerFactory()
{
return $this->getInjection('selectManagerFactory');
}
protected function isPerson($scope)
{
return in_array($scope, ['Contact', 'Lead', 'User']);
@@ -76,7 +82,7 @@ class Activities extends \Espo\Core\Services\Base
meeting.parent_type AS 'parentType', meeting.parent_id AS 'parentId', meeting.status AS status, meeting.created_at AS createdAt
FROM `meeting`
LEFT JOIN `user` AS `assignedUser` ON assignedUser.id = meeting.assigned_user_id
JOIN `meeting_user` AS `usersMiddle` ON usersMiddle.meeting_id = meeting.id AND usersMiddle.deleted = 0
JOIN `meeting_user` AS `usersMiddle` ON usersMiddle.meeting_id = meeting.id AND usersMiddle.deleted = 0 AND usersMiddle.status <> 'Declined'
WHERE meeting.deleted = 0 AND usersMiddle.user_id = ".$pdo->quote($id)."
";
if (!empty($notIn)) {
@@ -96,7 +102,7 @@ class Activities extends \Espo\Core\Services\Base
call.parent_type AS 'parentType', call.parent_id AS 'parentId', call.status AS status, call.created_at AS createdAt
FROM `call`
LEFT JOIN `user` AS `assignedUser` ON assignedUser.id = call.assigned_user_id
JOIN `call_user` AS `usersMiddle` ON usersMiddle.call_id = call.id AND usersMiddle.deleted = 0
JOIN `call_user` AS `usersMiddle` ON usersMiddle.call_id = call.id AND usersMiddle.deleted = 0 AND usersMiddle.status <> 'Declined'
WHERE call.deleted = 0 AND usersMiddle.user_id = ".$pdo->quote($id)."
";
if (!empty($notIn)) {
@@ -428,8 +434,6 @@ class Activities extends \Espo\Core\Services\Base
";
}
$sth = $pdo->prepare($qu);
if (!empty($params['maxSize'])) {
@@ -508,10 +512,13 @@ class Activities extends \Espo\Core\Services\Base
$fetchAll = empty($params['scope']);
$parts = array(
'Meeting' => ($fetchAll || $params['scope'] == 'Meeting') ? $this->getMeetingQuery($scope, $id, 'NOT IN', ['Held', 'Not Held']) : [],
'Call' => ($fetchAll || $params['scope'] == 'Call') ? $this->getCallQuery($scope, $id, 'NOT IN', ['Held', 'Not Held']) : [],
);
$parts = array();
if ($this->getAcl()->checkScope('Meeting')) {
$parts['Meeting'] = ($fetchAll || $params['scope'] == 'Meeting') ? $this->getMeetingQuery($scope, $id, 'NOT IN', ['Held', 'Not Held']) : [];
}
if ($this->getAcl()->checkScope('Call')) {
$parts['Call'] = ($fetchAll || $params['scope'] == 'Call') ? $this->getCallQuery($scope, $id, 'NOT IN', ['Held', 'Not Held']) : [];
}
return $this->getResult($parts, $scope, $id, $params);
}
@@ -523,11 +530,16 @@ class Activities extends \Espo\Core\Services\Base
$fetchAll = empty($params['scope']);
$parts = array(
'Meeting' => ($fetchAll || $params['scope'] == 'Meeting') ? $this->getMeetingQuery($scope, $id, 'IN', ['Held', 'Not Held']) : [],
'Call' => ($fetchAll || $params['scope'] == 'Call') ? $this->getCallQuery($scope, $id, 'IN', ['Held', 'Not Held']) : [],
'Email' => ($fetchAll || $params['scope'] == 'Email') ? $this->getEmailQuery($scope, $id, 'IN', ['Archived', 'Sent']) : [],
);
$parts = array();
if ($this->getAcl()->checkScope('Meeting')) {
$parts['Meeting'] = ($fetchAll || $params['scope'] == 'Meeting') ? $this->getMeetingQuery($scope, $id, 'IN', ['Held', 'Not Held']) : [];
}
if ($this->getAcl()->checkScope('Call')) {
$parts['Call'] = ($fetchAll || $params['scope'] == 'Call') ? $this->getCallQuery($scope, $id, 'IN', ['Held', 'Not Held']) : [];
}
if ($this->getAcl()->checkScope('Email')) {
$parts['Email'] = ($fetchAll || $params['scope'] == 'Email') ? $this->getEmailQuery($scope, $id, 'IN', ['Archived', 'Sent']) : [];
}
$result = $this->getResult($parts, $scope, $id, $params);
foreach ($result['list'] as &$item) {
@@ -680,5 +692,94 @@ class Activities extends \Espo\Core\Services\Base
}
return $result;
}
public function getUpcomingActivities($userId, $params)
{
$user = $this->getEntityManager()->getEntity('User', $userId);
$this->accessCheck($user);
$entityTypeList = ['Meeting', 'Call'];
$unionPartList = [];
foreach ($entityTypeList as $entityType) {
if (!$this->getAcl()->checkScope($entityType, 'read')) {
continue;
}
$selectParams = array(
'select' => ['id', 'name', 'dateStart', ['VALUE:' . $entityType, 'entityType']],
);
$selectManager = $this->getSelectManagerFactory()->create($entityType);
$selectManager->applyAccess($selectParams);
$selectManager->applyTextFilter($query, $selectParams);
$selectManager->applyPrimaryFilter('planned', $selectParams);
$selectManager->applyBoolFilter('onlyMy', $selectParams);
$selectManager->applyWhere(array(
'1' => array(
'type' => 'or',
'value' => array(
'1' => array(
'type' => 'today',
'field' => 'dateStart',
'dateTime' => true
),
'2' => array(
'type' => 'future',
'field' => 'dateEnd',
'dateTime' => true
)
)
)
), $selectParams);
$sql = $this->getEntityManager()->getQuery()->createSelectQuery($entityType, $selectParams);
$unionPartList[] = '' . $sql . '';
}
if (empty($unionPartList)) {
return array(
'total' => 0,
'list' => []
);
}
$pdo = $this->getEntityManager()->getPDO();
$unionSql = implode(' UNION ', $unionPartList);
$countSql = "SELECT COUNT(*) AS 'COUNT' FROM ({$unionSql}) AS c";
$sth = $pdo->prepare($countSql);
$sth->execute();
$row = $sth->fetch(\PDO::FETCH_ASSOC);
$totalCount = $row['COUNT'];
$unionSql .= " ORDER BY dateStart ASC";
$unionSql .= " LIMIT :offset, :maxSize";
$sth = $pdo->prepare($unionSql);
$sth->bindParam(':offset', intval($params['offset']), \PDO::PARAM_INT);
$sth->bindParam(':maxSize', intval($params['maxSize']), \PDO::PARAM_INT);
$sth->execute();
$rows = $sth->fetchAll(\PDO::FETCH_ASSOC);
$entityDataList = [];
foreach ($rows as $row) {
$entity = $this->getEntityManager()->getEntity($row['entityType'], $row['id']);
$entityData = $entity->toArray();
$entityData['_scope'] = $entity->getEntityType();
$entityDataList[] = $entityData;
}
return array(
'total' => $totalCount,
'list' => $entityDataList
);
}
}
@@ -0,0 +1,185 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2015 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/.
************************************************************************/
Espo.define('crm:views/dashlets/activities', ['views/dashlets/abstract/base', 'multi-collection'], function (Dep, MultiCollection) {
return Dep.extend({
name: 'Activities',
_template: '<div class="list-container">{{{list}}}</div>',
rowActionsView: 'crm:views/meeting/record/row-actions/dashlet',
scopeList: ['Meeting', 'Call'],
defaultOptions: {
displayRecords: 5,
autorefreshInterval: 0.5,
isDoubleHeight: false
},
optionsFields: _.extend(_.clone(Dep.prototype.optionsFields), {
'displayRecords': {
type: 'enumInt',
options: [3,4,5,10,15]
},
'isDoubleHeight': {
type: 'bool',
},
}),
listLayout: {
'Meeting': {
rows: [
[
{
name: 'ico',
view: 'crm:views/fields/ico',
params: {
notRelationship: true
}
},
{
name: 'name',
link: true,
},
],
[
{name: 'dateStart'}
]
]
},
'Call': {
rows: [
[
{
name: 'ico',
view: 'crm:views/fields/ico',
params: {
notRelationship: true
}
},
{
name: 'name',
link: true,
},
],
[
{name: 'dateStart'}
]
]
}
},
setupActionList: function () {
this.actionList.unshift({
name: 'createCall',
html: this.translate('Create Call', 'labels', 'Call'),
iconHtml: '<span class="glyphicon glyphicon-plus"></span>'
});
this.actionList.unshift({
name: 'createMeeting',
html: this.translate('Create Meeting', 'labels', 'Meeting'),
iconHtml: '<span class="glyphicon glyphicon-plus"></span>'
});
},
setup: function () {
this.seeds = {};
this.wait(true);
var i = 0;
this.scopeList.forEach(function (scope) {
this.getModelFactory().getSeed(scope, function (seed) {
this.seeds[scope] = seed;
i++;
if (i == this.scopeList.length) {
this.wait(false);
}
}.bind(this));
}, this);
},
afterRender: function () {
this.collection = new MultiCollection();
this.collection.seeds = this.seeds;
this.collection.url = 'Activities/action/listUpcoming';
this.collection.maxSize = this.getConfig().get('recordsPerPageSmall') || 5;
this.listenToOnce(this.collection, 'sync', function () {
this.createView('list', 'crm:views/meeting/record/list-expanded', {
el: this.options.el + ' > .list-container',
pagination: false,
type: 'list',
rowActionsView: this.rowActionsView,
checkboxes: false,
collection: this.collection,
listLayout: this.listLayout,
}, function (view) {
view.render();
});
}, this);
this.collection.fetch();
},
actionRefresh: function () {
this.collection.fetch();
},
actionCreateMeeting: function () {
var attributes = {};
this.notify('Loading...');
var viewName = this.getMetadata().get('clientDefs.Meeting.modalViews.edit') || 'views/modals/edit';
this.createView('quickCreate', viewName, {
scope: 'Meeting',
attributes: attributes,
}, function (view) {
view.render();
view.notify(false);
this.listenToOnce(view, 'after:save', function () {
this.actionRefresh();
}, this);
}.bind(this));
},
actionCreateCall: function () {
var attributes = {};
this.notify('Loading...');
var viewName = this.getMetadata().get('clientDefs.Call.modalViews.edit') || 'views/modals/edit';
this.createView('quickCreate', viewName, {
scope: 'Call',
attributes: attributes,
}, function (view) {
view.render();
view.notify(false);
this.listenToOnce(view, 'after:save', function () {
this.actionRefresh();
}, this);
}.bind(this));
}
});
});
@@ -25,7 +25,12 @@ Espo.define('Crm:Views.Fields.Ico', 'Views.Fields.Base', function (Dep) {
setup: function () {
var tpl;
var icoTpl = '<span class="glyphicon glyphicon-{icoName} text-muted action" style="cursor: pointer" title="'+this.translate('View')+'" data-action="viewRelated" data-id="'+this.model.id+'"></span>';
var icoTpl;
if (this.params.notRelationship) {
icoTpl = '<span class="glyphicon glyphicon-{icoName} text-muted action" style="cursor: pointer" title="'+this.translate('View')+'" data-action="quickView" data-id="'+this.model.id+'" data-scope="'+this.model.name+'"></span>';
} else {
icoTpl = '<span class="glyphicon glyphicon-{icoName} text-muted action" style="cursor: pointer" title="'+this.translate('View')+'" data-action="viewRelated" data-id="'+this.model.id+'"></span>';
}
switch (this.model.name) {
case 'Meeting':
@@ -24,26 +24,33 @@ Espo.define('crm:views/meeting/record/row-actions/dashlet', ['views/record/row-a
return Dep.extend({
getActionList: function () {
var actions = Dep.prototype.getActionList.call(this);
var actionList = Dep.prototype.getActionList.call(this);
actionList.forEach(function (item) {
item.data = item.data || {};
item.data.scope = this.model.name
}, this);
if (this.options.acl.edit && !~['Held', 'Not Held'].indexOf(this.model.get('status'))) {
actions.push({
actionList.push({
action: 'setHeld',
label: 'Set Held',
data: {
id: this.model.id
id: this.model.id,
scope: this.model.name
}
});
actions.push({
actionList.push({
action: 'setNotHeld',
label: 'Set Not Held',
data: {
id: this.model.id
id: this.model.id,
scope: this.model.name
}
});
}
return actions;
return actionList;
}
});
@@ -103,8 +103,8 @@ Espo.define('Views.Dashlets.Abstract.RecordList', ['Views.Dashlets.Abstract.Base
var attributes = this.getCreateAttributes() || {};
this.notify('Loading...');
var viewName = this.getMetadata().get('clientDefs.' + this.scope + '.modalViews.edit') || 'Modals.Edit';
this.createView('quickCreate', 'Modals.Edit', {
var viewName = this.getMetadata().get('clientDefs.' + this.scope + '.modalViews.edit') || 'views/modals/edit';
this.createView('quickCreate', viewName, {
scope: this.scope,
attributes: attributes,
}, function (view) {
@@ -131,20 +131,11 @@ Espo.define('Views.Fields.LinkMultiple', 'Views.Fields.Base', function (Dep) {
var boolList = this.getSelectBoolFilterList();
var where = [];
if (boolList) {
where.push({
type: 'bool',
value: boolList
});
url += '&' + $.param({'boolFilterList': boolList});
}
var primary = this.getSelectPrimaryFilterName();
if (primary) {
where.push({
type: 'primary',
value: primary
});
}
if (where.length) {
url += '&' + $.param({'where': where});
url += '&' + $.param({'primaryFilter': primary});
}
return url;
},
@@ -134,20 +134,11 @@ Espo.define('Views.Fields.LinkParent', 'Views.Fields.Base', function (Dep) {
var boolList = this.getSelectBoolFilterList();
var where = [];
if (boolList) {
where.push({
type: 'bool',
value: boolList
});
url += '&' + $.param({'boolFilterList': boolList});
}
var primary = this.getSelectPrimaryFilterName();
if (primary) {
where.push({
type: 'primary',
value: primary
});
}
if (where.length) {
url += '&' + $.param({'where': where});
url += '&' + $.param({'primaryFilter': primary});
}
return url;
},
+2 -11
View File
@@ -136,20 +136,11 @@ Espo.define('Views.Fields.Link', 'Views.Fields.Base', function (Dep) {
var boolList = this.getSelectBoolFilterList();
var where = [];
if (boolList) {
where.push({
type: 'bool',
value: boolList
});
url += '&' + $.param({'boolFilterList': boolList});
}
var primary = this.getSelectPrimaryFilterName();
if (primary) {
where.push({
type: 'primary',
value: primary
});
}
if (where.length) {
url += '&' + $.param({'where': where});
url += '&' + $.param({'primaryFilter': primary});
}
return url;
},
+21 -17
View File
@@ -19,11 +19,11 @@
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
************************************************************************/
Espo.define('Views.Record.List', 'View', function (Dep) {
Espo.define('views/record/list', 'view', function (Dep) {
return Dep.extend({
template: 'record.list',
template: 'record/list',
/**
* @param {String} Type of the list. Can be 'list', 'listSmall'.
@@ -860,12 +860,14 @@ Espo.define('Views.Record.List', 'View', function (Dep) {
var id = data.id;
if (!id) return;
var viewName = this.getMetadata().get('clientDefs.' + this.scope + '.modalViews.detail') || 'Modals.Detail';
var scope = data.scope || this.scope;
var viewName = this.getMetadata().get('clientDefs.' + scope + '.modalViews.detail') || 'Modals.Detail';
if (!this.quickDetailDisabled) {
this.notify('Loading...');
this.createView('quickDetail', viewName, {
scope: this.scope,
scope: scope,
model: this.collection.get(id),
id: id
}, function (view) {
@@ -881,27 +883,29 @@ Espo.define('Views.Record.List', 'View', function (Dep) {
}, this);
}.bind(this));
} else {
this.getRouter().navigate('#' + this.scope + '/view/' + id, {trigger: true});
this.getRouter().navigate('#' + scope + '/view/' + id, {trigger: true});
}
},
actionQuickEdit: function (d) {
d = d || {}
var id = d.id;
actionQuickEdit: function (data) {
data = data || {}
var id = data.id;
if (!id) return;
var viewName = this.getMetadata().get('clientDefs.' + this.scope + '.modalViews.edit') || 'Modals.Edit';
var scope = data.scope || this.scope;
var viewName = this.getMetadata().get('clientDefs.' + scope + '.modalViews.edit') || 'Modals.Edit';
if (!this.quickEditDisabled) {
this.notify('Loading...');
this.createView('quickEdit', viewName, {
scope: this.scope,
scope: scope,
id: id,
model: this.collection.get(id),
fullFormDisabled: d.noFullForm,
returnUrl: '#' + this.scope,
fullFormDisabled: data.noFullForm,
returnUrl: '#' + scope,
returnDispatchParams: {
controller: this.scope,
controller: scope,
action: null,
options: {
isReturn: true
@@ -923,19 +927,19 @@ Espo.define('Views.Record.List', 'View', function (Dep) {
}.bind(this));
} else {
this.getRouter().dispatch(this.scope, 'edit', {
this.getRouter().dispatch(scope, 'edit', {
id: id,
model: this.collection.get(id),
returnUrl: '#' + this.scope,
returnUrl: '#' + scope,
returnDispatchParams: {
controller: this.scope,
controller: scope,
action: null,
options: {
isReturn: true
}
}
});
this.getRouter().navigate('#' + this.scope + '/edit/' + id, {trigger: false});
this.getRouter().navigate('#' + scope + '/edit/' + id, {trigger: false});
}
},