opp last stage field

This commit is contained in:
yuri
2018-06-11 14:46:21 +03:00
parent 16c9f46583
commit 3c8b2534eb
12 changed files with 165 additions and 13 deletions
@@ -87,8 +87,9 @@ class Opportunity extends \Espo\Core\Controllers\Record
$dateFrom = $request->get('dateFrom');
$dateTo = $request->get('dateTo');
$dateFilter = $request->get('dateFilter');
$useLastStage = $request->get('useLastStage') === 'true';
return $this->getService('Opportunity')->reportSalesPipeline($dateFilter, $dateFrom, $dateTo);
return $this->getService('Opportunity')->reportSalesPipeline($dateFilter, $dateFrom, $dateTo, $useLastStage);
}
public function postActionMassConvertCurrency($params, $data, $request)
@@ -42,6 +42,53 @@ class Opportunity extends \Espo\Core\ORM\Repositories\RDB
}
}
if (!$entity->isAttributeChanged('lastStage') && $entity->isAttributeChanged('stage')) {
$probability = $this->getMetadata()->get(['entityDefs', 'Opportunity', 'fields', 'stage', 'probabilityMap', $entity->get('stage')], 0);
$probabilityMap = $this->getMetadata()->get(['entityDefs', 'Opportunity', 'fields', 'stage', 'probabilityMap'], []);
if (!$probability) {
$stageList = $this->getMetadata()->get('entityDefs.Opportunity.fields.stage.options', []);
if ($entity->isNew()) {
if (count($stageList)) {
$min = 100;
$minStage = null;
foreach ($stageList as $stage) {
if (!empty($probabilityMap[$stage]) && $probabilityMap[$stage] !== 100) {
if ($probabilityMap[$stage] < $min) {
$min = $probabilityMap[$stage];
$minStage = $stage;
}
}
}
if ($minStage) {
$entity->set('lastStage', $minStage);
}
}
} else {
$lastStageProbability = $this->getMetadata()->get(['entityDefs', 'Opportunity', 'fields', 'stage', 'probabilityMap', $entity->get('lastStage')], 0);
if ($lastStageProbability === 100) {
if (count($stageList)) {
$max = 0;
$maxStage = null;
foreach ($stageList as $stage) {
if (!empty($probabilityMap[$stage]) && $probabilityMap[$stage] !== 100) {
if ($probabilityMap[$stage] > $max) {
$max = $probabilityMap[$stage];
$maxStage = $stage;
}
}
}
if ($maxStage) {
$entity->set('lastStage', $maxStage);
}
}
}
}
} else {
$entity->set('lastStage', $entity->get('stage'));
}
}
parent::beforeSave($entity, $options);
}
@@ -1,5 +1,6 @@
{
"fields": {
"futureDays": "Next X Days"
"futureDays": "Next X Days",
"useLastStage": "Group by last reached stage"
}
}
@@ -15,7 +15,8 @@
"campaign": "Campaign",
"originalLead": "Original Lead",
"amountCurrency": "Amount Currency",
"contactRole": "Contact Role"
"contactRole": "Contact Role",
"lastStage": "Last Stage"
},
"links": {
"contacts": "Contacts",
@@ -73,6 +73,21 @@
}
},
"kanbanViewMode": true,
"dynamicLogic": {
"fields": {
"lastStage": {
"visible": {
"conditionGroup": [
{
"type": "equals",
"attribute": "stage",
"value": "Closed Lost"
}
]
}
}
}
},
"color": "#9fc77e",
"iconClass": "fas fa-dollar-sign"
}
@@ -21,6 +21,9 @@
"options": ["currentYear", "currentQuarter", "currentMonth", "ever", "between"],
"default": "currentYear",
"translation": "Global.options.dateSearchRanges"
},
"useLastStage": {
"type": "bool"
}
},
"layout": [
@@ -31,7 +34,7 @@
],
[
{"name": "dateFilter"},
false
{"name": "useLastStage"}
],
[
{"name": "dateFrom"},
@@ -62,6 +62,12 @@
}
]
},
"lastStage": {
"type": "enum",
"view": "crm:views/opportunity/fields/last-stage",
"customizationOptionsDisabled": true,
"customizationDefaultDisabled": true
},
"probability": {
"type": "int",
"required": true,
@@ -247,6 +253,9 @@
"stage": {
"columns": ["stage", "deleted"]
},
"lastStage": {
"columns": ["lastStage"]
},
"assignedUser": {
"columns": ["assignedUserId", "deleted"]
},
@@ -36,7 +36,7 @@ use \Espo\Core\Exceptions\Forbidden;
class Opportunity extends \Espo\Services\Record
{
public function reportSalesPipeline($dateFilter, $dateFrom = null, $dateTo = null)
public function reportSalesPipeline($dateFilter, $dateFrom = null, $dateTo = null, $useLastStage = false)
{
if (in_array('amount', $this->getAcl()->getScopeForbiddenAttributeList('Opportunity'))) {
throw new Forbidden();
@@ -46,19 +46,34 @@ class Opportunity extends \Espo\Services\Record
list($dateFrom, $dateTo) = $this->getDateRangeByFilter($dateFilter);
}
$lostStageList = [];
$probabilityMap = $this->getMetadata()->get(['entityDefs', 'Opportunity', 'fields', 'stage', 'probabilityMap'], []);
$stageList = $this->getMetadata()->get('entityDefs.Opportunity.fields.stage.options', []);
foreach ($stageList as $stage) {
if (empty($probabilityMap[$stage])) {
$lostStageList[] = $stage;
}
}
$pdo = $this->getEntityManager()->getPDO();
$options = $this->getMetadata()->get('entityDefs.Opportunity.fields.stage.options', []);
$selectManager = $this->getSelectManagerFactory()->create('Opportunity');
$stageField = 'stage';
if ($useLastStage) {
$stageField = 'lastStage';
}
$selectParams = [
'select' => ['stage', ['SUM:amountConverted', 'amount']],
'select' => [$stageField, ['SUM:amountConverted', 'amount']],
'whereClause' => [
'stage!=' => 'Closed Lost'
[$stageField . '!=' => $lostStageList],
[$stageField . '!=' => null]
],
'orderBy' => 'LIST:stage:' . implode(',', $options),
'groupBy' => ['stage']
'orderBy' => 'LIST:'.$stageField.':' . implode(',', $options),
'groupBy' => [$stageField]
];
if ($dateFilter !== 'ever') {
@@ -81,7 +96,7 @@ class Opportunity extends \Espo\Services\Record
$result = array();
foreach ($rows as $row) {
$result[$row['stage']] = floatval($row['amount']);
$result[$row[$stageField]] = floatval($row['amount']);
}
return $result;
@@ -43,6 +43,10 @@ Espo.define('crm:views/dashlets/sales-pipeline', 'crm:views/dashlets/abstract/ch
if (this.getDateFilter() === 'between') {
url += '&dateFrom=' + this.getOption('dateFrom') + '&dateTo=' + this.getOption('dateTo');
}
if (this.getOption('useLastStage')) {
url += '&useLastStage=true';
}
return url;
},
@@ -0,0 +1,52 @@
/************************************************************************
* 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('crm:views/opportunity/fields/last-stage', 'views/fields/enum', function (Dep) {
return Dep.extend({
setup: function () {
var optionList = this.getMetadata().get('entityDefs.Opportunity.fields.stage.options', []);
var probabilityMap = this.getMetadata().get('entityDefs.Opportunity.fields.stage.probabilityMap', {});
this.params.options = [];
optionList.forEach(function (item) {
if (!probabilityMap[item]) return;
if (probabilityMap[item] === 100) return;
this.params.options.push(item);
}, this);
this.params.translation = 'Opportunity.options.stage';
Dep.prototype.setup.call(this);
}
});
});
+5 -1
View File
@@ -37,7 +37,11 @@ define('dynamic-handler', [], function () {
init: function () {},
onChange: function (model, o) {}
onChange: function (model, o) {},
getMetadata: function () {
return this.recordView.getMetadata()
}
});
DynamicHandler.extend = Backbone.Router.extend;
+2 -2
View File
@@ -789,14 +789,14 @@ Espo.define('views/record/detail', ['views/record/base', 'view-record-helper'],
this.listenTo(this.model, 'change', function (model, o) {
if ('onChange' in this.dynamicHandler) {
this.dynamicHandler.onChange(model, o);
this.dynamicHandler.onChange.call(this.dynamicHandler, model, o);
}
var changedAttributes = model.changedAttributes();
for (var attribute in changedAttributes) {
var methodName = 'onChange' + Espo.Utils.upperCaseFirst(attribute);
if (methodName in this.dynamicHandler) {
this.dynamicHandler[methodName](model, changedAttributes[attribute], o);
this.dynamicHandler[methodName].call(this.dynamicHandler, model, changedAttributes[attribute], o);
}
}
}, this);