Compare commits

...

37 Commits

Author SHA1 Message Date
yuri adfec3441b template tab 2018-02-09 12:05:01 +02:00
yuri 9068302357 version 2018-02-08 16:52:23 +02:00
yuri 1bb0439f19 fix model reset 2018-02-08 16:02:12 +02:00
yuri f28585e245 mass update ui improvement 2018-02-08 15:37:41 +02:00
yuri 99cfeb25ee fix print to pdf button 2018-02-08 14:18:58 +02:00
yuri 6b7da9a39e fix entity manger and field manager 2018-02-08 13:22:00 +02:00
yuri 18bba7baec entity manager fix 2018-02-08 13:15:49 +02:00
yuri ec8ffbc090 chart dashlets acl 2018-02-08 12:52:18 +02:00
yuri 60eb1ca236 fix sales by month chart 2018-02-08 12:40:49 +02:00
yuri 5e3756313c fix sales pipeline chart 2018-02-08 12:20:13 +02:00
yuri 938415aa79 cleanup 2018-02-08 11:54:15 +02:00
yuri 2fd77e4034 excel export duration 2018-02-07 15:23:06 +02:00
yuri 4f004b8402 note status styles change 2018-02-07 11:07:24 +02:00
yuri 1adb19f427 fix field manager 2018-02-06 17:28:37 +02:00
yuri fce4a4b1d9 skip additional select params 2018-02-06 17:13:48 +02:00
yuri 9da8ab9bdb chart fix 2018-02-06 13:46:59 +02:00
yuri 8d1724df84 less opp stages 2018-02-06 13:01:09 +02:00
yuri cd88d59937 po ability to build po for all languages 2018-02-06 12:28:20 +02:00
yuri cab4f5ce64 opp stages translation 2018-02-06 12:22:20 +02:00
yuri 62ab1f8d00 fix export 2018-02-06 11:29:17 +02:00
yuri f0e1439c79 fix chart legend 2018-02-06 11:03:10 +02:00
yuri 5060918087 print pdf for all entity types 2018-02-05 16:42:33 +02:00
yuri bf6b058152 reset page title on logout 2018-02-05 15:54:13 +02:00
yuri 40b607a73f template change 2018-02-05 15:48:35 +02:00
yuri 2bdbba0eff fix sales chart 2018-02-05 15:42:56 +02:00
yuri 058d821a7c fix chart 2018-02-05 15:32:22 +02:00
yuri 4575ff89ad chart fixes 2018-02-05 12:59:50 +02:00
yuri 608454fcf2 fix css 2018-02-05 12:54:26 +02:00
yuri 58c1bec2da css fix 2018-02-05 12:30:57 +02:00
yuri e1ce16c399 chart legend title 2018-02-05 12:16:05 +02:00
yuri 2d3a748286 chart fixes 2018-02-05 11:58:21 +02:00
yuri 06834dd8bf fix calendar 2018-02-05 10:18:31 +02:00
yuri e4c0f19b1c fix css 2018-02-02 16:03:29 +02:00
yuri 84faf7a782 entity manager: supporting templates in modules 2018-02-02 14:27:40 +02:00
yuri 61db675ab1 css fix 2018-02-01 16:48:59 +02:00
yuri 59e72796e3 css fix 2018-02-01 16:43:07 +02:00
yuri 76e61a6358 fix dropdown on small screens 2018-02-01 14:54:04 +02:00
56 changed files with 634 additions and 206 deletions
+44
View File
@@ -87,6 +87,20 @@ class Xlsx extends \Espo\Core\Injectable
}
}
public function filterFieldList($entityType, $fieldList, $exportAllFields)
{
if ($exportAllFields) {
foreach ($fieldList as $i => $field) {
$type = $this->getMetadata()->get(['entityDefs', $entityType, 'fields', $field, 'type']);
if (in_array($type, ['linkMultiple', 'attachmentMultiple'])) {
unset($fieldList[$i]);
}
}
}
return array_values($fieldList);
}
public function addAdditionalAttributes($entityType, &$attributeList, $fieldList)
{
$linkList = [];
@@ -420,6 +434,36 @@ class Xlsx extends \Espo\Core\Injectable
$value .= $row[$name.'Country'];
}
$sheet->setCellValue("$col$rowNumber", $value);
} else if ($type == 'duration') {
if (!empty($row[$name])) {
$seconds = intval($row[$name]);
$days = intval(floor($seconds / 86400));
$seconds = $seconds - $days * 86400;
$hours = intval(floor($seconds / 3600));
$seconds = $seconds - $hours * 3600;
$minutes = intval(floor($seconds / 60));
$value = '';
if ($days) {
$value .= (string) $days . $this->getInjection('language')->translate('d', 'durationUnits');
if ($minutes || $hours) {
$value .= ' ';
}
}
if ($hours) {
$value .= (string) $hours . $this->getInjection('language')->translate('h', 'durationUnits');
if ($minutes) {
$value .= ' ';
}
}
if ($minutes) {
$value .= (string) $minutes . $this->getInjection('language')->translate('m', 'durationUnits');
}
$sheet->setCellValue("$col$rowNumber", $value);
}
} else {
if (array_key_exists($name, $row)) {
$sheet->setCellValue("$col$rowNumber", $row[$name]);
+1 -1
View File
@@ -351,7 +351,7 @@ class Importer
if ($email->get('messageId')) {
$duplicate = $this->getEntityManager()->getRepository('Email')->select(['id'])->where(array(
'messageId' => $email->get('messageId')
))->findOne();
))->findOne(['skipAdditionalSelectParams' => true]);
if ($duplicate) {
return $duplicate;
}
@@ -368,14 +368,14 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
protected function processEmailAddressSave(Entity $entity)
{
if ($entity->hasRelation('emailAddresses') && $entity->hasAttribute('emailAddress')) {
$emailAddressRepository = $this->getEntityManager()->getRepository('EmailAddress')->storeEntityEmailAddress($entity);
$this->getEntityManager()->getRepository('EmailAddress')->storeEntityEmailAddress($entity);
}
}
protected function processPhoneNumberSave(Entity $entity)
{
if ($entity->hasRelation('phoneNumbers') && $entity->hasAttribute('phoneNumber')) {
$emailAddressRepository = $this->getEntityManager()->getRepository('PhoneNumber')->storeEntityPhoneNumber($entity);
$this->getEntityManager()->getRepository('PhoneNumber')->storeEntityPhoneNumber($entity);
}
}
+102 -17
View File
@@ -30,6 +30,7 @@
namespace Espo\Core\Utils;
use \Espo\Core\Exceptions\Error;
use \Espo\Core\Exceptions\BadRequest;
use \Espo\Core\Exceptions\Forbidden;
use \Espo\Core\Exceptions\Conflict;
use \Espo\Core\Utils\Json;
@@ -122,17 +123,36 @@ class EntityManager
return false;
}
public function create($name, $type, $params = array())
public function create($name, $type, $params = [], $replaceData = [])
{
$name = ucfirst($name);
$name = trim($name);
if (empty($name) || empty($type)) {
throw new BadRequest();
}
if (strlen($name) > 100) {
throw new Error('Entity name should not be longer than 100.');
}
if (is_numeric($name[0])) {
throw new Error('Bad entity name.');
}
if (!in_array($type, $this->getMetadata()->get(['app', 'entityTemplateList'], []))) {
throw new Error('Type \''.$type.'\' does not exist.');
}
$templateDefs = $this->getMetadata()->get(['app', 'entityTemplates', $type], []);
if (!empty($templateDefs['isNotCreatable']) && empty($params['forceCreate'])) {
throw new Error('Type \''.$type.'\' is not creatable.');
}
if ($this->getMetadata()->get('scopes.' . $name)) {
throw new Conflict('Entity \''.$name.'\' already exists.');
}
if (empty($name) || empty($type)) {
throw new Error();
}
if ($this->checkControllerExists($name)) {
throw new Conflict('Entity name \''.$name.'\' is not allowed.');
@@ -149,9 +169,20 @@ class EntityManager
$normalizedName = Util::normilizeClassName($name);
$templateNamespace = "\Espo\Core\Templates";
$templatePath = "application/Espo/Core/Templates";
$templateModuleName = null;
if (!empty($templateDefs['module'])) {
$templateModuleName = $templateDefs['module'];
$normalizedTemplateModuleName = Util::normilizeClassName($templateModuleName);
$templateNamespace = "\Espo\Modules\\{$normalizedTemplateModuleName}\Core\Templates";
$templatePath = "application/Espo/Modules/".$normalizedTemplateModuleName."/Core/Templates";
}
$contents = "<" . "?" . "php\n\n".
"namespace Espo\Custom\Entities;\n\n".
"class {$normalizedName} extends \Espo\Core\Templates\Entities\\{$type}\n".
"class {$normalizedName} extends {$templateNamespace}\Entities\\{$type}\n".
"{\n".
" protected \$entityType = \"$name\";\n".
"}\n";
@@ -161,7 +192,7 @@ class EntityManager
$contents = "<" . "?" . "php\n\n".
"namespace Espo\Custom\Controllers;\n\n".
"class {$normalizedName} extends \Espo\Core\Templates\Controllers\\{$type}\n".
"class {$normalizedName} extends {$templateNamespace}\Controllers\\{$type}\n".
"{\n".
"}\n";
$filePath = "custom/Espo/Custom/Controllers/{$normalizedName}.php";
@@ -169,7 +200,7 @@ class EntityManager
$contents = "<" . "?" . "php\n\n".
"namespace Espo\Custom\Services;\n\n".
"class {$normalizedName} extends \Espo\Core\Templates\Services\\{$type}\n".
"class {$normalizedName} extends {$templateNamespace}\Services\\{$type}\n".
"{\n".
"}\n";
$filePath = "custom/Espo/Custom/Services/{$normalizedName}.php";
@@ -177,17 +208,17 @@ class EntityManager
$contents = "<" . "?" . "php\n\n".
"namespace Espo\Custom\Repositories;\n\n".
"class {$normalizedName} extends \Espo\Core\Templates\Repositories\\{$type}\n".
"class {$normalizedName} extends {$templateNamespace}\Repositories\\{$type}\n".
"{\n".
"}\n";
$filePath = "custom/Espo/Custom/Repositories/{$normalizedName}.php";
$this->getFileManager()->putContents($filePath, $contents);
if (file_exists('application/Espo/Core/Templates/SelectManagers/' . $type . '.php')) {
if (file_exists($templatePath . '/SelectManagers/' . $type . '.php')) {
$contents = "<" . "?" . "php\n\n".
"namespace Espo\Custom\SelectManagers;\n\n".
"class {$normalizedName} extends \Espo\Core\Templates\SelectManagers\\{$type}\n".
"class {$normalizedName} extends {$templateNamespace}\SelectManagers\\{$type}\n".
"{\n".
"}\n";
@@ -214,19 +245,26 @@ class EntityManager
$languageList = $this->getConfig()->get('languageList', []);
foreach ($languageList as $language) {
$filePath = 'application/Espo/Core/Templates/i18n/' . $language . '/' . $type . '.json';
$filePath = $templatePath . '/i18n/' . $language . '/' . $type . '.json';
if (!file_exists($filePath)) continue;
$languageContents = $this->getFileManager()->getContents($filePath);
$languageContents = str_replace('{entityType}', $name, $languageContents);
$languageContents = str_replace('{entityTypeTranslated}', $labelSingular, $languageContents);
foreach ($replaceData as $key => $value) {
$languageContents = str_replace('{'.$key.'}', $value, $languageContents);
}
$destinationFilePath = 'custom/Espo/Custom/Resources/i18n/' . $language . '/' . $name . '.json';
$this->getFileManager()->putContents($destinationFilePath, $languageContents);
}
$filePath = "application/Espo/Core/Templates/Metadata/{$type}/scopes.json";
$filePath = $templatePath . "/Metadata/{$type}/scopes.json";
$scopesDataContents = $this->getFileManager()->getContents($filePath);
$scopesDataContents = str_replace('{entityType}', $name, $scopesDataContents);
foreach ($replaceData as $key => $value) {
$scopesDataContents = str_replace('{'.$key.'}', $value, $scopesDataContents);
}
$scopesData = Json::decode($scopesDataContents, true);
$scopesData['stream'] = $stream;
@@ -236,18 +274,28 @@ class EntityManager
$scopesData['object'] = true;
$scopesData['isCustom'] = true;
if (!empty($templateDefs['isNotRemovable']) ||!empty($params['isNotRemovable'])) {
$scopesData['isNotRemovable'] = true;
}
$this->getMetadata()->set('scopes', $name, $scopesData);
$filePath = "application/Espo/Core/Templates/Metadata/{$type}/entityDefs.json";
$filePath = $templatePath . "/Metadata/{$type}/entityDefs.json";
$entityDefsDataContents = $this->getFileManager()->getContents($filePath);
$entityDefsDataContents = str_replace('{entityType}', $name, $entityDefsDataContents);
$entityDefsDataContents = str_replace('{tableName}', $this->getEntityManager()->getQuery()->toDb($name), $entityDefsDataContents);
foreach ($replaceData as $key => $value) {
$entityDefsDataContents = str_replace('{'.$key.'}', $value, $entityDefsDataContents);
}
$entityDefsData = Json::decode($entityDefsDataContents, true);
$this->getMetadata()->set('entityDefs', $name, $entityDefsData);
$filePath = "application/Espo/Core/Templates/Metadata/{$type}/clientDefs.json";
$filePath = $templatePath . "/Metadata/{$type}/clientDefs.json";
$clientDefsContents = $this->getFileManager()->getContents($filePath);
$clientDefsContents = str_replace('{entityType}', $name, $clientDefsContents);
foreach ($replaceData as $key => $value) {
$clientDefsContents = str_replace('{'.$key.'}', $value, $clientDefsContents);
}
$clientDefsData = Json::decode($clientDefsContents, true);
$this->getMetadata()->set('clientDefs', $name, $clientDefsData);
@@ -257,7 +305,7 @@ class EntityManager
$this->getMetadata()->save();
$this->getLanguage()->save();
$layoutsPath = "application/Espo/Core/Templates/Layouts/{$type}";
$layoutsPath = $templatePath . "/Layouts/{$type}";
if ($this->getFileManager()->isDir($layoutsPath)) {
$this->getFileManager()->copy($layoutsPath, 'custom/Espo/Custom/Resources/layouts/' . $name);
}
@@ -326,7 +374,7 @@ class EntityManager
return true;
}
public function delete($name)
public function delete($name, $params = [])
{
if (!$this->isCustom($name)) {
throw new Forbidden;
@@ -336,6 +384,19 @@ class EntityManager
$type = $this->getMetadata()->get(['scopes', $name, 'type']);
$isNotRemovable = $this->getMetadata()->get(['scopes', $name, 'isNotRemovable']);
$templateDefs = $this->getMetadata()->get(['app', 'entityTemplates', $type], []);
$templateModuleName = null;
if (!empty($templateDefs['module'])) {
$templateModuleName = $templateDefs['module'];
}
if ((!empty($templateDefs['isNotRemovable']) || $isNotRemovable) && empty($params['forceRemove'])) {
throw new Error('Type \''.$type.'\' is not removable.');
}
$unsets = array(
'entityDefs',
'clientDefs',
@@ -414,6 +475,18 @@ class EntityManager
}
}
if (empty($link) || empty($linkForeign)) {
throw new BadRequest();
}
if (strlen($link) > 255 || strlen($linkForeign) > 255) {
throw new Error('Link name should not be longer than 255.');
}
if (is_numeric($link[0]) || is_numeric($linkForeign[0])) {
throw new Error('Bad link name.');
}
$linkMultipleField = false;
if (!empty($params['linkMultipleField'])) {
$linkMultipleField = true;
@@ -786,8 +859,20 @@ class EntityManager
protected function getHook($type)
{
$templateDefs = $this->getMetadata()->get(['app', 'entityTemplates', $type], []);
$className = '\\Espo\\Core\\Utils\\EntityManager\\Hooks\\' . $type . 'Type';
$className = $this->getMetadata()->get(['entityTemplates', $type, 'hookClassName'], $className);
$templateModuleName = null;
if (!empty($templateDefs['module'])) {
$templateModuleName = $templateDefs['module'];
$normalizedTemplateModuleName = Util::normilizeClassName($templateModuleName);
$className = '\\Espo\\Modules\\'.$normalizedTemplateModuleName.'\\Core\\Utils\\EntityManager\\Hooks\\' . $type . 'Type';
}
$className = $this->getMetadata()->get(['app', 'entityTemplates', $type, 'hookClassName'], $className);
if (class_exists($className)) {
$hook = new $className();
@@ -95,6 +95,18 @@ class FieldManager
public function create($scope, $name, $fieldDefs)
{
if (empty($name)) {
throw new BadRequest();
}
if (strlen($name) > 255) {
throw new Error('Field name should not be longer than 255.');
}
if (is_numeric($name[0])) {
throw new Error('Bad field name.');
}
$existingField = $this->getFieldDefs($scope, $name);
if (isset($existingField)) {
throw new Conflict('Field ['.$name.'] exists in '.$scope);
@@ -37,7 +37,7 @@ class Opportunity extends \Espo\Core\Controllers\Record
public function actionReportByLeadSource($params, $data, $request)
{
$level = $this->getAcl()->getLevel('Opportunity', 'read');
if (!$level || $level == 'own' || $level == 'no') {
if (!$level || $level == 'no') {
throw new Forbidden();
}
@@ -51,7 +51,7 @@ class Opportunity extends \Espo\Core\Controllers\Record
public function actionReportByStage($params, $data, $request)
{
$level = $this->getAcl()->getLevel('Opportunity', 'read');
if (!$level || $level == 'own' || $level == 'no') {
if (!$level || $level == 'no') {
throw new Forbidden();
}
@@ -65,7 +65,7 @@ class Opportunity extends \Espo\Core\Controllers\Record
public function actionReportSalesByMonth($params, $data, $request)
{
$level = $this->getAcl()->getLevel('Opportunity', 'read');
if (!$level || $level == 'own' || $level == 'no') {
if (!$level || $level == 'no') {
throw new Forbidden();
}
@@ -79,7 +79,7 @@ class Opportunity extends \Espo\Core\Controllers\Record
public function actionReportSalesPipeline($params, $data, $request)
{
$level = $this->getAcl()->getLevel('Opportunity', 'read');
if (!$level || $level == 'own' || $level == 'no') {
if (!$level || $level == 'no') {
throw new Forbidden();
}
@@ -90,4 +90,3 @@ class Opportunity extends \Espo\Core\Controllers\Record
return $this->getService('Opportunity')->reportSalesPipeline($dateFilter, $dateFrom, $dateTo);
}
}
@@ -19,19 +19,6 @@
"documents": "Dokumenty",
"campaign": "Kampaň"
},
"options": {
"stage": {
"Request": "Poptávka",
"Quote": "Nabídka",
"Order": "Objednávka",
"ToProduction": "Čeká na výrobu",
"InProduction": "Vyrábí se...",
"Dispatch": "Expedice",
"Delivered": "Dodáno",
"Closed Won": "VYFAKTUROVÁNO",
"Closed Lost": "ODMÍTNUTO"
}
},
"labels": {
"Create Opportunity": "Vytvořit případ"
},
@@ -26,6 +26,8 @@
"stage": {
"Prospecting": "Undersøges",
"Qualification": "Kvalifikation",
"Proposal": "Tilbud",
"Negotiation": "Forhandler",
"Needs Analysis": "Behovsanalyse",
"Value Proposition": "Vejledende Tilbud",
"Id. Decision Makers": "Identifikation af Beslutningstagere",
@@ -25,6 +25,8 @@
"stage": {
"Prospecting": "Identifikation",
"Qualification": "Qualifikation",
"Proposal": "Angebot",
"Negotiation": "Verhandlung",
"Needs Analysis": "Bedarfserhebung",
"Value Proposition": "Richtangebot",
"Id. Decision Makers": "Identifikation der Entscheider",
@@ -27,6 +27,8 @@
"stage": {
"Prospecting": "Prospecting",
"Qualification": "Qualification",
"Proposal": "Proposal",
"Negotiation": "Negotiation",
"Needs Analysis": "Needs Analysis",
"Value Proposition": "Value Proposition",
"Id. Decision Makers": "Id. Decision Makers",
@@ -27,6 +27,8 @@
"stage": {
"Prospecting": "Prospección",
"Qualification": "Calificación",
"Proposal": "Propuesta",
"Negotiation": "Negociación",
"Needs Analysis": "Análisis de necesidades",
"Value Proposition": "Propuesta de valor",
"Id. Decision Makers": "Identificar tomador de decisiones",
@@ -27,6 +27,8 @@
"stage": {
"Prospecting": "Prospección",
"Qualification": "Calificación",
"Proposal": "Cotización con Propuesta",
"Negotiation": "Negociación",
"Needs Analysis": "Análisis de Necesidades",
"Value Proposition": "Propuesta de Valor",
"Id. Decision Makers": "Id. Tomadores de Decisiones",
@@ -27,6 +27,8 @@
"stage": {
"Prospecting": "Prospection",
"Qualification": "Qualification",
"Proposal": "Proposition",
"Negotiation": "Négociation",
"Needs Analysis": "Analyse des besoins",
"Value Proposition": "Valeur de la proposition",
"Id. Decision Makers": "Id. Decisionnaire",
@@ -24,6 +24,8 @@
"Prospecting": "Pencarian",
"Qualification": "Kualifikasi",
"Needs Analysis": "Butuh analisa",
"Proposal": "Proposal",
"Negotiation": "Negosiasi",
"Value Proposition": "Proposisi nilai",
"Id. Decision Makers": "Id. Pembuat keputusan",
"Perception Analysis": "Analisis persepsi",
@@ -27,6 +27,8 @@
"stage": {
"Prospecting": "Prospecting",
"Qualification": "Qualifica",
"Proposal": "Proposta",
"Negotiation": "Negoziazione",
"Needs Analysis": "Necessita di analisi",
"Value Proposition": "Proposta di Valore",
"Id. Decision Makers": "Id. Responsabile",
@@ -24,6 +24,8 @@
"stage": {
"Prospecting": "Potencialių klientų paieška",
"Qualification": "Kvalifikacija",
"Proposal": "Pasiūlymas",
"Negotiation": "Derybos",
"Needs Analysis": "Reikalinga analizė",
"Value Proposition": "Pasiūlymo vertė",
"Id. Decision Makers": "Id. Sprendimus priimantys žmonės",
@@ -25,6 +25,8 @@
"stage": {
"Prospecting": "Undersøkes",
"Qualification": "Kvalitetskontrolleres",
"Proposal": "Prisforslag",
"Negotiation": "Forhandler",
"Needs Analysis": "Trenger analyse",
"Value Proposition": "Verdivurdering",
"Id. Decision Makers": "Ser etter beslutningstagere",
@@ -23,6 +23,8 @@
"stage": {
"Prospecting": "Prospectie",
"Qualification": "Kwalificatie",
"Proposal": "Voorstel",
"Negotiation": "Onderhandeling",
"Needs Analysis": "Analyse behoefte",
"Value Proposition": "Waarde voorstel",
"Id. Decision Makers": "Id. Beslissers",
@@ -23,12 +23,14 @@
"stage": {
"Prospecting": "I Spotkanie",
"Qualification": "Kwalifikacja",
"Proposal": "Oferta Cenowa",
"Negotiation": "Negocjacje",
"Needs Analysis": "Dodatkowa Analiza",
"Value Proposition": "Oferta Produktowa",
"Id. Decision Makers": "Oferta przekazna do osoby decyzyjnej",
"Perception Analysis": "Perception Analysis",
"Proposal/Price Quote": "Oferta Cenowa",
"Negotiation/Review": "Negocjacje ",
"Negotiation/Review": "Negocjacje",
"Closed Won": "Zakończone Wygrane",
"Closed Lost": "Zakończone Przegrane"
}
@@ -22,6 +22,8 @@
"stage": {
"Prospecting": "Prospectando",
"Qualification": "Qualificação",
"Proposal": "Proposta",
"Negotiation": "Negociação",
"Needs Analysis": "Necessita de Análise",
"Value Proposition": "Proposta de Valor",
"Id. Decision Makers": "Identificação dos Decisores",
@@ -18,6 +18,8 @@
"stage": {
"Prospecting": "Prospectare",
"Qualification": "Calificari",
"Proposal": "Propunere",
"Negotiation": "Negociere",
"Needs Analysis": "Necesita Analiza",
"Value Proposition": "Valoare Propunere",
"Id. Decision Makers": "Id. Factori de Decizie",
@@ -25,7 +27,7 @@
"Proposal/Price Quote": "Propunere/Oferta Pret",
"Negotiation/Review": "Negociere/Revizuire",
"Closed Won": "Inchide ca Castigat",
"Closed Lost": "Inchide ca Pierdut"
"Closed Lost": "Inchide ca Pierdut"
}
},
"labels": {
@@ -26,6 +26,8 @@
"stage": {
"Prospecting": "Привлечение клиента",
"Qualification": "Оценка возможности",
"Proposal": "Предложение",
"Negotiation": "Согласование",
"Needs Analysis": "Требует анализа",
"Value Proposition": "Выбор предложения/оферты",
"Id. Decision Makers": "Определение ответственного лица",
@@ -27,6 +27,8 @@
"stage": {
"Prospecting": "Izviđanje",
"Qualification": "Kvalifikacija",
"Proposal": "Predlog",
"Negotiation": "Pregovori",
"Needs Analysis": "Analiza potrebna",
"Value Proposition": "Dobitak",
"Id. Decision Makers": "Id. Donosioci odluka",
@@ -26,6 +26,8 @@
"stage": {
"Prospecting": "Araştırma",
"Qualification": "Yeterlilik",
"Proposal": "Proposal",
"Negotiation": "Görüşme",
"Needs Analysis": "Analiz Yapılmalı",
"Value Proposition": "Değer Önerisi",
"Id. Decision Makers": "Id. Karar Vericiler",
@@ -26,12 +26,14 @@
"stage": {
"Prospecting": "Проспектінг",
"Qualification": "Кваліфікація",
"Proposal": "Пропозиція",
"Negotiation": "Переговори",
"Needs Analysis": "Вимагає аналізу",
"Value Proposition": "Цінова пропозиція",
"Id. Decision Makers": "Визначення осіб, що приймають рішення",
"Perception Analysis": "Аналізу сприйняття",
"Proposal/Price Quote": "Квота пропозицій/цін",
"Negotiation/Review": "Заперечення/перегляд",
"Negotiation/Review": "Переговори/перегляд",
"Closed Won": "Закрито переможно",
"Closed Lost": "Закрито провалом"
}
@@ -24,8 +24,10 @@
"Perception Analysis": "Phân tích khả năng",
"Proposal/Price Quote": "Giá đặt ra",
"Negotiation/Review": "Đàm phán / đánh giá",
"Proposal": "Giá đặt ra",
"Negotiation": "Đàm phán",
"Closed Won": "Thắng thầu",
"Closed Lost": "Thua thầu"
"Closed Lost": "Thua thầu"
}
},
"labels": {
@@ -27,6 +27,8 @@
"stage": {
"Prospecting": "预期",
"Qualification": "评价",
"Proposal": "提案",
"Negotiation": "协商",
"Needs Analysis": "需求分析",
"Value Proposition": "价值主张",
"Id. Decision Makers": "Id决策者",
@@ -186,6 +186,7 @@
"layoutListDisabled": true,
"layoutMassUpdateDisabled": true,
"importDisabled": true,
"exportDisabled": true,
"noLoad": true
},
"targetList": {
@@ -42,19 +42,15 @@
},
"stage": {
"type": "enum",
"options": ["Prospecting", "Qualification", "Needs Analysis", "Value Proposition", "Id. Decision Makers", "Perception Analysis", "Proposal/Price Quote", "Negotiation/Review", "Closed Won", "Closed Lost"],
"options": ["Prospecting", "Qualification", "Proposal", "Negotiation", "Closed Won", "Closed Lost"],
"view": "crm:views/opportunity/fields/stage",
"default": "Prospecting",
"audited": true,
"probabilityMap": {
"Prospecting": 10,
"Qualification": 10,
"Needs Analysis": 20,
"Value Proposition": 50,
"Id. Decision Makers": 60,
"Perception Analysis": 70,
"Proposal/Price Quote": 75,
"Negotiation/Review": 90,
"Qualification": 20,
"Proposal": 50,
"Negotiation": 80,
"Closed Won": 100,
"Closed Lost": 0
},
@@ -38,34 +38,41 @@ class Opportunity extends \Espo\Services\Record
{
public function reportSalesPipeline($dateFilter, $dateFrom = null, $dateTo = null)
{
if (in_array('amount', $this->getAcl()->getScopeForbiddenAttributeList('Opportunity'))) {
throw new Forbidden();
}
if ($dateFilter !== 'between' && $dateFilter !== 'ever') {
list($dateFrom, $dateTo) = $this->getDateRangeByFilter($dateFilter);
}
$pdo = $this->getEntityManager()->getPDO();
$options = $this->getMetadata()->get('entityDefs.Opportunity.fields.stage.options');
$options = $this->getMetadata()->get('entityDefs.Opportunity.fields.stage.options', []);
$sql = "
SELECT opportunity.stage AS `stage`, SUM(opportunity.amount * currency.rate) as `amount`
FROM opportunity
JOIN currency ON currency.id = opportunity.amount_currency
WHERE
opportunity.deleted = 0 AND
";
$selectManager = $this->getSelectManagerFactory()->create('Opportunity');
$selectParams = [
'select' => ['stage', ['SUM:amountConverted', 'amount']],
'whereClause' => [
'stage!=' => 'Closed Lost'
],
'orderBy' => 'LIST:stage:' . implode(',', $options),
'groupBy' => ['stage']
];
if ($dateFilter !== 'ever') {
$sql .= "
opportunity.close_date >= ".$pdo->quote($dateFrom)." AND
opportunity.close_date < ".$pdo->quote($dateTo)." AND
";
$selectParams['whereClause'][] = [
'closeDate>=' => $dateFrom,
'closeDate<' => $dateTo
];
}
$sql .= "
opportunity.stage <> 'Closed Lost'
GROUP BY opportunity.stage
ORDER BY FIELD(opportunity.stage, '".implode("','", $options)."')
";
$selectManager->applyAccess($selectParams);
$this->getEntityManager()->getRepository('Opportunity')->handleSelectParams($selectParams);
$sql = $this->getEntityManager()->getQuery()->createSelectQuery('Opportunity', $selectParams);
$sth = $pdo->prepare($sql);
$sth->execute();
@@ -82,31 +89,46 @@ class Opportunity extends \Espo\Services\Record
public function reportByLeadSource($dateFilter, $dateFrom = null, $dateTo = null)
{
if (in_array('amount', $this->getAcl()->getScopeForbiddenAttributeList('Opportunity'))) {
throw new Forbidden();
}
if (in_array('leadSource', $this->getAcl()->getScopeForbiddenAttributeList('Opportunity'))) {
throw new Forbidden();
}
if ($dateFilter !== 'between' && $dateFilter !== 'ever') {
list($dateFrom, $dateTo) = $this->getDateRangeByFilter($dateFilter);
}
$pdo = $this->getEntityManager()->getPDO();
$sql = "
SELECT opportunity.lead_source AS `leadSource`, SUM(opportunity.amount * currency.rate * opportunity.probability / 100) as `amount`
FROM opportunity
JOIN currency ON currency.id = opportunity.amount_currency
WHERE
opportunity.deleted = 0 AND
";
$options = $this->getMetadata()->get('entityDefs.Lead.fields.source.options', []);
$selectManager = $this->getSelectManagerFactory()->create('Opportunity');
$selectParams = [
'select' => ['leadSource', ['SUM:amountWeightedConverted', 'amount']],
'whereClause' => [
'stage!=' => 'Closed Lost',
['leadSource!=' => ''],
['leadSource!=' => null]
],
'orderBy' => 'LIST:leadSource:' . implode(',', $options),
'groupBy' => ['leadSource']
];
if ($dateFilter !== 'ever') {
$sql .= "
opportunity.close_date >= ".$pdo->quote($dateFrom)." AND
opportunity.close_date < ".$pdo->quote($dateTo)." AND
";
$selectParams['whereClause'][] = [
'closeDate>=' => $dateFrom,
'closeDate<' => $dateTo
];
}
$sql .= "
opportunity.stage <> 'Closed Lost' AND
opportunity.lead_source <> ''
GROUP BY opportunity.lead_source
";
$selectManager->applyAccess($selectParams);
$this->getEntityManager()->getRepository('Opportunity')->handleSelectParams($selectParams);
$sql = $this->getEntityManager()->getQuery()->createSelectQuery('Opportunity', $selectParams);
$sth = $pdo->prepare($sql);
$sth->execute();
@@ -123,35 +145,42 @@ class Opportunity extends \Espo\Services\Record
public function reportByStage($dateFilter, $dateFrom = null, $dateTo = null)
{
if (in_array('amount', $this->getAcl()->getScopeForbiddenAttributeList('Opportunity'))) {
throw new Forbidden();
}
if ($dateFilter !== 'between' && $dateFilter !== 'ever') {
list($dateFrom, $dateTo) = $this->getDateRangeByFilter($dateFilter);
}
$pdo = $this->getEntityManager()->getPDO();
$options = $this->getMetadata()->get('entityDefs.Opportunity.fields.stage.options');
$options = $this->getMetadata()->get('entityDefs.Opportunity.fields.stage.options', []);
$sql = "
SELECT opportunity.stage AS `stage`, SUM(opportunity.amount * currency.rate) as `amount`
FROM opportunity
JOIN currency ON currency.id = opportunity.amount_currency
WHERE
opportunity.deleted = 0 AND
";
$selectManager = $this->getSelectManagerFactory()->create('Opportunity');
$selectParams = [
'select' => ['stage', ['SUM:amountConverted', 'amount']],
'whereClause' => [
'stage!=' => 'Closed Lost',
'stage!=' => 'Closed Won'
],
'orderBy' => 'LIST:stage:' . implode(',', $options),
'groupBy' => ['stage']
];
if ($dateFilter !== 'ever') {
$sql .= "
opportunity.close_date >= ".$pdo->quote($dateFrom)." AND
opportunity.close_date < ".$pdo->quote($dateTo)." AND
";
$selectParams['whereClause'][] = [
'closeDate>=' => $dateFrom,
'closeDate<' => $dateTo
];
}
$sql .= "
opportunity.stage <> 'Closed Lost' AND
opportunity.stage <> 'Closed Won'
GROUP BY opportunity.stage
ORDER BY FIELD(opportunity.stage, '".implode("','", $options)."')
";
$selectManager->applyAccess($selectParams);
$this->getEntityManager()->getRepository('Opportunity')->handleSelectParams($selectParams);
$sql = $this->getEntityManager()->getQuery()->createSelectQuery('Opportunity', $selectParams);
$sth = $pdo->prepare($sql);
$sth->execute();
@@ -168,32 +197,40 @@ class Opportunity extends \Espo\Services\Record
public function reportSalesByMonth($dateFilter, $dateFrom = null, $dateTo = null)
{
if (in_array('amount', $this->getAcl()->getScopeForbiddenAttributeList('Opportunity'))) {
throw new Forbidden();
}
if ($dateFilter !== 'between' && $dateFilter !== 'ever') {
list($dateFrom, $dateTo) = $this->getDateRangeByFilter($dateFilter);
}
$pdo = $this->getEntityManager()->getPDO();
$sql = "
SELECT DATE_FORMAT(opportunity.close_date, '%Y-%m') AS `month`, SUM(opportunity.amount * currency.rate) as `amount`
FROM opportunity
JOIN currency ON currency.id = opportunity.amount_currency
WHERE
opportunity.deleted = 0 AND
";
$selectManager = $this->getSelectManagerFactory()->create('Opportunity');
$selectParams = [
'select' => [['MONTH:closeDate', 'month'], ['SUM:amountConverted', 'amount']],
'whereClause' => [
'stage' => 'Closed Won'
],
'orderBy' => 1,
'groupBy' => ['MONTH:closeDate']
];
if ($dateFilter !== 'ever') {
$sql .= "
opportunity.close_date >= ".$pdo->quote($dateFrom)." AND
opportunity.close_date < ".$pdo->quote($dateTo)." AND
";
$selectParams['whereClause'][] = [
'closeDate>=' => $dateFrom,
'closeDate<' => $dateTo
];
}
$sql .= "
opportunity.stage = 'Closed Won'
GROUP BY DATE_FORMAT(opportunity.close_date, '%Y-%m')
ORDER BY `month`
";
$selectManager->applyAccess($selectParams);
$this->getEntityManager()->getRepository('Opportunity')->handleSelectParams($selectParams);
$sql = $this->getEntityManager()->getQuery()->createSelectQuery('Opportunity', $selectParams);
$sth = $pdo->prepare($sql);
$sth->execute();
@@ -207,7 +244,14 @@ class Opportunity extends \Espo\Services\Record
$dt = new \DateTime($dateFrom);
$dtTo = new \DateTime($dateTo);
$dtTo->setDate($dtTo->format('Y'), $dtTo->format('m'), 1);
if (intval($dtTo->format('d')) !== 1) {
$dtTo->setDate($dtTo->format('Y'), $dtTo->format('m'), 1);
$dtTo->modify('+ 1 month');
} else {
$dtTo->setDate($dtTo->format('Y'), $dtTo->format('m'), 1);
}
if ($dt && $dtTo) {
$interval = new \DateInterval('P1M');
while ($dt->getTimestamp() <= $dtTo->getTimestamp()) {
@@ -219,7 +263,6 @@ class Opportunity extends \Espo\Services\Record
}
}
$keyList = array_keys($result);
sort($keyList);
+12 -4
View File
@@ -187,7 +187,9 @@ class RDB extends \Espo\ORM\Repository
public function find(array $params = array())
{
$this->handleSelectParams($params);
if (empty($params['skipAdditionalSelectParams'])) {
$this->handleSelectParams($params);
}
$params = $this->getSelectParams($params);
$dataArr = $this->getMapper()->select($this->seed, $params);
@@ -230,7 +232,9 @@ class RDB extends \Espo\ORM\Repository
}
if ($entityType) {
$this->getEntityManager()->getRepository($entityType)->handleSelectParams($params);
if (empty($params['skipAdditionalSelectParams'])) {
$this->getEntityManager()->getRepository($entityType)->handleSelectParams($params);
}
}
$result = $this->getMapper()->selectRelated($entity, $relationName, $params);
@@ -250,7 +254,9 @@ class RDB extends \Espo\ORM\Repository
return;
}
$entityType = $entity->relations[$relationName]['entity'];
$this->getEntityManager()->getRepository($entityType)->handleSelectParams($params);
if (empty($params['skipAdditionalSelectParams'])) {
$this->getEntityManager()->getRepository($entityType)->handleSelectParams($params);
}
return intval($this->getMapper()->countRelated($entity, $relationName, $params));
}
@@ -423,7 +429,9 @@ class RDB extends \Espo\ORM\Repository
public function count(array $params = array())
{
$this->handleSelectParams($params);
if (empty($params['skipAdditionalSelectParams'])) {
$this->handleSelectParams($params);
}
$params = $this->getSelectParams($params);
$count = $this->getMapper()->count($this->seed, $params);
@@ -437,6 +437,12 @@
"dayNamesShort": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
"dayNamesMin": ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]
},
"durationUnits": {
"d": "d",
"h": "h",
"m": "m",
"s": "s"
},
"options": {
"salutationName": {
"Mr.": "Mr.",
@@ -124,7 +124,6 @@
},
"statusStyles": {
"Lead": {
"New" : "primary",
"Assigned" : "primary",
"In Process" : "primary",
"Converted" : "success",
@@ -140,14 +139,8 @@
"Duplicate" : "danger"
},
"Opportunity": {
"Prospecting": "primary",
"Qualification": "primary",
"Needs Analysis": "primary",
"Value Proposition": "primary",
"Id. Decision Makers": "primary",
"Perception Analysis": "primary",
"Proposal/Price Quote": "primary",
"Negotiation/Review": "primary",
"Proposal": "primary",
"Negotiation": "primary",
"Closed Won" : "success",
"Closed Lost" : "danger"
},
@@ -1,7 +1,7 @@
{
"entity": true,
"layouts": false,
"tab": false,
"tab": true,
"acl": "recordAllTeamNo",
"customizable": true,
"disabled": false
+29 -1
View File
@@ -44,6 +44,7 @@ class App extends \Espo\Core\Services\Base
$this->addDependency('container');
$this->addDependency('entityManager');
$this->addDependency('metadata');
$this->addDependency('selectManagerFactory');
}
protected function getPreferences()
@@ -112,7 +113,8 @@ class App extends \Espo\Core\Services\Base
'settings' => $settings,
'language' => $language,
'appParams' => [
'maxUploadSize' => $this->getMaxUploadSize() / 1024.0 / 1024.0
'maxUploadSize' => $this->getMaxUploadSize() / 1024.0 / 1024.0,
'templateEntityTypeList' => $this->getTemplateEntityTypeList()
]
];
}
@@ -210,6 +212,31 @@ class App extends \Espo\Core\Services\Base
return $value;
}
protected function getTemplateEntityTypeList()
{
if (!$this->getAcl()->checkScope('Template')) {
return [];
}
$list = [];
$selectManager = $this->getInjection('selectManagerFactory')->create('Template');
$selectParams = $selectManager->getEmptySelectParams();
$selectManager->applyAccess($selectParams);
$templateList = $this->getEntityManager()->getRepository('Template')
->select(['entityType'])
->groupBy(['entityType'])
->find($selectParams);
foreach ($templateList as $template) {
$list[] = $template->get('entityType');
}
return $list;
}
public function jobClearCache()
{
$this->getInjection('container')->get('dataManager')->clearCache();
@@ -219,4 +246,5 @@ class App extends \Espo\Core\Services\Base
{
$this->getInjection('container')->get('dataManager')->rebuild();
}
}
+6
View File
@@ -1424,10 +1424,12 @@ class Record extends \Espo\Core\Services\Base
}
if (!array_key_exists('fieldList', $params)) {
$exportAllFields = true;
$fieldDefs = $this->getMetadata()->get(['entityDefs', $this->entityType, 'fields'], []);
$fieldList = array_keys($fieldDefs);
array_unshift($fieldList, 'id');
} else {
$exportAllFields = false;
$fieldList = $params['fieldList'];
}
@@ -1438,6 +1440,10 @@ class Record extends \Espo\Core\Services\Base
}
$fieldList = array_values($fieldList);
if (method_exists($exportObj, 'filterFieldList')) {
$fieldList = $exportObj->filterFieldList($this->entityType, $fieldList, $exportAllFields);
}
if (is_null($attributeList)) {
$attributeList = [];
$seed = $this->getEntityManager()->getEntity($this->entityType);
@@ -158,10 +158,6 @@ Espo.define('crm:views/calendar/calendar', ['view', 'lib!full-calendar'], functi
if (Object.prototype.toString.call(this.enabledScopeList) !== '[object Array]') {
this.enabledScopeList = [];
}
this.once('remove', function () {
this.isRemoved = true;
}, this);
},
toggleScopeFilter: function (name) {
@@ -380,7 +376,7 @@ Espo.define('crm:views/calendar/calendar', ['view', 'lib!full-calendar'], functi
},
adjustSize: function () {
if (this.isRemoved) return;
if (this.isRemoved()) return;
var height = this.getCalculatedHeight();
this.$calendar.fullCalendar('option', 'contentHeight', height);
},
@@ -48,7 +48,7 @@ Espo.define('crm:views/dashlets/abstract/chart', ['views/dashlets/abstract/base'
hoverColor: '#FF3F19',
legendColumnWidth: 90,
legendColumnWidth: 110,
legendColumnNumber: 6,
@@ -85,6 +85,7 @@ Espo.define('crm:views/dashlets/abstract/chart', ['views/dashlets/abstract/base'
this.once('after:render', function () {
$(window).on('resize.chart' + this.name, function () {
this.adjustContainer();
this.draw();
}.bind(this));
}, this);
@@ -141,38 +142,73 @@ Espo.define('crm:views/dashlets/abstract/chart', ['views/dashlets/abstract/base'
return '';
},
getLegentColumnNumber: function () {
getLegendColumnNumber: function () {
var width = this.$el.closest('.panel-body').width();
var legendColumnNumber = Math.floor(width / this.legendColumnWidth);
return legendColumnNumber || this.legendColumnNumber;
},
getLegentHeight: function () {
var lineNumber = Math.ceil(this.chartData.length / this.getLegentColumnNumber());
getLegendHeight: function () {
var lineNumber = Math.ceil(this.chartData.length / this.getLegendColumnNumber());
var legendHeight = 0;
var lineHeight = this.getThemeManager().getParam('dashletChartLegentRowHeight') || 22;
var lineHeight = this.getThemeManager().getParam('dashletChartLegendRowHeight') || 19;
var paddingTopHeight = this.getThemeManager().getParam('dashletChartLegendPaddingTopHeight') || 7;
if (lineNumber > 0) {
legendHeight = lineHeight * lineNumber;
legendHeight = lineHeight * lineNumber + paddingTopHeight;
}
return legendHeight;
},
adjustContainer: function () {
var legendHeight = this.getLegendHeight();
var heightCss = 'calc(100% - ' + legendHeight.toString() + 'px)';
this.$container.css('height', heightCss);
},
adjustLegend: function () {
var number = this.getLegendColumnNumber();
if (!number) return;
var dashletChartLegendBoxWidth = this.getThemeManager().getParam('dashletChartLegendBoxWidth') || 21;
var containerWidth = this.$legendContainer.width();
var width = Math.floor((containerWidth - dashletChartLegendBoxWidth * number) / number);
var columnNumber = this.$legendContainer.find('> table tr:first-child > td').size() / 2;
var 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(function(i, span) {
span.setAttribute('title', span.textContent);
}.bind(this));
},
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);
var $container = this.$container = this.$el.find('.chart-container');
var legendHeight = this.getLegentHeight();
var heightCss = 'calc(100% - '+legendHeight.toString()+'px)';
$container.css('height', heightCss);
this.adjustContainer();
setTimeout(function () {
if (!$container.size() || !$container.is(":visible")) return;
if (!this.$container.size() || !this.$container.is(":visible")) return;
this.draw();
}.bind(this), 1);
});
@@ -112,7 +112,7 @@ Espo.define('crm:views/dashlets/opportunities-by-lead-source', 'crm:views/dashle
},
legend: {
show: true,
noColumns: this.getLegentColumnNumber(),
noColumns: this.getLegendColumnNumber(),
container: this.$el.find('.legend-container'),
labelBoxMargin: 0,
labelFormatter: self.labelFormatter.bind(self),
@@ -120,6 +120,8 @@ Espo.define('crm:views/dashlets/opportunities-by-lead-source', 'crm:views/dashle
backgroundOpacity: 0
}
});
this.adjustLegend();
}
});
});
@@ -73,6 +73,16 @@ Espo.define('crm:views/dashlets/opportunities-by-stage', 'crm:views/dashlets/abs
i++;
}, this);
var max = 0;
if (d.length) {
d.forEach(function (item) {
if ( item.value && item.value > max) {
max = item.value;
}
}, this);
}
this.max = max;
return data;
},
@@ -108,11 +118,15 @@ Espo.define('crm:views/dashlets/opportunities-by-stage', 'crm:views/dashlets/abs
xaxis: {
min: 0,
color: this.textColor,
max: this.max + 0.08 * this.max,
tickFormatter: function (value) {
if (value == 0) {
return '';
}
if (value % 1 == 0) {
if (value > self.max + 0.05 * this.max) {
return '';
}
return self.currencySymbol + self.formatNumber(Math.floor(value)).toString();
}
return '';
@@ -131,7 +145,7 @@ Espo.define('crm:views/dashlets/opportunities-by-stage', 'crm:views/dashlets/abs
},
legend: {
show: true,
noColumns: this.getLegentColumnNumber(),
noColumns: this.getLegendColumnNumber(),
container: this.$el.find('.legend-container'),
labelBoxMargin: 0,
labelFormatter: self.labelFormatter.bind(self),
@@ -139,6 +153,8 @@ Espo.define('crm:views/dashlets/opportunities-by-stage', 'crm:views/dashlets/abs
backgroundOpacity: 0
}
});
this.adjustLegend();
}
});
});
@@ -46,7 +46,7 @@ Espo.define('crm:views/dashlets/sales-by-month', 'crm:views/dashlets/abstract/ch
return url;
},
getLegentHeight: function () {
getLegendHeight: function () {
return 0;
},
@@ -70,13 +70,20 @@ Espo.define('crm:views/dashlets/sales-by-month', 'crm:views/dashlets/abstract/ch
var data = [];
var max = 0;
values.forEach(function (value, i) {
if (value && value > max) {
max = value;
}
data.push({
data: [[i, value]],
color: (value >= mid) ? this.successColor : this.colorBad
});
}, this);
this.max = max;
return data;
},
@@ -110,6 +117,7 @@ Espo.define('crm:views/dashlets/sales-by-month', 'crm:views/dashlets/abstract/ch
min: 0,
showLabels: true,
color: this.textColor,
max: this.max + 0.08 * this.max,
tickFormatter: function (value) {
if (value == 0) {
return '';
@@ -127,6 +135,9 @@ Espo.define('crm:views/dashlets/sales-by-month', 'crm:views/dashlets/abstract/ch
if (value % 1 == 0) {
var i = parseInt(value);
if (i in self.monthList) {
if (self.monthList.length > 12 && i === self.monthList.length - 1) {
return '';
}
return moment(self.monthList[i] + '-01').format('MMM YYYY');
}
}
@@ -69,16 +69,15 @@ Espo.define('crm:views/dashlets/sales-pipeline', 'crm:views/dashlets/abstract/ch
});
}
this.maxY = 1000;
var max = 0;
if (d.length) {
for (var i = 0; i < d.length; i++) {
var y = d[i].value + (d[i].value / 20);
if (y > this.maxY) {
this.maxY = y;
d.forEach(function (item) {
if ( item.value && item.value > max) {
max = item.value;
}
}
}, this);
}
this.max = max;
return data;
},
@@ -124,7 +123,7 @@ Espo.define('crm:views/dashlets/sales-pipeline', 'crm:views/dashlets/abstract/ch
},
yaxis: {
min: 0,
max: this.maxY,
max: this.max + 0.08 * this.max,
showLabels: false
},
xaxis: {
@@ -147,7 +146,7 @@ Espo.define('crm:views/dashlets/sales-pipeline', 'crm:views/dashlets/abstract/ch
},
legend: {
show: true,
noColumns: this.getLegentColumnNumber(),
noColumns: this.getLegendColumnNumber(),
container: this.$el.find('.legend-container'),
labelBoxMargin: 0,
labelFormatter: self.labelFormatter.bind(self),
@@ -155,6 +154,8 @@ Espo.define('crm:views/dashlets/sales-pipeline', 'crm:views/dashlets/abstract/ch
backgroundOpacity: 0
}
});
this.adjustLegend();
}
});
@@ -55,7 +55,7 @@
</button>
<ul class="dropdown-menu pull-right">
<li><a href="javascript:" data-action="editFormula" data-scope="{{name}}">{{translate 'Formula' scope='EntityManager'}}</a></li>
{{#if isCustom}}
{{#if isRemovable}}
<li><a href="javascript:" data-action="removeEntity" data-scope="{{name}}">{{translate 'Remove'}}</a></li>
{{/if}}
</ul>
+4 -3
View File
@@ -1,12 +1,13 @@
{{#unless fields}}
{{translate 'No fields available for Mass Update'}}
{{else}}
{{else}}
<div class="button-container">
<button class="btn btn-default pull-right hidden" data-action="reset">{{translate 'Reset'}}</button>
<div class="btn-group">
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown" tabindex="-1">{{translate 'Select Field'}} <span class="caret"></span></button>
<button class="btn btn-default dropdown-toggle select-field" data-toggle="dropdown" tabindex="-1">{{translate 'Select Field'}} <span class="caret"></span></button>
<ul class="dropdown-menu pull-left filter-list">
{{#each ../fields}}
<li><a href="javascript:" data-name="{{./this}}" data-action="add-field">{{translate this scope=../../scope category='fields'}}</a></li>
<li data-name="{{./this}}"><a href="javascript:" data-name="{{./this}}" data-action="add-field">{{translate this scope=../../scope category='fields'}}</a></li>
{{/each}}
</ul>
</div>
+2
View File
@@ -39,6 +39,8 @@ Espo.define('controllers/base', 'controller', function (Dep) {
},
logout: function () {
var title = this.getConfig().get('applicationName') || 'EspoCRM';
$('head title').text(title);
this.trigger('logout');
},
@@ -90,9 +90,15 @@ Espo.define('views/admin/entity-manager/index', 'view', function (Dep) {
scopeList.forEach(function (scope) {
var d = this.getMetadata().get('scopes.' + scope);
var isRemovable = !!d.isCustom;
if (d.isNotRemovable) {
isRemovable = false;
}
this.scopeDataList.push({
name: scope,
isCustom: d.isCustom,
isRemovable: isRemovable,
customizable: d.customizable,
type: d.type,
label: this.getLanguage().translate(scope, 'scopeNames'),
+1 -1
View File
@@ -117,7 +117,7 @@ Espo.define('views/admin/field-manager/edit', ['view', 'model'], function (Dep,
}.bind(this))
]).then(function () {
this.paramList = [];
var paramList = this.getFieldManager().getParams(this.type) || [];
var paramList = Espo.Utils.clone(this.getFieldManager().getParams(this.type) || []);
if (!this.isNew) {
(this.getMetadata().get(['entityDefs', this.scope, 'fields', this.field, 'fieldManagerAdditionalParamList']) || []).forEach(function (item) {
+3 -3
View File
@@ -112,13 +112,13 @@ Espo.define('views/fields/duration', 'views/fields/enum', function (Dep) {
var parts = [];
if (days) {
parts.push(days + '' + this.getLanguage().translate('d'));
parts.push(days + '' + this.getLanguage().translate('d', 'durationUnits'));
}
if (hours) {
parts.push(hours + '' + this.getLanguage().translate('h'));
parts.push(hours + '' + this.getLanguage().translate('h', 'durationUnits'));
}
if (minutes) {
parts.push(minutes + '' + this.getLanguage().translate('m'));
parts.push(minutes + '' + this.getLanguage().translate('m', 'durationUnits'));
}
return parts.join(' ');
},
+35 -14
View File
@@ -49,13 +49,11 @@ Espo.define('views/modals/mass-update', 'views/modal', function (Dep) {
},
'click a[data-action="add-field"]': function (e) {
var field = $(e.currentTarget).data('name');
var $ul = $(e.currentTarget).closest('ul');
$(e.currentTarget).parent().remove();
if ($ul.children().size() == 0) {
$ul.parent().find('button').addClass('disabled');
}
this.addField(field);
},
'click button[data-action="reset"]': function (e) {
this.reset();
}
},
setup: function () {
@@ -96,17 +94,23 @@ Espo.define('views/modals/mass-update', 'views/modal', function (Dep) {
}.bind(this));
}.bind(this));
this.fieldsToUpdate = [];
this.fieldList = [];
},
addField: function (name) {
this.enableButton('update');
this.$el.find('[data-action="reset"]').removeClass('hidden');
this.$el.find('ul.filter-list li[data-name="'+name+'"]').addClass('hidden');
if (this.$el.find('ul.filter-list li:not(.hidden)').size() == 0) {
this.$el.find('button.select-field').addClass('disabled').attr('disabled', 'disabled');
}
this.notify('Loading...');
var label = this.translate(name, 'fields', this.scope);
var html = '<div class="cell form-group col-sm-6"><label class="control-label">'+label+'</label><div class="field" data-name="'+name+'" /></div>';
var html = '<div class="cell form-group col-sm-6" data-name="'+name+'"><label class="control-label">'+label+'</label><div class="field" data-name="'+name+'" /></div>';
this.$el.find('.fields-container').append(html);
var type = this.model.getFieldType(name);
@@ -121,7 +125,7 @@ Espo.define('views/modals/mass-update', 'views/modal', function (Dep) {
},
mode: 'edit'
}, function (view) {
this.fieldsToUpdate.push(name);
this.fieldList.push(name);
view.render();
view.notify(false);
}.bind(this));
@@ -131,7 +135,7 @@ Espo.define('views/modals/mass-update', 'views/modal', function (Dep) {
var self = this;
var attributes = {};
this.fieldsToUpdate.forEach(function (field) {
this.fieldList.forEach(function (field) {
var view = self.getView(field);
_.extend(attributes, view.fetch());
});
@@ -139,7 +143,7 @@ Espo.define('views/modals/mass-update', 'views/modal', function (Dep) {
this.model.set(attributes);
var notValid = false;
this.fieldsToUpdate.forEach(function (field) {
this.fieldList.forEach(function (field) {
var view = self.getView(field);
notValid = view.validate() || notValid;
});
@@ -164,12 +168,29 @@ Espo.define('views/modals/mass-update', 'views/modal', function (Dep) {
},
error: function () {
self.notify('Error occurred', 'error');
},
}
});
} else {
this.notify('Not valid', 'error');
}
},
reset: function () {
this.fieldList.forEach(function (field) {
this.clearView(field);
this.$el.find('.cell[data-name="'+field+'"]').remove();
}, this);
this.fieldList = [];
this.model.clear();
this.$el.find('[data-action="reset"]').addClass('hidden');
this.$el.find('button.select-field').removeClass('disabled').removeAttr('disabled');
this.$el.find('ul.filter-list').find('li').removeClass('hidden');
this.disableButton('update');
}
});
});
+14 -3
View File
@@ -253,9 +253,9 @@ Espo.define('views/record/base', ['view', 'view-record-helper', 'dynamic-logic']
this.recordHelper = new ViewRecordHelper();
this.on('remove', function () {
this.once('remove', function () {
if (this.isChanged) {
this.model.set(this.attributes);
this.resetModelChanges();
}
this.setIsNotChanged();
}, this);
@@ -302,6 +302,17 @@ Espo.define('views/record/base', ['view', 'view-record-helper', 'dynamic-logic']
return !_.isEqual(this.attributes[name], this.model.get(name));
},
resetModelChanges: function () {
var attributes = this.model.attributes;
for (var attr in attributes) {
if (!(attr in this.attributes)) {
this.model.unset(attr);
}
}
this.model.set(this.attributes);
},
initDynamicLogic: function () {
if (!Object.keys(this.dynamicLogicDefs || {}).length) return;
@@ -474,7 +485,7 @@ Espo.define('views/record/base', ['view', 'view-record-helper', 'dynamic-logic']
if (xhr.status == 400) {
if (!this.isNew) {
this.model.set(this.attributes);
this.resetModelChanges();
}
}
+34 -8
View File
@@ -223,11 +223,25 @@ Espo.define('views/record/detail', ['views/record/base', 'view-record-helper'],
}
}
if (this.printPdfAction) {
this.dropdownItemList.push({
'label': 'Print to PDF',
'name': 'printPdf'
});
if (this.type === 'detail') {
var printPdfAction = this.printPdfAction;
if (!printPdfAction) {
if (~(this.getHelper().getAppParam('templateEntityTypeList') || []).indexOf(this.entityType)) {
printPdfAction = true;
}
}
if (printPdfAction && !this.getAcl().check('Template', 'read')) {
printPdfAction = false
}
if (printPdfAction) {
this.dropdownItemList.push({
'label': 'Print to PDF',
'name': 'printPdf'
});
}
}
},
@@ -459,11 +473,23 @@ Espo.define('views/record/detail', ['views/record/base', 'view-record-helper'],
},
cancelEdit: function () {
this.model.set(this.attributes);
this.resetModelChanges();
this.setDetailMode();
this.setIsNotChanged();
},
resetModelChanges: function () {
var attributes = this.model.attributes;
for (var attr in attributes) {
if (!(attr in this.attributes)) {
this.model.unset(attr);
}
}
this.model.set(this.attributes);
},
delete: function () {
this.confirm({
message: this.translate('removeRecordConfirmation', 'messages'),
@@ -649,9 +675,9 @@ Espo.define('views/record/detail', ['views/record/base', 'view-record-helper'],
}
}
this.on('remove', function () {
this.once('remove', function () {
if (this.isChanged) {
this.model.set(this.attributes);
this.resetModelChanges();
}
this.setIsNotChanged();
$(window).off('scroll.detail-' + this.numId);
+15 -3
View File
@@ -57,9 +57,21 @@ Espo.define('views/template/record/edit', 'views/record/edit', function (Dep) {
return;
}
var header = this.getMetadata().get(['entityDefs', 'Template', 'defaultTemplates', entityType, 'header']);
var body = this.getMetadata().get(['entityDefs', 'Template', 'defaultTemplates', entityType, 'body']);
var footer = this.getMetadata().get(['entityDefs', 'Template', 'defaultTemplates', entityType, 'footer']);
var header, body, footer;
if (this.getMetadata().get(['entityDefs', 'Template', 'defaultTemplates', entityType])) {
header = this.getMetadata().get(['entityDefs', 'Template', 'defaultTemplates', entityType, 'header']);
body = this.getMetadata().get(['entityDefs', 'Template', 'defaultTemplates', entityType, 'body']);
footer = this.getMetadata().get(['entityDefs', 'Template', 'defaultTemplates', entityType, 'footer']);
} else {
var scopeType = this.getMetadata().get(['scopes', entityType, 'type']);
if (scopeType) {
if (this.getMetadata().get(['entityDefs', 'Template', 'defaultTemplates', scopeType])) {
header = this.getMetadata().get(['entityDefs', 'Template', 'defaultTemplates', scopeType, 'header']);
body = this.getMetadata().get(['entityDefs', 'Template', 'defaultTemplates', scopeType, 'body']);
footer = this.getMetadata().get(['entityDefs', 'Template', 'defaultTemplates', scopeType, 'footer']);
}
}
}
if (header) {
this.model.set('header', header);
+23 -4
View File
@@ -1280,9 +1280,10 @@ stream-head-text-container .span {
font-size: @font-size-base;
}
.dashlet-body .legend-container {
.legend-container {
overflow-x: hidden;
overflow-y: hidden;
padding-top: 7px;
}
.legend-container table td {
@@ -1301,12 +1302,20 @@ stream-head-text-container .span {
overflow: hidden;
}
.dashlet-body .legend-container td.flotr-legend-label > span {
max-height: 2em;
display: inline-block;
.legend-container td.flotr-legend-label {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.legend-container td.flotr-legend-label > span {
display: block;
text-overflow: ellipsis;
overflow: hidden;
line-height: 1.3em;
max-height: 15px;
}
.legend-container .flotr-legend-color-box > div {
position: relative;
top: 1px;
@@ -1341,6 +1350,7 @@ table.table td.cell .complex-text {
p:first-child,
ul:first-child,
ol:first-child,
pre:first-child,
blockquote:first-child {
margin-top: 0 !important;
}
@@ -1829,4 +1839,13 @@ pre > code {
.panel-body > div:first-child > .list:first-child {
margin-top: 0;
}
.list > .table {
.dropdown-menu {
position: static !important;
}
.pull-right.open > .btn {
float: right;
}
}
}
+5 -1
View File
@@ -57,7 +57,7 @@ table.table {
}
.record .middle > .panel:first-child {
border-top-width: 1px;
border-top-width: 0;
}
.record .middle > .panel {
@@ -152,3 +152,7 @@ table.table {
.admin-panel-iframe-container {
background-color: @white-color;
}
.panel.dashlet > .panel-heading > .btn-group {
margin-top: -6px;
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "espocrm",
"version": "5.0.4",
"version": "5.0.5",
"description": "",
"main": "index.php",
"repository": {
+18 -2
View File
@@ -182,7 +182,23 @@ PO.prototype.fixString = function (savedString) {
return savedString;
}
var po = new PO(espoPath, language);
po.run();
if (language === '--all') {
var pathToLanguage = espoPath + '/application/Espo/Resources/i18n/';
var languageList = [];
fs.readdirSync(pathToLanguage).forEach(function (dir) {
if (dir.indexOf('_') == 2) {
languageList.push(dir);
}
});
languageList.forEach(function (language) {
var po = new PO(espoPath, language);
po.run();
});
} else {
var po = new PO(espoPath, language);
po.run();
}