Merge branch 'hotfix/5.0.4'
This commit is contained in:
@@ -30,6 +30,7 @@
|
||||
namespace Espo\Acl;
|
||||
|
||||
use \Espo\ORM\Entity;
|
||||
use \Espo\Entities\User as EntityUser;
|
||||
|
||||
class User extends \Espo\Core\Acl\Base
|
||||
{
|
||||
@@ -37,5 +38,20 @@ class User extends \Espo\Core\Acl\Base
|
||||
{
|
||||
return $user->id === $entity->id;
|
||||
}
|
||||
}
|
||||
|
||||
public function checkEntityDelete(EntityUser $user, Entity $entity, $data)
|
||||
{
|
||||
if ($entity->id === 'system') {
|
||||
return false;
|
||||
}
|
||||
return parent::checkEntityDelete($user, $entity, $data);
|
||||
}
|
||||
|
||||
public function checkEntityEdit(EntityUser $user, Entity $entity, $data)
|
||||
{
|
||||
if ($entity->id === 'system') {
|
||||
return false;
|
||||
}
|
||||
return $this->checkEntity($user, $entity, $data, 'edit');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +73,8 @@ class Table
|
||||
|
||||
protected $forbiddenFieldsCache = array();
|
||||
|
||||
protected $isStrictModeForced = false;
|
||||
|
||||
protected $isStrictMode = false;
|
||||
|
||||
public function __construct(User $user, Config $config = null, FileManager $fileManager = null, Metadata $metadata = null, FieldManagerUtil $fieldManager = null)
|
||||
@@ -83,7 +85,11 @@ class Table
|
||||
'fieldTableQuickAccess' => (object) [],
|
||||
];
|
||||
|
||||
$this->isStrictMode = $config->get('aclStrictMode', false);
|
||||
if ($this->isStrictModeForced) {
|
||||
$this->isStrictMode = true;
|
||||
} else {
|
||||
$this->isStrictMode = $config->get('aclStrictMode', false);
|
||||
}
|
||||
|
||||
$this->user = $user;
|
||||
|
||||
@@ -405,7 +411,12 @@ class Table
|
||||
return;
|
||||
}
|
||||
|
||||
$data = $this->metadata->get('app.'.$this->type.'.default.scopeLevel', array());
|
||||
$defaultsGroupName = 'default';
|
||||
if ($this->isStrictMode) {
|
||||
$defaultsGroupName = 'strictDefault';
|
||||
}
|
||||
|
||||
$data = $this->metadata->get(['app', $this->type, $defaultsGroupName, 'scopeLevel'], []);
|
||||
|
||||
foreach ($data as $scope => $item) {
|
||||
if (isset($table->$scope)) continue;
|
||||
@@ -416,7 +427,7 @@ class Table
|
||||
$table->$scope = $value;
|
||||
}
|
||||
|
||||
$defaultFieldData = $this->metadata->get('app.'.$this->type.'.default.fieldLevel', array());
|
||||
$defaultFieldData = $this->metadata->get(['app', $this->type, $defaultsGroupName, 'fieldLevel'], []);
|
||||
|
||||
foreach ($this->getScopeList() as $scope) {
|
||||
if (isset($table->$scope) && $table->$scope === false) continue;
|
||||
@@ -424,7 +435,7 @@ class Table
|
||||
|
||||
$fieldList = array_keys($this->getMetadata()->get("entityDefs.{$scope}.fields", []));
|
||||
|
||||
$defaultScopeFieldData = $this->metadata->get('app.'.$this->type.'.default.scopeFieldLevel.' . $scope, array());
|
||||
$defaultScopeFieldData = $this->metadata->get('app.'.$this->type.'.'.$defaultsGroupName.'.scopeFieldLevel.' . $scope, []);
|
||||
|
||||
foreach (array_merge($defaultFieldData, $defaultScopeFieldData) as $field => $f) {
|
||||
if (!in_array($field, $fieldList)) continue;
|
||||
|
||||
@@ -50,6 +50,8 @@ class Table extends \Espo\Core\Acl\Table
|
||||
|
||||
protected $levelList = ['yes', 'all', 'account', 'contact', 'own', 'no'];
|
||||
|
||||
protected $isStrictModeForced = true;
|
||||
|
||||
public function __construct(User $user, Portal $portal, Config $config = null, FileManager $fileManager = null, Metadata $metadata = null, FieldManagerUtil $fieldManager = null)
|
||||
{
|
||||
if (empty($portal)) {
|
||||
|
||||
@@ -116,9 +116,10 @@ class CronManager
|
||||
{
|
||||
$lastRunData = $this->getFileManager()->getPhpContents($this->lastRunTime);
|
||||
|
||||
$lastRunTime = time() - intval($this->getConfig()->get('cron.minExecutionTime')) - 1;
|
||||
if (is_array($lastRunData) && !empty($lastRunData['time'])) {
|
||||
$lastRunTime = $lastRunData['time'];
|
||||
} else {
|
||||
$lastRunTime = time() - intval($this->getConfig()->get('cronMinInterval', 0)) - 1;
|
||||
}
|
||||
|
||||
return $lastRunTime;
|
||||
@@ -136,9 +137,9 @@ class CronManager
|
||||
{
|
||||
$currentTime = time();
|
||||
$lastRunTime = $this->getLastRunTime();
|
||||
$minTime = $this->getConfig()->get('cron.minExecutionTime');
|
||||
$cronMinInterval = $this->getConfig()->get('cronMinInterval', 0);
|
||||
|
||||
if ($currentTime > ($lastRunTime + $minTime) ) {
|
||||
if ($currentTime > ($lastRunTime + $cronMinInterval)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -167,8 +168,27 @@ class CronManager
|
||||
$pendingJobList = $this->getCronJobUtil()->getPendingJobList();
|
||||
|
||||
foreach ($pendingJobList as $job) {
|
||||
$skip = false;
|
||||
$this->getEntityManager()->getPdo()->query('LOCK TABLES `job` WRITE');
|
||||
if ($this->getCronJobUtil()->isJobPending($job->id)) {
|
||||
if ($job->get('scheduledJobId')) {
|
||||
if ($this->getCronJobUtil()->isScheduledJobRunning($job->get('scheduledJobId'), $job->get('targetId'), $job->get('targetType'))) {
|
||||
$skip = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$skip = true;
|
||||
}
|
||||
|
||||
if ($skip) {
|
||||
$this->getEntityManager()->getPdo()->query('UNLOCK TABLES');
|
||||
continue;
|
||||
}
|
||||
|
||||
$job->set('status', self::RUNNING);
|
||||
$job->set('pid', $this->getCronJobUtil()->getPid());
|
||||
$this->getEntityManager()->saveEntity($job);
|
||||
$this->getEntityManager()->getPdo()->query('UNLOCK TABLES');
|
||||
|
||||
$isSuccess = true;
|
||||
$skipLog = false;
|
||||
|
||||
@@ -143,13 +143,17 @@ class DataManager
|
||||
if ($job) {
|
||||
$entityManager->removeEntity($job);
|
||||
}
|
||||
$name = $jobName;
|
||||
if (!empty($defs['name'])) {
|
||||
$name = $defs['name'];
|
||||
}
|
||||
$job = $entityManager->getEntity('ScheduledJob');
|
||||
$job->set(array(
|
||||
'job' => $jobName,
|
||||
'status' => 'Active',
|
||||
'scheduling' => $defs['scheduling'],
|
||||
'isInternal' => true,
|
||||
'name' => $jobName
|
||||
'name' => $name
|
||||
));
|
||||
$entityManager->saveEntity($job);
|
||||
}
|
||||
|
||||
@@ -31,19 +31,35 @@ namespace Espo\Core\Export;
|
||||
|
||||
use \Espo\Core\Exceptions\Error;
|
||||
|
||||
use \Espo\Core\ORM\Entity;
|
||||
|
||||
class Csv extends \Espo\Core\Injectable
|
||||
{
|
||||
protected $dependencyList = [
|
||||
'config',
|
||||
'preferences'
|
||||
'preferences',
|
||||
'metadata'
|
||||
];
|
||||
|
||||
public function loadAdditionalFields(Entity $entity, $fieldList)
|
||||
{
|
||||
foreach ($fieldList as $field) {
|
||||
if ($this->getInjection('metadata')->get(['entityDefs', $entity->getEntityType(), 'fields', $field, 'type']) === 'linkMultiple') {
|
||||
if (!$entity->has($field . 'Ids')) {
|
||||
$entity->loadLinkMultipleField($field);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function process($entityType, $params, $dataList)
|
||||
{
|
||||
if (!is_array($params['attributeList'])) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
$dataList = $this->prepareDataList($dataList);
|
||||
|
||||
$attributeList = $params['attributeList'];
|
||||
|
||||
$delimiter = $this->getInjection('preferences')->get('exportDelimiter');
|
||||
@@ -62,4 +78,21 @@ class Csv extends \Espo\Core\Injectable
|
||||
|
||||
return $csv;
|
||||
}
|
||||
|
||||
protected function prepareDataList($dataList)
|
||||
{
|
||||
$prepareDataList = [];
|
||||
foreach ($dataList as $row) {
|
||||
$preparedRow = [];
|
||||
foreach ($row as $item) {
|
||||
if (is_array($item) || is_object($item)) {
|
||||
$item = \Espo\Core\Utils\Json::encode($item);
|
||||
}
|
||||
$preparedRow[] = $item;
|
||||
}
|
||||
$prepareDataList[] = $preparedRow;
|
||||
}
|
||||
|
||||
return $prepareDataList;
|
||||
}
|
||||
}
|
||||
@@ -78,6 +78,13 @@ class Xlsx extends \Espo\Core\Injectable
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($fieldList as $field) {
|
||||
if ($this->getMetadata()->get(['entityDefs', $entity->getEntityType(), 'fields', $field, 'type']) === 'linkMultiple') {
|
||||
if (!$entity->has($field . 'Ids')) {
|
||||
$entity->loadLinkMultipleField($field);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function addAdditionalAttributes($entityType, &$attributeList, $fieldList)
|
||||
@@ -367,6 +374,52 @@ class Xlsx extends \Espo\Core\Injectable
|
||||
}
|
||||
$sheet->setCellValue("$col$rowNumber", $value);
|
||||
}
|
||||
} else if ($type == 'linkMultiple') {
|
||||
if (array_key_exists($name . 'Ids', $row) && array_key_exists($name . 'Names', $row)) {
|
||||
$nameList = [];
|
||||
foreach ($row[$name . 'Ids'] as $relatedId) {
|
||||
$relatedName = $relatedId;
|
||||
if (property_exists($row[$name . 'Names'], $relatedId)) {
|
||||
$relatedName = $row[$name . 'Names']->$relatedId;
|
||||
}
|
||||
$nameList[] = $relatedName;
|
||||
}
|
||||
$sheet->setCellValue("$col$rowNumber", implode(', ', $nameList));
|
||||
}
|
||||
} else if ($type == 'address') {
|
||||
$value = '';
|
||||
if (!empty($row[$name . 'Street'])) {
|
||||
$value = $value .= $row[$name.'Street'];
|
||||
}
|
||||
if (!empty($row[$name.'City']) || !empty($row[$name.'State']) || !empty($row[$name.'PostalCode'])) {
|
||||
if ($value) {
|
||||
$value .= "\n";
|
||||
}
|
||||
if (!empty($row[$name.'City'])) {
|
||||
$value .= $row[$name.'City'];
|
||||
if (
|
||||
!empty($row[$name.'State']) || !empty($row[$name.'PostalCode'])
|
||||
) {
|
||||
$value .= ', ';
|
||||
}
|
||||
}
|
||||
if (!empty($row[$name.'State'])) {
|
||||
$value .= $row[$name.'State'];
|
||||
if (!empty($row[$name.'PostalCode'])) {
|
||||
$value .= ' ';
|
||||
}
|
||||
}
|
||||
if (!empty($row[$name.'PostalCode'])) {
|
||||
$value .= $row[$name.'PostalCode'];
|
||||
}
|
||||
}
|
||||
if (!empty($row[$name.'Country'])) {
|
||||
if ($value) {
|
||||
$value .= "\n";
|
||||
}
|
||||
$value .= $row[$name.'Country'];
|
||||
}
|
||||
$sheet->setCellValue("$col$rowNumber", $value);
|
||||
} else {
|
||||
if (array_key_exists($name, $row)) {
|
||||
$sheet->setCellValue("$col$rowNumber", $row[$name]);
|
||||
|
||||
@@ -154,31 +154,8 @@ class Importer
|
||||
}
|
||||
|
||||
if ($duplicate = $this->findDuplicate($email)) {
|
||||
if ($assignedUserId) {
|
||||
$duplicate->addLinkMultipleId('users', $assignedUserId);
|
||||
$duplicate->addLinkMultipleId('assignedUsers', $assignedUserId);
|
||||
}
|
||||
if (!empty($userIdList)) {
|
||||
foreach ($userIdList as $uId) {
|
||||
$duplicate->addLinkMultipleId('users', $uId);
|
||||
}
|
||||
}
|
||||
|
||||
if ($folderData) {
|
||||
foreach ($folderData as $uId => $folderId) {
|
||||
$email->setLinkMultipleColumn('users', 'folderId', $uId, $folderId);
|
||||
}
|
||||
}
|
||||
|
||||
$duplicate->set('isBeingImported', true);
|
||||
|
||||
$this->getEntityManager()->saveEntity($duplicate);
|
||||
|
||||
if (!empty($teamsIdList)) {
|
||||
foreach ($teamsIdList as $teamId) {
|
||||
$this->getEntityManager()->getRepository('Email')->relate($duplicate, 'teams', $teamId);
|
||||
}
|
||||
}
|
||||
$duplicate = $this->getEntityManager()->getEntity('Email', $duplicate->id);
|
||||
$this->processDuplicate($duplicate, $assignedUserId, $userIdList, $folderData, $teamsIdList);
|
||||
return $duplicate;
|
||||
}
|
||||
|
||||
@@ -299,6 +276,26 @@ class Importer
|
||||
}
|
||||
}
|
||||
|
||||
$this->getEntityManager()->getPdo()->query('LOCK TABLES `email` WRITE');
|
||||
|
||||
if ($duplicate = $this->findDuplicate($email)) {
|
||||
$this->getEntityManager()->getPdo()->query('UNLOCK TABLES');
|
||||
$duplicate = $this->getEntityManager()->getEntity('Email', $duplicate->id);
|
||||
$this->processDuplicate($duplicate, $assignedUserId, $userIdList, $folderData, $teamsIdList);
|
||||
return $duplicate;
|
||||
}
|
||||
|
||||
if (!$email->get('messageId')) {
|
||||
$email->setDummyMessageId();
|
||||
}
|
||||
|
||||
$this->getEntityManager()->saveEntity($email, [
|
||||
'skipAll' => true,
|
||||
'keepNew' => true
|
||||
]);
|
||||
|
||||
$this->getEntityManager()->getPdo()->query('UNLOCK TABLES');
|
||||
|
||||
$this->getEntityManager()->saveEntity($email);
|
||||
|
||||
foreach ($inlineAttachmentList as $attachment) {
|
||||
@@ -352,7 +349,7 @@ class Importer
|
||||
protected function findDuplicate(Entity $email)
|
||||
{
|
||||
if ($email->get('messageId')) {
|
||||
$duplicate = $this->getEntityManager()->getRepository('Email')->where(array(
|
||||
$duplicate = $this->getEntityManager()->getRepository('Email')->select(['id'])->where(array(
|
||||
'messageId' => $email->get('messageId')
|
||||
))->findOne();
|
||||
if ($duplicate) {
|
||||
@@ -360,4 +357,33 @@ class Importer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function processDuplicate(Entity $duplicate, $assignedUserId, $userIdList, $folderData, $teamsIdList)
|
||||
{
|
||||
if ($assignedUserId) {
|
||||
$duplicate->addLinkMultipleId('users', $assignedUserId);
|
||||
$duplicate->addLinkMultipleId('assignedUsers', $assignedUserId);
|
||||
}
|
||||
if (!empty($userIdList)) {
|
||||
foreach ($userIdList as $uId) {
|
||||
$duplicate->addLinkMultipleId('users', $uId);
|
||||
}
|
||||
}
|
||||
|
||||
if ($folderData) {
|
||||
foreach ($folderData as $uId => $folderId) {
|
||||
$duplicate->setLinkMultipleColumn('users', 'folderId', $uId, $folderId);
|
||||
}
|
||||
}
|
||||
|
||||
$duplicate->set('isBeingImported', true);
|
||||
|
||||
$this->getEntityManager()->saveEntity($duplicate);
|
||||
|
||||
if (!empty($teamsIdList)) {
|
||||
foreach ($teamsIdList as $teamId) {
|
||||
$this->getEntityManager()->getRepository('Email')->relate($duplicate, 'teams', $teamId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ use \PhpMimeMailParser\Attachment;
|
||||
|
||||
class Parser extends \PhpMimeMailParser\Parser
|
||||
{
|
||||
public function getAttachments()
|
||||
public function getAttachments($include_inline = true)
|
||||
{
|
||||
$attachments = [];
|
||||
$dispositions = ['attachment', 'inline'];
|
||||
|
||||
@@ -349,7 +349,7 @@ class Sender
|
||||
|
||||
try {
|
||||
$messageId = $email->get('messageId');
|
||||
if (empty($messageId) || !is_string($messageId) || strlen($messageId) < 4) {
|
||||
if (empty($messageId) || !is_string($messageId) || strlen($messageId) < 4 || strpos($messageId, 'dummy:') === 0) {
|
||||
$messageId = $this->generateMessageId($email);
|
||||
$email->set('messageId', '<' . $messageId . '>');
|
||||
} else {
|
||||
|
||||
@@ -41,6 +41,24 @@ class Entity extends \Espo\ORM\Entity
|
||||
return $this->hasAttribute($field . 'Id');
|
||||
}
|
||||
|
||||
public function loadParentNameField($field)
|
||||
{
|
||||
if (!$this->hasAttribute($field. 'Id') || !$this->hasAttribute($field . 'Type')) return;
|
||||
|
||||
$parentId = $this->get($field . 'Id');
|
||||
$parentType = $this->get($field . 'Type');
|
||||
|
||||
if ($parentId && $parentType) {
|
||||
if (!$this->entityManager->hasRepository($parentType)) return;
|
||||
$repository = $this->entityManager->getRepository($parentType);
|
||||
|
||||
$foreignEntity = $repository->select(['id', 'name'])->where(['id' => $parentId])->findOne();
|
||||
if ($foreignEntity) {
|
||||
$this->set($field . 'Name', $foreignEntity->get('name'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function loadLinkMultipleField($field, $columns = null)
|
||||
{
|
||||
if (!$this->hasRelation($field) || !$this->hasAttribute($field . 'Ids')) return;
|
||||
@@ -69,12 +87,20 @@ class Entity extends \Espo\ORM\Entity
|
||||
}
|
||||
}
|
||||
|
||||
$defs['select'] = ['id', 'name'];
|
||||
|
||||
$hasType = false;
|
||||
if ($this->hasField($field . 'Types')) {
|
||||
$hasType = true;
|
||||
$defs['select'][] = 'type';
|
||||
}
|
||||
|
||||
$collection = $this->get($field, $defs);
|
||||
$ids = array();
|
||||
$names = new \stdClass();
|
||||
$types = new \stdClass();
|
||||
$ids = [];
|
||||
$names = (object) [];
|
||||
$types = (object) [];
|
||||
if (!empty($columns)) {
|
||||
$columnsData = new \stdClass();
|
||||
$columnsData = (object) [];
|
||||
}
|
||||
|
||||
if ($collection) {
|
||||
@@ -82,7 +108,9 @@ class Entity extends \Espo\ORM\Entity
|
||||
$id = $e->id;
|
||||
$ids[] = $id;
|
||||
$names->$id = $e->get('name');
|
||||
$types->$id = $e->get('type');
|
||||
if ($hasType) {
|
||||
$types->$id = $e->get('type');
|
||||
}
|
||||
if (!empty($columns)) {
|
||||
$columnsData->$id = new \stdClass();
|
||||
foreach ($columns as $column => $f) {
|
||||
@@ -94,7 +122,9 @@ class Entity extends \Espo\ORM\Entity
|
||||
|
||||
$this->set($field . 'Ids', $ids);
|
||||
$this->set($field . 'Names', $names);
|
||||
$this->set($field . 'Types', $types);
|
||||
if ($hasType) {
|
||||
$this->set($field . 'Types', $types);
|
||||
}
|
||||
if (!empty($columns)) {
|
||||
$this->set($field . 'Columns', $columnsData);
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
|
||||
protected function beforeRemove(Entity $entity, array $options = array())
|
||||
{
|
||||
parent::beforeRemove($entity, $options);
|
||||
if (!$this->hooksDisabled) {
|
||||
if (!$this->hooksDisabled && empty($options['skipHooks'])) {
|
||||
$this->getEntityManager()->getHookManager()->process($this->entityType, 'beforeRemove', $entity, $options);
|
||||
}
|
||||
|
||||
@@ -192,14 +192,14 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
|
||||
protected function afterRemove(Entity $entity, array $options = array())
|
||||
{
|
||||
parent::afterRemove($entity, $options);
|
||||
if (!$this->hooksDisabled) {
|
||||
if (!$this->hooksDisabled && empty($options['skipHooks'])) {
|
||||
$this->getEntityManager()->getHookManager()->process($this->entityType, 'afterRemove', $entity, $options);
|
||||
}
|
||||
}
|
||||
|
||||
protected function afterMassRelate(Entity $entity, $relationName, array $params = array(), array $options = array())
|
||||
{
|
||||
if (!$this->hooksDisabled) {
|
||||
if (!$this->hooksDisabled && empty($options['skipHooks'])) {
|
||||
$hookData = array(
|
||||
'relationName' => $relationName,
|
||||
'relationParams' => $params
|
||||
@@ -220,7 +220,7 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
|
||||
|
||||
if ($foreign instanceof Entity) {
|
||||
$foreignEntity = $foreign;
|
||||
if (!$this->hooksDisabled) {
|
||||
if (!$this->hooksDisabled && empty($options['skipHooks'])) {
|
||||
$hookData = array(
|
||||
'relationName' => $relationName,
|
||||
'relationData' => $data,
|
||||
@@ -237,7 +237,7 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
|
||||
|
||||
if ($foreign instanceof Entity) {
|
||||
$foreignEntity = $foreign;
|
||||
if (!$this->hooksDisabled) {
|
||||
if (!$this->hooksDisabled && empty($options['skipHooks'])) {
|
||||
$hookData = array(
|
||||
'relationName' => $relationName,
|
||||
'foreignEntity' => $foreignEntity
|
||||
@@ -251,7 +251,7 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
|
||||
{
|
||||
parent::beforeSave($entity, $options);
|
||||
|
||||
if (!$this->hooksDisabled) {
|
||||
if (!$this->hooksDisabled && empty($options['skipHooks'])) {
|
||||
$this->getEntityManager()->getHookManager()->process($this->entityType, 'beforeSave', $entity, $options);
|
||||
}
|
||||
}
|
||||
@@ -271,7 +271,7 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
|
||||
$this->processFileFieldsSave($entity);
|
||||
}
|
||||
|
||||
if (!$this->hooksDisabled) {
|
||||
if (!$this->hooksDisabled && empty($options['skipHooks'])) {
|
||||
$this->getEntityManager()->getHookManager()->process($this->entityType, 'afterSave', $entity, $options);
|
||||
}
|
||||
}
|
||||
@@ -285,35 +285,40 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
|
||||
if (!$entity->has('id')) {
|
||||
$entity->set('id', Util::generateId());
|
||||
}
|
||||
}
|
||||
|
||||
if ($entity->hasAttribute('createdAt')) {
|
||||
if (empty($options['import']) || !$entity->has('createdAt')) {
|
||||
$entity->set('createdAt', $nowString);
|
||||
}
|
||||
}
|
||||
if ($entity->hasAttribute('modifiedAt')) {
|
||||
$entity->set('modifiedAt', $nowString);
|
||||
}
|
||||
if ($entity->hasAttribute('createdById')) {
|
||||
if (empty($options['skipCreatedBy']) && (empty($options['import']) || !$entity->has('createdById'))) {
|
||||
if ($this->getEntityManager()->getUser()) {
|
||||
$entity->set('createdById', $this->getEntityManager()->getUser()->id);
|
||||
if (empty($options['skipAll'])) {
|
||||
if ($entity->isNew()) {
|
||||
if ($entity->hasAttribute('createdAt')) {
|
||||
if (empty($options['import']) || !$entity->has('createdAt')) {
|
||||
$entity->set('createdAt', $nowString);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (empty($options['silent']) && empty($options['skipModifiedBy'])) {
|
||||
if ($entity->hasAttribute('modifiedAt')) {
|
||||
$entity->set('modifiedAt', $nowString);
|
||||
}
|
||||
if ($entity->hasAttribute('modifiedById')) {
|
||||
if ($this->getEntityManager()->getUser()) {
|
||||
$entity->set('modifiedById', $this->getEntityManager()->getUser()->id);
|
||||
$entity->set('modifiedByName', $this->getEntityManager()->getUser()->get('name'));
|
||||
if ($entity->hasAttribute('createdById')) {
|
||||
if (empty($options['skipCreatedBy']) && (empty($options['import']) || !$entity->has('createdById'))) {
|
||||
if ($this->getEntityManager()->getUser()) {
|
||||
$entity->set('createdById', $this->getEntityManager()->getUser()->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (empty($options['silent']) && empty($options['skipModifiedBy'])) {
|
||||
if ($entity->hasAttribute('modifiedAt')) {
|
||||
$entity->set('modifiedAt', $nowString);
|
||||
}
|
||||
if ($entity->hasAttribute('modifiedById')) {
|
||||
if ($this->getEntityManager()->getUser()) {
|
||||
$entity->set('modifiedById', $this->getEntityManager()->getUser()->id);
|
||||
$entity->set('modifiedByName', $this->getEntityManager()->getUser()->get('name'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->restoreData = $restoreData;
|
||||
|
||||
$result = parent::save($entity, $options);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"links": {
|
||||
"meetings": "Susirinkimai",
|
||||
"meetings": "Susitikimai",
|
||||
"calls": "Skambučiai",
|
||||
"tasks": "Užduotys"
|
||||
},
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
"fields": {
|
||||
"billingAddress": "Mokėtojo adresas",
|
||||
"shippingAddress": "Siuntimo adresas",
|
||||
"website": "Interntinė Svetainė"
|
||||
"website": "Internetinė Svetainė"
|
||||
},
|
||||
"links": {
|
||||
"meetings": "Susirinkimai",
|
||||
"meetings": "Susitikimai",
|
||||
"calls": "Skambučiai",
|
||||
"tasks": "Užduotys"
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"address": "Adresas"
|
||||
},
|
||||
"links": {
|
||||
"meetings": "Susirinkimai",
|
||||
"meetings": "Susitikimai",
|
||||
"calls": "Skambučiai",
|
||||
"tasks": "Užduotys"
|
||||
},
|
||||
|
||||
@@ -186,11 +186,10 @@ class AdminNotificationManager
|
||||
$notification->set(array(
|
||||
'type' => 'message',
|
||||
'data' => array(
|
||||
'userId' => $this->getUser()->id,
|
||||
'userName' => $this->getUser()->get('name')
|
||||
'userId' => $userId,
|
||||
),
|
||||
'userId' => $user->id,
|
||||
'message' => $actionData['messageTemplate']
|
||||
'userId' => $userId,
|
||||
'message' => $message
|
||||
));
|
||||
$this->getEntityManager()->saveEntity($notification);
|
||||
}
|
||||
|
||||
@@ -28,10 +28,12 @@
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Cron;
|
||||
use \PDO;
|
||||
use \Espo\Core\CronManager;
|
||||
use \Espo\Core\Utils\Config;
|
||||
use \Espo\Core\ORM\EntityManager;
|
||||
|
||||
use PDO;
|
||||
use Espo\Core\CronManager;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\ORM\EntityManager;
|
||||
use Espo\Core\Utils\System;
|
||||
|
||||
class Job
|
||||
{
|
||||
@@ -64,15 +66,17 @@ class Job
|
||||
return $this->cronScheduledJob;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Pending Jobs
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function isJobPending($id)
|
||||
{
|
||||
return !!$this->getEntityManager()->getRepository('Job')->select(['id'])->where([
|
||||
'id' => $id,
|
||||
'status' => CronManager::PENDING
|
||||
])->findOne();
|
||||
}
|
||||
|
||||
public function getPendingJobList()
|
||||
{
|
||||
$jobConfigs = $this->getConfig()->get('cron');
|
||||
$limit = !empty($jobConfigs['maxJobNumber']) ? intval($jobConfigs['maxJobNumber']) : 0;
|
||||
$limit = intval($this->getConfig()->get('jobMaxPortion', 0));
|
||||
|
||||
$selectParams = [
|
||||
'select' => [
|
||||
@@ -88,7 +92,7 @@ class Job
|
||||
'data'
|
||||
],
|
||||
'whereClause' => [
|
||||
'status' => 'Pending',
|
||||
'status' => CronManager::PENDING,
|
||||
'executeTime<=' => date('Y-m-d H:i:s')
|
||||
],
|
||||
'orderBy' => 'executeTime'
|
||||
@@ -97,17 +101,21 @@ class Job
|
||||
$selectParams['offset'] = 0;
|
||||
$selectParams['limit'] = $limit;
|
||||
}
|
||||
$jobList = $this->getEntityManager()->getRepository('Job')->find($selectParams);
|
||||
|
||||
$runningScheduledJobIdList = $this->getRunningScheduledJobIdList();
|
||||
return $this->getEntityManager()->getRepository('Job')->find($selectParams);
|
||||
}
|
||||
|
||||
$actualJobList = [];
|
||||
foreach ($jobList as $job) {
|
||||
if ($job->get('scheduledJobId') && in_array($job->get('scheduledJobId'), $runningScheduledJobIdList)) continue;
|
||||
$actualJobList[] = $job;
|
||||
public function isScheduledJobRunning($scheduledJobId, $targetId = null, $targetType = null)
|
||||
{
|
||||
$where = [
|
||||
'scheduledJobId' => $scheduledJobId,
|
||||
'status' => CronManager::RUNNING
|
||||
];
|
||||
if ($targetId && $targetType) {
|
||||
$where['targetId'] = $targetId;
|
||||
$where['targetType'] = $targetType;
|
||||
}
|
||||
|
||||
return $actualJobList;
|
||||
return !!$this->getEntityManager()->getRepository('Job')->select(['id'])->where($where)->findOne();
|
||||
}
|
||||
|
||||
public function getRunningScheduledJobIdList()
|
||||
@@ -175,39 +183,62 @@ class Job
|
||||
*/
|
||||
public function markFailedJobs()
|
||||
{
|
||||
$jobConfigs = $this->getConfig()->get('cron');
|
||||
$this->markFailedJobsByPeriod('jobPeriodForActiveProcess');
|
||||
$this->markFailedJobsByPeriod('jobPeriod');
|
||||
}
|
||||
|
||||
$currentTime = time();
|
||||
$periodTime = $currentTime - intval($jobConfigs['jobPeriod']);
|
||||
protected function markFailedJobsByPeriod($period)
|
||||
{
|
||||
$time = time() - $this->getConfig()->get($period);
|
||||
|
||||
$pdo = $this->getEntityManager()->getPDO();
|
||||
|
||||
$select = "
|
||||
SELECT id, scheduled_job_id, execute_time, target_id, target_type FROM `job`
|
||||
SELECT id, scheduled_job_id, execute_time, target_id, target_type, pid FROM `job`
|
||||
WHERE
|
||||
`status` = '" . CronManager::RUNNING ."' AND execute_time < '".date('Y-m-d H:i:s', $periodTime)."'
|
||||
`status` = '" . CronManager::RUNNING ."' AND execute_time < '".date('Y-m-d H:i:s', $time)."'
|
||||
";
|
||||
$sth = $pdo->prepare($select);
|
||||
$sth->execute();
|
||||
|
||||
$jobData = array();
|
||||
while ($row = $sth->fetch(PDO::FETCH_ASSOC)){
|
||||
$jobData[$row['id']] = $row;
|
||||
|
||||
switch ($period) {
|
||||
case 'jobPeriod':
|
||||
while ($row = $sth->fetch(PDO::FETCH_ASSOC)) {
|
||||
if (empty($row['pid']) || !System::isProcessActive($row['pid'])) {
|
||||
$jobData[$row['id']] = $row;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'jobPeriodForActiveProcess':
|
||||
while ($row = $sth->fetch(PDO::FETCH_ASSOC)) {
|
||||
$jobData[$row['id']] = $row;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$update = "
|
||||
UPDATE job
|
||||
SET `status` = '". CronManager::FAILED ."'
|
||||
WHERE id IN ('".implode("', '", array_keys($jobData))."')
|
||||
";
|
||||
$sth = $pdo->prepare($update);
|
||||
$sth->execute();
|
||||
if (!empty($jobData)) {
|
||||
$jobQuotedIdList = [];
|
||||
foreach ($jobData as $jobId => $job) {
|
||||
$jobQuotedIdList[] = $pdo->quote($jobId);
|
||||
}
|
||||
|
||||
//add status 'Failed' to SchediledJobLog
|
||||
$cronScheduledJob = $this->getCronScheduledJob();
|
||||
foreach ($jobData as $jobId => $job) {
|
||||
if (!empty($job['scheduled_job_id'])) {
|
||||
$cronScheduledJob->addLogRecord($job['scheduled_job_id'], CronManager::FAILED, $job['execute_time'], $job['target_id'], $job['target_type']);
|
||||
$update = "
|
||||
UPDATE job
|
||||
SET `status` = '" . CronManager::FAILED . "', attempts = 0
|
||||
WHERE id IN (".implode(", ", $jobQuotedIdList).")
|
||||
";
|
||||
|
||||
$sth = $pdo->prepare($update);
|
||||
$sth->execute();
|
||||
|
||||
$cronScheduledJob = $this->getCronScheduledJob();
|
||||
foreach ($jobData as $jobId => $job) {
|
||||
if (!empty($job['scheduled_job_id'])) {
|
||||
$cronScheduledJob->addLogRecord($job['scheduled_job_id'], CronManager::FAILED, $job['execute_time'], $job['target_id'], $job['target_type']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -246,20 +277,29 @@ class Job
|
||||
$query = "
|
||||
SELECT id FROM `job`
|
||||
WHERE
|
||||
scheduled_job_id = '" . $row['scheduled_job_id'] . "' AND
|
||||
`status` = '" . CronManager::PENDING ."'
|
||||
scheduled_job_id = ".$pdo->quote($row['scheduled_job_id'])."
|
||||
AND `status` = '" . CronManager::PENDING ."'
|
||||
ORDER BY execute_time
|
||||
DESC LIMIT 1, 100000
|
||||
";
|
||||
$sth = $pdo->prepare($query);
|
||||
$sth->execute();
|
||||
$jobIds = $sth->fetchAll(PDO::FETCH_COLUMN);
|
||||
$jobIdList = $sth->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
if (empty($jobIdList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$quotedJobIdList = [];
|
||||
foreach ($jobIdList as $jobId) {
|
||||
$quotedJobIdList[] = $pdo->quote($jobId);
|
||||
}
|
||||
|
||||
$update = "
|
||||
UPDATE job
|
||||
SET deleted = 1
|
||||
WHERE
|
||||
id IN ('". implode("', '", $jobIds)."')
|
||||
id IN (".implode(", ", $quotedJobIdList).")
|
||||
";
|
||||
|
||||
$sth = $pdo->prepare($update);
|
||||
@@ -300,13 +340,18 @@ class Job
|
||||
UPDATE job
|
||||
SET
|
||||
`status` = '" . CronManager::PENDING ."',
|
||||
attempts = '".$attempts."',
|
||||
failed_attempts = '".$failedAttempts."'
|
||||
attempts = ".$pdo->quote($attempts).",
|
||||
failed_attempts = ".$pdo->quote($failedAttempts)."
|
||||
WHERE
|
||||
id = '".$row['id']."'
|
||||
id = ".$pdo->quote($row['id'])."
|
||||
";
|
||||
$pdo->prepare($update)->execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getPid()
|
||||
{
|
||||
return System::getPid();
|
||||
}
|
||||
}
|
||||
@@ -91,7 +91,7 @@ class ScheduledJob
|
||||
}
|
||||
|
||||
$scheduledJob->set('lastRun', $runTime);
|
||||
$entityManager->saveEntity($scheduledJob);
|
||||
$entityManager->saveEntity($scheduledJob, ['silent' => true]);
|
||||
|
||||
$scheduledJobLog = $entityManager->getEntity('ScheduledJobLogRecord');
|
||||
$scheduledJobLog->set(array(
|
||||
|
||||
@@ -134,4 +134,40 @@ class System
|
||||
|
||||
return $version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pet process ID
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public static function getPid()
|
||||
{
|
||||
if (function_exists('getmypid')) {
|
||||
return getmypid();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if process is active
|
||||
*
|
||||
* @param integer $pid
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isProcessActive($pid)
|
||||
{
|
||||
if (empty($pid)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!function_exists('posix_getsid')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (posix_getsid($pid) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -167,8 +167,8 @@ return array (
|
||||
'cleanupJobPeriod' => '1 month',
|
||||
'cleanupActionHistoryPeriod' => '15 days',
|
||||
'cleanupAuthTokenPeriod' => '1 month',
|
||||
'currencyFormat' => 1,
|
||||
'currencyDecimalPlaces' => null,
|
||||
'currencyFormat' => 2,
|
||||
'currencyDecimalPlaces' => 2,
|
||||
'aclStrictMode' => false,
|
||||
'aclAllowDeleteCreated' => false,
|
||||
'inlineAttachmentUploadMaxSize' => 20,
|
||||
|
||||
@@ -56,16 +56,11 @@ return array ( 'defaultPermissions' =>
|
||||
'reset.html',
|
||||
),
|
||||
),
|
||||
'cron' => array(
|
||||
/** Max number of jobs per one execution. */
|
||||
'maxJobNumber' => 15,
|
||||
/** Max execution time (in seconds) allocated for a sinle job. If exceeded then set to Failed.*/
|
||||
'jobPeriod' => 7800,
|
||||
/** Min time (in seconds) between two cron runs. */
|
||||
'minExecutionTime' => 50,
|
||||
/** Attempts to re-run failed jobs. */
|
||||
'attempts' => 2
|
||||
),
|
||||
'jobMaxPortion' => 15, /** Max number of jobs per one execution. */
|
||||
'jobPeriod' => 7800, /** Max execution time (in seconds) allocated for a sinle job. If exceeded then set to Failed.*/
|
||||
'jobPeriodForActiveProcess' => 36000, /** Max execution time (in seconds) allocated for a sinle job with active process. If exceeded then set to Failed.*/
|
||||
'jobRerunAttemptNumber' => 1, /** Number of attempts to re-run failed jobs. */
|
||||
'cronMinInterval' => 4, /** Min interval (in seconds) between two cron runs. */
|
||||
'crud' => array(
|
||||
'get' => 'read',
|
||||
'post' => 'create',
|
||||
@@ -117,7 +112,10 @@ return array ( 'defaultPermissions' =>
|
||||
'smtpSecurity',
|
||||
'smtpUsername',
|
||||
'smtpPassword',
|
||||
'cron',
|
||||
'jobMaxPortion',
|
||||
'jobPeriod',
|
||||
'jobRerunAttemptNumber',
|
||||
'cronMinInterval',
|
||||
'authenticationMethod',
|
||||
'adminPanelIframeUrl',
|
||||
'ldapHost',
|
||||
|
||||
@@ -174,5 +174,9 @@ class Email extends \Espo\Core\ORM\Entity
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public function setDummyMessageId()
|
||||
{
|
||||
$this->set('messageId', 'dummy:' . \Espo\Core\Utils\Util::generateId());
|
||||
}
|
||||
}
|
||||
|
||||
+4
-5
@@ -31,7 +31,7 @@ namespace Espo\Jobs;
|
||||
|
||||
use Espo\Core\Exceptions;
|
||||
|
||||
class NewVersionChecker extends \Espo\Core\Jobs\Base
|
||||
class CheckNewVersion extends \Espo\Core\Jobs\Base
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
@@ -41,11 +41,10 @@ class NewVersionChecker extends \Espo\Core\Jobs\Base
|
||||
|
||||
$job = $this->getEntityManager()->getEntity('Job');
|
||||
$job->set(array(
|
||||
'name' => 'NewVersionChecker',
|
||||
'name' => 'Check for New Version (job)',
|
||||
'serviceName' => 'AdminNotifications',
|
||||
'method' => 'newVersionChecker',
|
||||
'methodName' => 'newVersionChecker',
|
||||
'executeTime' => $this->getRunTime(),
|
||||
'methodName' => 'jobCheckNewVersion',
|
||||
'executeTime' => $this->getRunTime()
|
||||
));
|
||||
|
||||
$this->getEntityManager()->saveEntity($job);
|
||||
@@ -43,8 +43,9 @@ class Opportunity extends \Espo\Core\Controllers\Record
|
||||
|
||||
$dateFrom = $request->get('dateFrom');
|
||||
$dateTo = $request->get('dateTo');
|
||||
$dateFilter = $request->get('dateFilter');
|
||||
|
||||
return $this->getService('Opportunity')->reportByLeadSource($dateFrom, $dateTo);
|
||||
return $this->getService('Opportunity')->reportByLeadSource($dateFilter, $dateFrom, $dateTo);
|
||||
}
|
||||
|
||||
public function actionReportByStage($params, $data, $request)
|
||||
@@ -56,8 +57,9 @@ class Opportunity extends \Espo\Core\Controllers\Record
|
||||
|
||||
$dateFrom = $request->get('dateFrom');
|
||||
$dateTo = $request->get('dateTo');
|
||||
$dateFilter = $request->get('dateFilter');
|
||||
|
||||
return $this->getService('Opportunity')->reportByStage($dateFrom, $dateTo);
|
||||
return $this->getService('Opportunity')->reportByStage($dateFilter, $dateFrom, $dateTo);
|
||||
}
|
||||
|
||||
public function actionReportSalesByMonth($params, $data, $request)
|
||||
@@ -69,8 +71,9 @@ class Opportunity extends \Espo\Core\Controllers\Record
|
||||
|
||||
$dateFrom = $request->get('dateFrom');
|
||||
$dateTo = $request->get('dateTo');
|
||||
$dateFilter = $request->get('dateFilter');
|
||||
|
||||
return $this->getService('Opportunity')->reportSalesByMonth($dateFrom, $dateTo);
|
||||
return $this->getService('Opportunity')->reportSalesByMonth($dateFilter, $dateFrom, $dateTo);
|
||||
}
|
||||
|
||||
public function actionReportSalesPipeline($params, $data, $request)
|
||||
@@ -82,8 +85,9 @@ class Opportunity extends \Espo\Core\Controllers\Record
|
||||
|
||||
$dateFrom = $request->get('dateFrom');
|
||||
$dateTo = $request->get('dateTo');
|
||||
$dateFilter = $request->get('dateFilter');
|
||||
|
||||
return $this->getService('Opportunity')->reportSalesPipeline($dateFrom, $dateTo);
|
||||
return $this->getService('Opportunity')->reportSalesPipeline($dateFilter, $dateFrom, $dateTo);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"fields": {
|
||||
"name": "Vardas",
|
||||
"emailAddress": "El. paštas",
|
||||
"website": "Interntinė Svetainė",
|
||||
"website": "Internetinė Svetainė",
|
||||
"phoneNumber": "Telefonas",
|
||||
"billingAddress": "Mokėtojo adresas",
|
||||
"shippingAddress": "Siuntimo adresas",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"openedCount": "Atverta",
|
||||
"clickedCount": "Paspausta",
|
||||
"optedOutCount": "Pasirinkta",
|
||||
"bouncedCount": "Atmesta",
|
||||
"leadCreatedCount": "Potencialūs klientai sukurti",
|
||||
"revenue": "Pajamos",
|
||||
"revenueConverted": "Pajamos (konvertuotos)",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"account": "Įmonė",
|
||||
"contact": "Kontaktas (pirminis)",
|
||||
"Contacts": "Kontaktai",
|
||||
"meetings": "Susirinkimai",
|
||||
"meetings": "Susitikimai",
|
||||
"calls": "Skambučiai",
|
||||
"tasks": "Užduotys",
|
||||
"emails": "El. laiška",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"contacts": "Kontaktai",
|
||||
"opportunities": "Galimybės",
|
||||
"leads": "Potencialūs klientai",
|
||||
"meetings": "Susirinkimai",
|
||||
"meetings": "Susitikimai",
|
||||
"calls": "Skambučiai",
|
||||
"tasks": "Užduotys",
|
||||
"emails": "El. laiška",
|
||||
@@ -21,7 +21,7 @@
|
||||
"Lead": "Potencialus klientas",
|
||||
"Target": "Adresatas",
|
||||
"Opportunity": "Galimybė",
|
||||
"Meeting": "Susirinkimas",
|
||||
"Meeting": "Susitikimas",
|
||||
"Calendar": "Kalendorius",
|
||||
"Call": "Skambutis",
|
||||
"Task": "Užduotis",
|
||||
@@ -42,7 +42,7 @@
|
||||
"Lead": "Potencialūs klientai",
|
||||
"Target": "Adresatai",
|
||||
"Opportunity": "Galimybės",
|
||||
"Meeting": "Susirinkimai",
|
||||
"Meeting": "Susitikimai",
|
||||
"Calendar": "Kalendorius",
|
||||
"Call": "Skambučiai",
|
||||
"Task": "Užduotys",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"name": "Vardas",
|
||||
"emailAddress": "El. paštas",
|
||||
"title": "Pavadinimas",
|
||||
"website": "Interntinė Svetainė",
|
||||
"website": "Internetinė Svetainė",
|
||||
"phoneNumber": "Telefonas",
|
||||
"accountName": "Įmonės pavadinimas",
|
||||
"doNotCall": "Neskambinti",
|
||||
|
||||
+15
-1
@@ -2,6 +2,7 @@
|
||||
"view":"crm:views/dashlets/opportunities-by-lead-source",
|
||||
"aclScope": "Opportunity",
|
||||
"options": {
|
||||
"view": "crm:views/dashlets/options/chart",
|
||||
"fields": {
|
||||
"title": {
|
||||
"type": "varchar",
|
||||
@@ -14,6 +15,12 @@
|
||||
"dateTo": {
|
||||
"type": "date",
|
||||
"required": true
|
||||
},
|
||||
"dateFilter": {
|
||||
"type": "enum",
|
||||
"options": ["currentYear", "currentQuarter", "currentMonth", "ever", "between"],
|
||||
"default": "currentYear",
|
||||
"translation": "Global.options.dateSearchRanges"
|
||||
}
|
||||
},
|
||||
"layout": [
|
||||
@@ -22,12 +29,19 @@
|
||||
[
|
||||
{"name": "title"}
|
||||
],
|
||||
[
|
||||
{"name": "dateFilter"},
|
||||
false
|
||||
],
|
||||
[
|
||||
{"name": "dateFrom"},
|
||||
{"name": "dateTo"}
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
"defaults": {
|
||||
"dateFilter": "currentYear"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"view":"crm:views/dashlets/opportunities-by-stage",
|
||||
"aclScope": "Opportunity",
|
||||
"options": {
|
||||
"view": "crm:views/dashlets/options/chart",
|
||||
"fields": {
|
||||
"title": {
|
||||
"type": "varchar",
|
||||
@@ -14,6 +15,12 @@
|
||||
"dateTo": {
|
||||
"type": "date",
|
||||
"required": true
|
||||
},
|
||||
"dateFilter": {
|
||||
"type": "enum",
|
||||
"options": ["currentYear", "currentQuarter", "currentMonth", "ever", "between"],
|
||||
"default": "currentYear",
|
||||
"translation": "Global.options.dateSearchRanges"
|
||||
}
|
||||
},
|
||||
"layout": [
|
||||
@@ -22,12 +29,19 @@
|
||||
[
|
||||
{"name": "title"}
|
||||
],
|
||||
[
|
||||
{"name": "dateFilter"},
|
||||
false
|
||||
],
|
||||
[
|
||||
{"name": "dateFrom"},
|
||||
{"name": "dateTo"}
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
"defaults": {
|
||||
"dateFilter": "currentYear"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"view":"crm:views/dashlets/sales-by-month",
|
||||
"aclScope": "Opportunity",
|
||||
"options": {
|
||||
"view": "crm:views/dashlets/options/chart",
|
||||
"fields": {
|
||||
"title": {
|
||||
"type": "varchar",
|
||||
@@ -14,6 +15,12 @@
|
||||
"dateTo": {
|
||||
"type": "date",
|
||||
"required": true
|
||||
},
|
||||
"dateFilter": {
|
||||
"type": "enum",
|
||||
"options": ["currentYear", "currentQuarter", "between"],
|
||||
"default": "currentYear",
|
||||
"translation": "Global.options.dateSearchRanges"
|
||||
}
|
||||
},
|
||||
"layout": [
|
||||
@@ -22,12 +29,19 @@
|
||||
[
|
||||
{"name": "title"}
|
||||
],
|
||||
[
|
||||
{"name": "dateFilter"},
|
||||
false
|
||||
],
|
||||
[
|
||||
{"name": "dateFrom"},
|
||||
{"name": "dateTo"}
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
"defaults": {
|
||||
"dateFilter": "currentYear"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"view":"crm:views/dashlets/sales-pipeline",
|
||||
"aclScope": "Opportunity",
|
||||
"options": {
|
||||
"view": "crm:views/dashlets/options/chart",
|
||||
"fields": {
|
||||
"title": {
|
||||
"type": "varchar",
|
||||
@@ -14,13 +15,23 @@
|
||||
"dateTo": {
|
||||
"type": "date",
|
||||
"required": true
|
||||
},
|
||||
"dateFilter": {
|
||||
"type": "enum",
|
||||
"options": ["currentYear", "currentQuarter", "currentMonth", "ever", "between"],
|
||||
"default": "currentYear",
|
||||
"translation": "Global.options.dateSearchRanges"
|
||||
}
|
||||
},
|
||||
"layout": [
|
||||
{
|
||||
"rows": [
|
||||
[
|
||||
{"name": "title"}
|
||||
{"name": "title", "span": 2}
|
||||
],
|
||||
[
|
||||
{"name": "dateFilter"},
|
||||
false
|
||||
],
|
||||
[
|
||||
{"name": "dateFrom"},
|
||||
@@ -28,6 +39,9 @@
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
"defaults": {
|
||||
"dateFilter": "currentYear"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +195,6 @@
|
||||
"layoutListDisabled": true,
|
||||
"layoutMassUpdateDisabled": true,
|
||||
"layoutFiltersDisabled": true,
|
||||
"exportDisabled": true,
|
||||
"entity": "TargetList"
|
||||
},
|
||||
"originalLead": {
|
||||
|
||||
@@ -253,7 +253,6 @@
|
||||
"layoutListDisabled": true,
|
||||
"layoutMassUpdateDisabled": true,
|
||||
"importDisabled": true,
|
||||
"exportDisabled": true,
|
||||
"noLoad": true
|
||||
},
|
||||
"targetList": {
|
||||
|
||||
@@ -36,8 +36,12 @@ use \Espo\Core\Exceptions\Forbidden;
|
||||
|
||||
class Opportunity extends \Espo\Services\Record
|
||||
{
|
||||
public function reportSalesPipeline($dateFrom, $dateTo)
|
||||
public function reportSalesPipeline($dateFilter, $dateFrom = null, $dateTo = null)
|
||||
{
|
||||
if ($dateFilter !== 'between' && $dateFilter !== 'ever') {
|
||||
list($dateFrom, $dateTo) = $this->getDateRangeByFilter($dateFilter);
|
||||
}
|
||||
|
||||
$pdo = $this->getEntityManager()->getPDO();
|
||||
|
||||
$options = $this->getMetadata()->get('entityDefs.Opportunity.fields.stage.options');
|
||||
@@ -48,8 +52,16 @@ class Opportunity extends \Espo\Services\Record
|
||||
JOIN currency ON currency.id = opportunity.amount_currency
|
||||
WHERE
|
||||
opportunity.deleted = 0 AND
|
||||
";
|
||||
|
||||
if ($dateFilter !== 'ever') {
|
||||
$sql .= "
|
||||
opportunity.close_date >= ".$pdo->quote($dateFrom)." AND
|
||||
opportunity.close_date < ".$pdo->quote($dateTo)." AND
|
||||
";
|
||||
}
|
||||
|
||||
$sql .= "
|
||||
opportunity.stage <> 'Closed Lost'
|
||||
GROUP BY opportunity.stage
|
||||
ORDER BY FIELD(opportunity.stage, '".implode("','", $options)."')
|
||||
@@ -68,8 +80,12 @@ class Opportunity extends \Espo\Services\Record
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function reportByLeadSource($dateFrom, $dateTo)
|
||||
public function reportByLeadSource($dateFilter, $dateFrom = null, $dateTo = null)
|
||||
{
|
||||
if ($dateFilter !== 'between' && $dateFilter !== 'ever') {
|
||||
list($dateFrom, $dateTo) = $this->getDateRangeByFilter($dateFilter);
|
||||
}
|
||||
|
||||
$pdo = $this->getEntityManager()->getPDO();
|
||||
|
||||
$sql = "
|
||||
@@ -78,8 +94,15 @@ class Opportunity extends \Espo\Services\Record
|
||||
JOIN currency ON currency.id = opportunity.amount_currency
|
||||
WHERE
|
||||
opportunity.deleted = 0 AND
|
||||
";
|
||||
if ($dateFilter !== 'ever') {
|
||||
$sql .= "
|
||||
opportunity.close_date >= ".$pdo->quote($dateFrom)." AND
|
||||
opportunity.close_date < ".$pdo->quote($dateTo)." AND
|
||||
";
|
||||
}
|
||||
|
||||
$sql .= "
|
||||
opportunity.stage <> 'Closed Lost' AND
|
||||
opportunity.lead_source <> ''
|
||||
GROUP BY opportunity.lead_source
|
||||
@@ -98,8 +121,12 @@ class Opportunity extends \Espo\Services\Record
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function reportByStage($dateFrom, $dateTo)
|
||||
public function reportByStage($dateFilter, $dateFrom = null, $dateTo = null)
|
||||
{
|
||||
if ($dateFilter !== 'between' && $dateFilter !== 'ever') {
|
||||
list($dateFrom, $dateTo) = $this->getDateRangeByFilter($dateFilter);
|
||||
}
|
||||
|
||||
$pdo = $this->getEntityManager()->getPDO();
|
||||
|
||||
$options = $this->getMetadata()->get('entityDefs.Opportunity.fields.stage.options');
|
||||
@@ -110,8 +137,16 @@ class Opportunity extends \Espo\Services\Record
|
||||
JOIN currency ON currency.id = opportunity.amount_currency
|
||||
WHERE
|
||||
opportunity.deleted = 0 AND
|
||||
";
|
||||
|
||||
if ($dateFilter !== 'ever') {
|
||||
$sql .= "
|
||||
opportunity.close_date >= ".$pdo->quote($dateFrom)." AND
|
||||
opportunity.close_date < ".$pdo->quote($dateTo)." AND
|
||||
";
|
||||
}
|
||||
|
||||
$sql .= "
|
||||
opportunity.stage <> 'Closed Lost' AND
|
||||
opportunity.stage <> 'Closed Won'
|
||||
GROUP BY opportunity.stage
|
||||
@@ -131,8 +166,12 @@ class Opportunity extends \Espo\Services\Record
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function reportSalesByMonth($dateFrom, $dateTo)
|
||||
public function reportSalesByMonth($dateFilter, $dateFrom = null, $dateTo = null)
|
||||
{
|
||||
if ($dateFilter !== 'between' && $dateFilter !== 'ever') {
|
||||
list($dateFrom, $dateTo) = $this->getDateRangeByFilter($dateFilter);
|
||||
}
|
||||
|
||||
$pdo = $this->getEntityManager()->getPDO();
|
||||
|
||||
$sql = "
|
||||
@@ -141,10 +180,17 @@ class Opportunity extends \Espo\Services\Record
|
||||
JOIN currency ON currency.id = opportunity.amount_currency
|
||||
WHERE
|
||||
opportunity.deleted = 0 AND
|
||||
";
|
||||
|
||||
if ($dateFilter !== 'ever') {
|
||||
$sql .= "
|
||||
opportunity.close_date >= ".$pdo->quote($dateFrom)." AND
|
||||
opportunity.close_date < ".$pdo->quote($dateTo)." AND
|
||||
opportunity.stage = 'Closed Won'
|
||||
";
|
||||
}
|
||||
|
||||
$sql .= "
|
||||
opportunity.stage = 'Closed Won'
|
||||
GROUP BY DATE_FORMAT(opportunity.close_date, '%Y-%m')
|
||||
ORDER BY `month`
|
||||
";
|
||||
@@ -159,8 +205,73 @@ class Opportunity extends \Espo\Services\Record
|
||||
$result[$row['month']] = floatval($row['amount']);
|
||||
}
|
||||
|
||||
return $result;
|
||||
$dt = new \DateTime($dateFrom);
|
||||
$dtTo = new \DateTime($dateTo);
|
||||
$dtTo->setDate($dtTo->format('Y'), $dtTo->format('m'), 1);
|
||||
if ($dt && $dtTo) {
|
||||
$interval = new \DateInterval('P1M');
|
||||
while ($dt->getTimestamp() <= $dtTo->getTimestamp()) {
|
||||
$month = $dt->format('Y-m');
|
||||
if (!array_key_exists($month, $result)) {
|
||||
$result[$month] = 0;
|
||||
}
|
||||
$dt->add($interval);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$keyList = array_keys($result);
|
||||
sort($keyList);
|
||||
|
||||
$today = new \DateTime();
|
||||
|
||||
$endPosition = count($keyList) - 1;
|
||||
for ($i = count($keyList) - 1; $i >= 0; $i--) {
|
||||
$key = $keyList[$i];
|
||||
$dt = new \DateTime($key . '-01');
|
||||
|
||||
if ($dt->getTimestamp() < $today->getTimestamp()) {
|
||||
break;
|
||||
}
|
||||
if (empty($result[$key])) {
|
||||
$endPosition = $i;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$keyList = array_slice($keyList, 0, $endPosition);
|
||||
|
||||
return (object) [
|
||||
'keyList' => $keyList,
|
||||
'dataMap' => $result
|
||||
];
|
||||
}
|
||||
|
||||
protected function getDateRangeByFilter($dateFilter)
|
||||
{
|
||||
switch ($dateFilter) {
|
||||
case 'currentYear':
|
||||
$dt = new \DateTime();
|
||||
return [
|
||||
$dt->modify('first day of January this year')->format('Y-m-d'),
|
||||
$dt->add(new \DateInterval('P1Y'))->format('Y-m-d')
|
||||
];
|
||||
case 'currentQuarter':
|
||||
$dt = new \DateTime();
|
||||
$quarter = ceil($dt->format('m') / 3);
|
||||
$dt->modify('first day of January this year');
|
||||
return [
|
||||
$dt->add(new \DateInterval('P'.(($quarter - 1) * 3).'M'))->format('Y-m-d'),
|
||||
$dt->add(new \DateInterval('P3M'))->format('Y-m-d')
|
||||
];
|
||||
case 'currentMonth':
|
||||
$dt = new \DateTime();
|
||||
return [
|
||||
$dt->modify('first day of this month')->format('Y-m-d'),
|
||||
$dt->add(new \DateInterval('P1M'))->format('Y-m-d')
|
||||
];
|
||||
}
|
||||
return [0, 0];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -250,7 +250,7 @@ abstract class Base
|
||||
case 'WEEK_0':
|
||||
return "CONCAT(YEAR({$part}), '/', WEEK({$part}, 0))";
|
||||
case 'WEEK_1':
|
||||
return "CONCAT(YEAR({$part}), '/', WEEK({$part}, 1))";
|
||||
return "CONCAT(YEAR({$part}), '/', WEEK({$part}, 5))";
|
||||
case 'MONTH_NUMBER':
|
||||
$function = 'MONTH';
|
||||
break;
|
||||
@@ -266,7 +266,7 @@ abstract class Base
|
||||
case 'WEEK_NUMBER_0':
|
||||
return "WEEK({$part}, 0)";
|
||||
case 'WEEK_NUMBER_1':
|
||||
return "WEEK({$part}, 1)";
|
||||
return "WEEK({$part}, 5)";
|
||||
case 'HOUR_NUMBER':
|
||||
$function = 'HOUR';
|
||||
break;
|
||||
|
||||
@@ -163,14 +163,14 @@ class EntityManager
|
||||
|
||||
public function saveEntity(Entity $entity, array $options = array())
|
||||
{
|
||||
$entityName = $entity->getEntityName();
|
||||
return $this->getRepository($entityName)->save($entity, $options);
|
||||
$entityType = $entity->getEntityType();
|
||||
return $this->getRepository($entityType)->save($entity, $options);
|
||||
}
|
||||
|
||||
public function removeEntity(Entity $entity, array $options = array())
|
||||
{
|
||||
$entityName = $entity->getEntityName();
|
||||
return $this->getRepository($entityName)->remove($entity, $options);
|
||||
$entityType = $entity->getEntityType();
|
||||
return $this->getRepository($entityType)->remove($entity, $options);
|
||||
}
|
||||
|
||||
public function getRepository($name)
|
||||
@@ -186,6 +186,11 @@ class EntityManager
|
||||
$this->metadata->setData($data);
|
||||
}
|
||||
|
||||
public function hasRepository($name)
|
||||
{
|
||||
return $this->getMetadata()->has($name);
|
||||
}
|
||||
|
||||
public function getMetadata()
|
||||
{
|
||||
return $this->metadata;
|
||||
|
||||
@@ -45,4 +45,12 @@ class Metadata
|
||||
}
|
||||
return $this->data[$entityType];
|
||||
}
|
||||
|
||||
public function has($entityType)
|
||||
{
|
||||
if (!array_key_exists($entityType, $this->data)) {
|
||||
return null;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +136,9 @@ class RDB extends \Espo\ORM\Repository
|
||||
|
||||
public function save(Entity $entity, array $options = array())
|
||||
{
|
||||
$this->beforeSave($entity, $options);
|
||||
if (empty($options['skipBeforeSave']) && empty($options['skipAll'])) {
|
||||
$this->beforeSave($entity, $options);
|
||||
}
|
||||
if ($entity->isNew() && !$entity->isSaved()) {
|
||||
$result = $this->getMapper()->insert($entity);
|
||||
} else {
|
||||
@@ -144,9 +146,13 @@ class RDB extends \Espo\ORM\Repository
|
||||
}
|
||||
if ($result) {
|
||||
$entity->setIsSaved(true);
|
||||
$this->afterSave($entity, $options);
|
||||
if (empty($options['skipAfterSave']) && empty($options['skipAll'])) {
|
||||
$this->afterSave($entity, $options);
|
||||
}
|
||||
if ($entity->isNew()) {
|
||||
$entity->setIsNew(false);
|
||||
if (empty($options['keepNew'])) {
|
||||
$entity->setIsNew(false);
|
||||
}
|
||||
} else {
|
||||
if ($entity->isFetched()) {
|
||||
$entity->updateFetchedValues();
|
||||
|
||||
@@ -185,6 +185,10 @@ class Email extends \Espo\Core\ORM\Repositories\RDB
|
||||
|
||||
protected function beforeSave(Entity $entity, array $options = array())
|
||||
{
|
||||
if ($entity->isNew() && !$entity->get('messageId')) {
|
||||
$entity->setDummyMessageId();
|
||||
}
|
||||
|
||||
$eaRepository = $this->getEntityManager()->getRepository('EmailAddress');
|
||||
|
||||
if ($entity->has('attachmentsIds')) {
|
||||
@@ -322,8 +326,8 @@ class Email extends \Espo\Core\ORM\Repositories\RDB
|
||||
}
|
||||
}
|
||||
|
||||
if ($entity->get('isBeingImportered')) {
|
||||
$entity->set('isBeingImportered', false);
|
||||
if ($entity->get('isBeingImported')) {
|
||||
$entity->set('isBeingImported', false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,10 @@ use Espo\ORM\Entity;
|
||||
|
||||
class Job extends \Espo\Core\ORM\Repositories\RDB
|
||||
{
|
||||
protected $hooksDisabled = true;
|
||||
|
||||
protected $processFieldsAfterSaveDisabled = true;
|
||||
|
||||
protected function init()
|
||||
{
|
||||
parent::init();
|
||||
@@ -46,14 +50,13 @@ class Job extends \Espo\Core\ORM\Repositories\RDB
|
||||
|
||||
public function beforeSave(Entity $entity, array $options = array())
|
||||
{
|
||||
if (!$entity->has('executeTime')) {
|
||||
if (!$entity->has('executeTime') && $entity->isNew()) {
|
||||
$entity->set('executeTime', date('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
if (!$entity->has('attempts') && $entity->isNew()) {
|
||||
$attempts = $this->getConfig()->get('cron.attempts', 0);
|
||||
$attempts = $this->getConfig()->get('jobRerunAttemptNumber', 0);
|
||||
$entity->set('attempts', $attempts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* 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.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Repositories;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
class ScheduledJob extends \Espo\Core\ORM\Repositories\RDB
|
||||
{
|
||||
protected $hooksDisabled = true;
|
||||
|
||||
protected $processFieldsAfterSaveDisabled = true;
|
||||
}
|
||||
@@ -32,6 +32,7 @@ namespace Espo\Repositories;
|
||||
use \Espo\ORM\Entity;
|
||||
|
||||
use \Espo\Core\Exceptions\Error;
|
||||
use \Espo\Core\Exceptions\Conflict;
|
||||
|
||||
class User extends \Espo\Core\ORM\Repositories\RDB
|
||||
{
|
||||
@@ -50,7 +51,7 @@ class User extends \Espo\Core\ORM\Repositories\RDB
|
||||
))->findOne();
|
||||
|
||||
if ($user) {
|
||||
throw new Error();
|
||||
throw new Conflict(json_encode(['reason' => 'userNameExists']));
|
||||
}
|
||||
} else {
|
||||
if ($entity->isFieldChanged('userName')) {
|
||||
@@ -64,7 +65,7 @@ class User extends \Espo\Core\ORM\Repositories\RDB
|
||||
'id!=' => $entity->id
|
||||
))->findOne();
|
||||
if ($user) {
|
||||
throw new Error();
|
||||
throw new Conflict(json_encode(['reason' => 'userNameExists']));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,4 +114,3 @@ class User extends \Espo\Core\ORM\Repositories\RDB
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
"boolFilterList": "Additional Filters",
|
||||
"sortBy": "Order (field)",
|
||||
"sortDirection": "Order (direction)",
|
||||
"expandedLayout": "Layout"
|
||||
"expandedLayout": "Layout",
|
||||
"dateFilter": "Date Filter"
|
||||
},
|
||||
"options": {
|
||||
"mode": {
|
||||
|
||||
@@ -37,7 +37,15 @@
|
||||
"emailAccounts": "Personal Accounts",
|
||||
"hasAttachment": "Has Attachment",
|
||||
"assignedUsers": "Assigned Users",
|
||||
"sentBy": "Sent By"
|
||||
"sentBy": "Sent By",
|
||||
"ccEmailAddresses": "CC Email Addresses",
|
||||
"messageId": "Message Id",
|
||||
"messageIdInternal": "Message Id (Internal)",
|
||||
"folderId": "Folder Id",
|
||||
"fromName": "From Name",
|
||||
"fromString": "From String",
|
||||
"isSystem": "Is System"
|
||||
|
||||
},
|
||||
"links": {
|
||||
"replied": "Replied",
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
"CheckEmailAccounts": "Check Personal Email Accounts",
|
||||
"SendEmailReminders": "Send Email Reminders",
|
||||
"AuthTokenControl": "Auth Token Control",
|
||||
"SendEmailNotifications": "Send Email Notifications"
|
||||
"SendEmailNotifications": "Send Email Notifications",
|
||||
"CheckNewVersion": "Check for New Version"
|
||||
},
|
||||
"cronSetup": {
|
||||
"linux": "Note: Add this line to the crontab file to run Espo Scheduled Jobs:",
|
||||
|
||||
@@ -68,10 +68,10 @@
|
||||
"theme": "Theme",
|
||||
"userThemesDisabled": "Disable User Themes",
|
||||
"emailMessageMaxSize": "Email Max Size (Mb)",
|
||||
"massEmailMaxPerHourCount": "Max count of emails sent per hour",
|
||||
"massEmailMaxPerHourCount": "Max number of emails sent per hour",
|
||||
"personalEmailMaxPortionSize": "Max email portion size for personal account fetching",
|
||||
"inboundEmailMaxPortionSize": "Max email portion size for group account fetching",
|
||||
"maxEmailAccountCount": "Max count of personal email accounts per user",
|
||||
"maxEmailAccountCount": "Max number of personal email accounts per user",
|
||||
"authTokenLifetime": "Auth Token Lifetime (hours)",
|
||||
"authTokenMaxIdleTime": "Auth Token Max Idle Time (hours)",
|
||||
"dashboardLayout": "Dashboard Layout (default)",
|
||||
|
||||
@@ -81,7 +81,8 @@
|
||||
"forbidden": "Forbidden, please try later",
|
||||
"uniqueLinkHasBeenSent": "The unique URL has been sent to the specified email address.",
|
||||
"passwordChangedByRequest": "Password has been changed.",
|
||||
"setupSmtpBefore": "You need to setup <a href=\"{url}\">SMTP settings</a> to make the system be able to send password in email."
|
||||
"setupSmtpBefore": "You need to setup <a href=\"{url}\">SMTP settings</a> to make the system be able to send password in email.",
|
||||
"userNameExists": "User Name already exists"
|
||||
},
|
||||
"options": {
|
||||
"gender": {
|
||||
|
||||
@@ -46,6 +46,9 @@
|
||||
"desc": "Mažėjančia tvarka"
|
||||
}
|
||||
},
|
||||
"messages": {
|
||||
"linkAlreadyExists": "Sąsajos pavadinimo konfliktas."
|
||||
},
|
||||
"tooltips": {
|
||||
"statusField": "Šio lauko atnaujinimai įrašomi sraute.",
|
||||
"textFilterFields": "Laukeliai, naudojami teksto paieškoje.",
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
"PhoneNumber": "Telefono numeris"
|
||||
},
|
||||
"scopeNamesPlural": {
|
||||
"Email": "El. laiška",
|
||||
"Email": "El. laiškai",
|
||||
"User": "Vartotojai",
|
||||
"Team": "Komandos",
|
||||
"Role": "Rolės",
|
||||
@@ -54,7 +54,7 @@
|
||||
"ExternalAccount": "Išorinės paskyros",
|
||||
"Extension": "Plėtiniai",
|
||||
"Dashboard": "Prietaisų skydelis",
|
||||
"InboundEmail": "Grupės el. pašto paskyros ",
|
||||
"InboundEmail": "Grupės el. pašto paskyros",
|
||||
"Stream": "Srautas",
|
||||
"Import": "Importuoti rezultatus",
|
||||
"Template": "Šablonai",
|
||||
@@ -196,6 +196,7 @@
|
||||
"List View": "Rodyti kaip sąrašą\n",
|
||||
"Tree View": "Rodyti kaip medį",
|
||||
"Unlink All": "Atsieti visus",
|
||||
"Total": "Iš viso",
|
||||
"Print to PDF": "Spausdinti į PDF",
|
||||
"Default": "Numatytas",
|
||||
"Number": "Numeris",
|
||||
@@ -291,7 +292,7 @@
|
||||
"firstName": "Vardas",
|
||||
"lastName": "Pavardė",
|
||||
"salutationName": "Sveikinimas",
|
||||
"assignedUser": "Priskirta vartotojas",
|
||||
"assignedUser": "Priskirtas darbuotojas",
|
||||
"assignedUsers": "Priskirti vartotojai",
|
||||
"emailAddress": "El. paštas",
|
||||
"assignedUserName": "Priskirto vartotojo vardas",
|
||||
@@ -313,7 +314,7 @@
|
||||
"children": "Antrinis"
|
||||
},
|
||||
"links": {
|
||||
"assignedUser": "Priskirta vartotojas",
|
||||
"assignedUser": "Priskirtas darbuotojas",
|
||||
"createdBy": "Sukurta",
|
||||
"modifiedBy": "Koreguota",
|
||||
"team": "Komanda",
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"Header Row Value": "Antraštinės eilutės vertė",
|
||||
"Field": "Laukelis",
|
||||
"What to Import?": "Ką importuoti?",
|
||||
"Entity Type": "Vedinio tipas",
|
||||
"What to do?": "Ką daryti?",
|
||||
"Properties": "Savybės",
|
||||
"Header Row": "Antraštinė eilutė",
|
||||
@@ -56,6 +57,7 @@
|
||||
},
|
||||
"fields": {
|
||||
"file": "Failas",
|
||||
"entityType": "Vedinio tipas",
|
||||
"imported": "Importuoti įrašai",
|
||||
"duplicates": "Dublikuoti įrašai",
|
||||
"updated": "Atnaujinti įrašai",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"fields": {
|
||||
"enabled": "Įjungta",
|
||||
"clientId": "Kliento ID",
|
||||
"redirectUri": "Nukreipti URL",
|
||||
"apiKey": "API raktas"
|
||||
|
||||
@@ -41,13 +41,12 @@
|
||||
"exportDisabled": "Išjungti eksportavimą (leidžiama tik admionistratoriui)",
|
||||
"b2cMode": "B2C režimas",
|
||||
"avatarsDisabled": "Išjungti piktogramas",
|
||||
"displayListViewRecordCount": "Rodyti visus (sąrašo rodmenyje)",
|
||||
"theme": "Tema",
|
||||
"userThemesDisabled": "Išjungti naudotojo temas",
|
||||
"emailMessageMaxSize": "Maksimalus el. laiško dydis (Mb)",
|
||||
"massEmailMaxPerHourCount": "Maksimalus per valandą siunčiamų el. laiškų skaičius",
|
||||
"personalEmailMaxPortionSize": "Maksimalus el. pašto siuntimo dydis asmeninei paskyrai",
|
||||
"inboundEmailMaxPortionSize": "Maksimalus el. laiškų kiekis grupės paskyrai laikomas",
|
||||
"maxEmailAccountCount": "Maksimalus el. pašto paskyrų kiekis vienam vartotojui",
|
||||
"authTokenLifetime": "Autentikavimo žymės gyvavimas (valandomis)",
|
||||
"authTokenMaxIdleTime": "Autentikavimo žymės maksimalus laikas (valandomis)",
|
||||
"dashboardLayout": "Prietaisų skydelio išdėstymas (numatytas)",
|
||||
|
||||
@@ -3,13 +3,11 @@
|
||||
"rows": [
|
||||
[
|
||||
{"name": "name"},
|
||||
{"name": "exportPermission"}
|
||||
],
|
||||
[
|
||||
{"name": "assignmentPermission"},
|
||||
{"name": "exportPermission"},
|
||||
{"name": "userPermission"}
|
||||
],
|
||||
[
|
||||
{"name": "assignmentPermission"},
|
||||
{"name": "portalPermission"},
|
||||
{"name": "groupEmailAccountPermission"}
|
||||
]
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
{
|
||||
"label": "Currency Settings",
|
||||
"rows": [
|
||||
[{"name": "defaultCurrency"}, {"name": "currencyList"}]
|
||||
[{"name": "defaultCurrency"}, {"name": "currencyFormat"}],
|
||||
[{"name": "currencyList"}, {"name": "currencyDecimalPlaces"}]
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -11,12 +11,10 @@
|
||||
{
|
||||
"label": "Locale",
|
||||
"rows": [
|
||||
[{"name": "dateFormat"}, {"name": "timeZone"}],
|
||||
[{"name": "timeFormat"}, {"name": "weekStart"}],
|
||||
[{"name": "language"}, false],
|
||||
[{"name": "currencyFormat"}, {"name": "thousandSeparator"}],
|
||||
[{"name": "currencyDecimalPlaces"}, {"name": "decimalMark"}],
|
||||
[{"name": "addressFormat"}, false],
|
||||
[{"name": "language"}, {"name": "timeZone"}],
|
||||
[{"name": "dateFormat"}, {"name": "weekStart"}],
|
||||
[{"name": "timeFormat"}, {"name": "thousandSeparator"}],
|
||||
[{"name": "addressFormat"}, {"name": "decimalMark"}],
|
||||
[{"name": "addressPreview"}, false]
|
||||
|
||||
]
|
||||
|
||||
@@ -64,9 +64,6 @@
|
||||
"Attachment": {
|
||||
"parent": false
|
||||
},
|
||||
"User": {
|
||||
"gender": false
|
||||
},
|
||||
"EmailFolder": {
|
||||
"assignedUser": false
|
||||
},
|
||||
@@ -78,8 +75,30 @@
|
||||
},
|
||||
"default": {
|
||||
"scopeLevel": {
|
||||
"User": {
|
||||
"read": "all"
|
||||
}
|
||||
},
|
||||
"fieldLevel": {
|
||||
},
|
||||
"scopeFieldLevel": {
|
||||
"User": {
|
||||
"gender": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"strictDefault": {
|
||||
"scopeLevel": {
|
||||
"User": {
|
||||
"read": "own"
|
||||
}
|
||||
},
|
||||
"fieldLevel": {
|
||||
},
|
||||
"scopeFieldLevel": {
|
||||
"User": {
|
||||
"gender": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"valuePermissionList": [
|
||||
|
||||
@@ -80,12 +80,13 @@
|
||||
"isInternal": false,
|
||||
"isGlobal": false
|
||||
},
|
||||
"User": {
|
||||
"gender": false
|
||||
"Email": {
|
||||
"inboundEmails": false,
|
||||
"emailAccounts": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"strictDefault": {
|
||||
"scopeLevel": {
|
||||
},
|
||||
"fieldLevel": {
|
||||
@@ -113,22 +114,21 @@
|
||||
"edit": "no"
|
||||
},
|
||||
"leads": false
|
||||
},
|
||||
"KnowledgeBaseArticle": {
|
||||
"assignedUser": false
|
||||
},
|
||||
"User": {
|
||||
"gender": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"valuePermissionList": [
|
||||
"exportPermission"
|
||||
],
|
||||
"permissionsDefaults": {
|
||||
"exportPermission": "no"
|
||||
},
|
||||
"permissionsStrictDefaults": {
|
||||
"exportPermission": "no"
|
||||
},
|
||||
"scopeLevelTypesDefaults": {
|
||||
"boolean": false,
|
||||
"record": false
|
||||
},
|
||||
"scopeLevelTypesStrictDefaults": {
|
||||
"boolean": false,
|
||||
"record": false
|
||||
|
||||
@@ -131,7 +131,8 @@
|
||||
"messageId": {
|
||||
"type": "varchar",
|
||||
"maxLength": 255,
|
||||
"readOnly": true
|
||||
"readOnly": true,
|
||||
"index": true
|
||||
},
|
||||
"messageIdInternal": {
|
||||
"type": "varchar",
|
||||
@@ -416,9 +417,6 @@
|
||||
"textFilterFields": ["name", "bodyPlain", "body"]
|
||||
},
|
||||
"indexes": {
|
||||
"dateSentAssignedUser": {
|
||||
"columns": ["dateSent", "assignedUserId"]
|
||||
},
|
||||
"dateSent": {
|
||||
"columns": ["dateSent", "deleted"]
|
||||
},
|
||||
|
||||
@@ -39,6 +39,9 @@
|
||||
"link": "scheduledJob",
|
||||
"field": "job"
|
||||
},
|
||||
"pid": {
|
||||
"type": "int"
|
||||
},
|
||||
"attempts": {
|
||||
"type": "int"
|
||||
},
|
||||
|
||||
@@ -66,8 +66,8 @@
|
||||
"asc": true
|
||||
},
|
||||
"jobSchedulingMap": {
|
||||
"CheckInboundEmails": "*/4 * * * *",
|
||||
"CheckEmailAccounts": "*/5 * * * *",
|
||||
"CheckInboundEmails": "*/2 * * * *",
|
||||
"CheckEmailAccounts": "*/1 * * * *",
|
||||
"SendEmailReminders": "*/2 * * * *",
|
||||
"Cleanup": "1 1 * * 0",
|
||||
"AuthTokenControl": "*/6 * * * *",
|
||||
@@ -78,7 +78,8 @@
|
||||
"isSystem": true,
|
||||
"scheduling": "1 */12 * * *"
|
||||
},
|
||||
"NewVersionChecker": {
|
||||
"CheckNewVersion": {
|
||||
"name": "Check for New Version",
|
||||
"isSystem": true,
|
||||
"scheduling": "15 5 * * *"
|
||||
}
|
||||
|
||||
@@ -337,10 +337,14 @@
|
||||
"tooltip": true
|
||||
},
|
||||
"inboundEmailMaxPortionSize": {
|
||||
"type": "int"
|
||||
"type": "int",
|
||||
"min": 1,
|
||||
"max": 500
|
||||
},
|
||||
"personalEmailMaxPortionSize": {
|
||||
"type": "int"
|
||||
"type": "int",
|
||||
"min": 1,
|
||||
"max": 500
|
||||
},
|
||||
"maxEmailAccountCount": {
|
||||
"type": "int"
|
||||
|
||||
@@ -5,5 +5,12 @@
|
||||
"dashboardCellMargin": 19,
|
||||
"navbarHeight": 44,
|
||||
"modalFooterAtTheTop": true,
|
||||
"modalFullHeight": true
|
||||
"modalFullHeight": true,
|
||||
"fontSize": 14,
|
||||
"textColor": "#333",
|
||||
"chartGridColor": "#ddd",
|
||||
"chartSuccessColor": "#87C956",
|
||||
"chartTickColor": "#e8eced",
|
||||
"chartColorList": ["#6FA8D6", "#4E6CAD", "#EDC555", "#ED8F42", "#DE6666", "#7CC4A4", "#8A7CC2", "#D4729B"],
|
||||
"chartColorAlternativeList": ["#6FA8D6", "#EDC555", "#ED8F42", "#7CC4A4", "#D4729B"]
|
||||
}
|
||||
@@ -5,5 +5,12 @@
|
||||
"dashboardCellMargin": 19,
|
||||
"navbarHeight": 44,
|
||||
"modalFooterAtTheTop": true,
|
||||
"modalFullHeight": true
|
||||
"modalFullHeight": true,
|
||||
"fontSize": 14,
|
||||
"textColor": "#333",
|
||||
"chartGridColor": "#ddd",
|
||||
"chartTickColor": "#e8eced",
|
||||
"chartSuccessColor": "#87C956",
|
||||
"chartColorList": ["#6FA8D6", "#4E6CAD", "#EDC555", "#ED8F42", "#DE6666", "#7CC4A4", "#8A7CC2", "#D4729B"],
|
||||
"chartColorAlternativeList": ["#6FA8D6", "#EDC555", "#ED8F42", "#7CC4A4", "#D4729B"]
|
||||
}
|
||||
@@ -8,5 +8,12 @@
|
||||
"dashboardCellHeight": 155,
|
||||
"dashboardCellMargin": 19,
|
||||
"modalFooterAtTheTop": true,
|
||||
"modalFullHeight": true
|
||||
"modalFullHeight": true,
|
||||
"fontSize": 14,
|
||||
"textColor": "#333",
|
||||
"chartGridColor": "#ddd",
|
||||
"chartTickColor": "#e8eced",
|
||||
"chartSuccessColor": "#87C956",
|
||||
"chartColorList": ["#6FA8D6", "#4E6CAD", "#EDC555", "#ED8F42", "#DE6666", "#7CC4A4", "#8A7CC2", "#D4729B"],
|
||||
"chartColorAlternativeList": ["#6FA8D6", "#EDC555", "#ED8F42", "#7CC4A4", "#D4729B"]
|
||||
}
|
||||
@@ -5,5 +5,12 @@
|
||||
"dashboardCellMargin": 19,
|
||||
"navbarHeight": 44,
|
||||
"modalFooterAtTheTop": true,
|
||||
"modalFullHeight": true
|
||||
"modalFullHeight": true,
|
||||
"fontSize": 14,
|
||||
"textColor": "#333",
|
||||
"chartGridColor": "#ddd",
|
||||
"chartTickColor": "#e8eced",
|
||||
"chartSuccessColor": "#85b75f",
|
||||
"chartColorList": ["#6FA8D6", "#4E6CAD", "#EDC555", "#ED8F42", "#DE6666", "#7CC4A4", "#8A7CC2", "#D4729B"],
|
||||
"chartColorAlternativeList": ["#6FA8D6", "#EDC555", "#ED8F42", "#7CC4A4", "#D4729B"]
|
||||
}
|
||||
@@ -8,5 +8,12 @@
|
||||
"dashboardCellHeight": 155,
|
||||
"dashboardCellMargin": 19,
|
||||
"modalFooterAtTheTop": true,
|
||||
"modalFullHeight": true
|
||||
"modalFullHeight": true,
|
||||
"fontSize": 14,
|
||||
"textColor": "#333",
|
||||
"chartGridColor": "#ddd",
|
||||
"chartTickColor": "#e8eced",
|
||||
"chartSuccessColor": "#85b75f",
|
||||
"chartColorList": ["#6FA8D6", "#4E6CAD", "#EDC555", "#ED8F42", "#DE6666", "#7CC4A4", "#8A7CC2", "#D4729B"],
|
||||
"chartColorAlternativeList": ["#6FA8D6", "#EDC555", "#ED8F42", "#7CC4A4", "#D4729B"]
|
||||
}
|
||||
@@ -5,5 +5,12 @@
|
||||
"dashboardCellMargin": 19,
|
||||
"navbarHeight": 44,
|
||||
"modalFooterAtTheTop": true,
|
||||
"modalFullHeight": true
|
||||
"modalFullHeight": true,
|
||||
"fontSize": 14,
|
||||
"textColor": "#333",
|
||||
"chartGridColor": "#ddd",
|
||||
"chartTickColor": "#e8eced",
|
||||
"chartSuccessColor": "#83CD77",
|
||||
"chartColorList": ["#6FA8D6", "#4E6CAD", "#EDC555", "#ED8F42", "#DE6666", "#7CC4A4", "#8A7CC2", "#D4729B"],
|
||||
"chartColorAlternativeList": ["#6FA8D6", "#EDC555", "#ED8F42", "#7CC4A4", "#D4729B"]
|
||||
}
|
||||
@@ -8,5 +8,12 @@
|
||||
"dashboardCellHeight": 155,
|
||||
"dashboardCellMargin": 19,
|
||||
"modalFooterAtTheTop": true,
|
||||
"modalFullHeight": true
|
||||
"modalFullHeight": true,
|
||||
"fontSize": 14,
|
||||
"textColor": "#333",
|
||||
"chartGridColor": "#ddd",
|
||||
"chartTickColor": "#e8eced",
|
||||
"chartSuccessColor": "#83CD77",
|
||||
"chartColorList": ["#6FA8D6", "#4E6CAD", "#EDC555", "#ED8F42", "#DE6666", "#7CC4A4", "#8A7CC2", "#D4729B"],
|
||||
"chartColorAlternativeList": ["#6FA8D6", "#EDC555", "#ED8F42", "#7CC4A4", "#D4729B"]
|
||||
}
|
||||
@@ -5,5 +5,12 @@
|
||||
"dashboardCellMargin": 19,
|
||||
"navbarHeight": 44,
|
||||
"modalFooterAtTheTop": true,
|
||||
"modalFullHeight": true
|
||||
"modalFullHeight": true,
|
||||
"textColor": "#333",
|
||||
"fontSize": 14,
|
||||
"chartGridColor": "#ddd",
|
||||
"chartTickColor": "#e8eced",
|
||||
"chartSuccessColor": "#7BC169",
|
||||
"chartColorList": ["#6FA8D6", "#4E6CAD", "#EDC555", "#ED8F42", "#DE6666", "#7CC4A4", "#8A7CC2", "#D4729B"],
|
||||
"chartColorAlternativeList": ["#6FA8D6", "#EDC555", "#ED8F42", "#7CC4A4", "#D4729B"]
|
||||
}
|
||||
@@ -8,5 +8,12 @@
|
||||
"dashboardCellHeight": 155,
|
||||
"dashboardCellMargin": 19,
|
||||
"modalFooterAtTheTop": true,
|
||||
"modalFullHeight": true
|
||||
"modalFullHeight": true,
|
||||
"fontSize": 14,
|
||||
"textColor": "#333",
|
||||
"chartGridColor": "#ddd",
|
||||
"chartTickColor": "#e8eced",
|
||||
"chartSuccessColor": "#7BC169",
|
||||
"chartColorList": ["#6FA8D6", "#4E6CAD", "#EDC555", "#ED8F42", "#DE6666", "#7CC4A4", "#8A7CC2", "#D4729B"],
|
||||
"chartColorAlternativeList": ["#6FA8D6", "#EDC555", "#ED8F42", "#7CC4A4", "#D4729B"]
|
||||
}
|
||||
@@ -31,7 +31,7 @@ namespace Espo\Services;
|
||||
|
||||
class AdminNotifications extends \Espo\Core\Services\Base
|
||||
{
|
||||
public function newVersionChecker($data)
|
||||
public function jobCheckNewVersion($data)
|
||||
{
|
||||
$config = $this->getConfig();
|
||||
|
||||
|
||||
@@ -209,4 +209,14 @@ class App extends \Espo\Core\Services\Base
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function jobClearCache()
|
||||
{
|
||||
$this->getInjection('container')->get('dataManager')->clearCache();
|
||||
}
|
||||
|
||||
public function jobRebuild()
|
||||
{
|
||||
$this->getInjection('container')->get('dataManager')->rebuild();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,9 +119,11 @@ class Email extends Record
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$emailAccountService = $this->getServiceFactory()->create('EmailAccount');
|
||||
$emailAccount = $emailAccountService->findAccountForUser($this->getUser(), $fromAddress);
|
||||
|
||||
if (!$smtpParams) {
|
||||
$emailAccountService = $this->getServiceFactory()->create('EmailAccount');
|
||||
$emailAccount = $emailAccountService->findAccountForUser($this->getUser(), $fromAddress);
|
||||
if ($emailAccount) {
|
||||
$smtpParams = $emailAccountService->getSmtpParamsFromAccount($emailAccount);
|
||||
if ($smtpParams) {
|
||||
|
||||
@@ -320,8 +320,8 @@ class EmailAccount extends Record
|
||||
}
|
||||
|
||||
if (!empty($email)) {
|
||||
$this->getEntityManager()->getRepository('EmailAccount')->relate($emailAccount, 'emails', $email);
|
||||
if (!$email->isFetched()) {
|
||||
$this->getEntityManager()->getRepository('EmailAccount')->relate($emailAccount, 'emails', $email);
|
||||
$this->noteAboutEmail($email);
|
||||
}
|
||||
}
|
||||
@@ -368,6 +368,9 @@ class EmailAccount extends Record
|
||||
$email = $importer->importMessage($parserName, $message, $userId, $teamIdList, $userIdList, $filterCollection, $fetchOnlyHeader, $folderData);
|
||||
} catch (\Exception $e) {
|
||||
$GLOBALS['log']->error('EmailAccount '.$emailAccount->id.' (Import Message w/ '.$parserName.'): [' . $e->getCode() . '] ' .$e->getMessage());
|
||||
if ($e->getCode() === 'HY000' && strpos($e->getMessage(), '1100') !== false) {
|
||||
$this->getEntityManager()->getPdo()->query('UNLOCK TABLES');
|
||||
}
|
||||
}
|
||||
return $email;
|
||||
}
|
||||
|
||||
@@ -394,6 +394,9 @@ class InboundEmail extends \Espo\Services\Record
|
||||
$email = $importer->importMessage($parserName, $message, $userId, $teamIdList, $userIdList, $filterCollection, $fetchOnlyHeader, $folderData);
|
||||
} catch (\Exception $e) {
|
||||
$GLOBALS['log']->error('InboundEmail '.$emailAccount->id.' (Import Message w/ '.$parserName.'): [' . $e->getCode() . '] ' .$e->getMessage());
|
||||
if ($e->getCode() === 'HY000' && strpos($e->getMessage(), '1100') !== false) {
|
||||
$this->getEntityManager()->getPdo()->query('UNLOCK TABLES');
|
||||
}
|
||||
}
|
||||
return $email;
|
||||
}
|
||||
|
||||
@@ -282,13 +282,10 @@ class Record extends \Espo\Core\Services\Base
|
||||
$fieldDefs = $this->getMetadata()->get('entityDefs.' . $entity->getEntityType() . '.fields', array());
|
||||
foreach ($fieldDefs as $field => $defs) {
|
||||
if (isset($defs['type']) && $defs['type'] == 'linkParent') {
|
||||
$id = $entity->get($field . 'Id');
|
||||
$scope = $entity->get($field . 'Type');
|
||||
|
||||
if ($scope) {
|
||||
if ($foreignEntity = $this->getEntityManager()->getEntity($scope, $id)) {
|
||||
$entity->set($field . 'Name', $foreignEntity->get('name'));
|
||||
}
|
||||
$parentId = $entity->get($field . 'Id');
|
||||
$parentType = $entity->get($field . 'Type');
|
||||
if ($parentId && $parentType) {
|
||||
$entity->loadParentNameField($field);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1065,7 +1062,7 @@ class Record extends \Espo\Core\Services\Base
|
||||
$ids = $params['ids'];
|
||||
foreach ($ids as $id) {
|
||||
$entity = $this->getEntity($id);
|
||||
if ($this->getAcl()->check($entity, 'edit')) {
|
||||
if ($this->getAcl()->check($entity, 'edit') && $this->checkEntityForMassUpdate($entity, $data)) {
|
||||
$entity->set($data);
|
||||
if ($this->checkAssignment($entity)) {
|
||||
if ($repository->save($entity)) {
|
||||
@@ -1095,7 +1092,7 @@ class Record extends \Espo\Core\Services\Base
|
||||
$collection = $repository->find($selectParams);
|
||||
|
||||
foreach ($collection as $entity) {
|
||||
if ($this->getAcl()->check($entity, 'edit')) {
|
||||
if ($this->getAcl()->check($entity, 'edit') && $this->checkEntityForMassUpdate($entity, $data)) {
|
||||
$entity->set($data);
|
||||
if ($this->checkAssignment($entity)) {
|
||||
if ($repository->save($entity)) {
|
||||
@@ -1128,6 +1125,11 @@ class Record extends \Espo\Core\Services\Base
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function checkEntityForMassUpdate(Entity $entity, $data)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function massRemove(array $params)
|
||||
{
|
||||
$idsRemoved = array();
|
||||
@@ -1306,14 +1308,16 @@ class Record extends \Espo\Core\Services\Base
|
||||
return false;
|
||||
}
|
||||
|
||||
public function checkAttributeIsAllowedForExport($entity, $attribute)
|
||||
public function checkAttributeIsAllowedForExport($entity, $attribute, $isExportAllFields = false)
|
||||
{
|
||||
$entity = $this->getEntityManager()->getEntity($this->getEntityType());
|
||||
|
||||
if (in_array($attribute, $this->internalAttributeList)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$isExportAllFields) {
|
||||
return true;
|
||||
}
|
||||
$isNotStorable = $entity->getAttributeParam($attribute, 'notStorable');
|
||||
if (!$isNotStorable) {
|
||||
return true;
|
||||
@@ -1441,7 +1445,7 @@ class Record extends \Espo\Core\Services\Base
|
||||
if (in_array($attribute, $attributeListToSkip)) {
|
||||
continue;
|
||||
}
|
||||
if ($this->checkAttributeIsAllowedForExport($seed, $attribute)) {
|
||||
if ($this->checkAttributeIsAllowedForExport($seed, $attribute, true)) {
|
||||
$attributeList[] = $attribute;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,6 +212,15 @@ class User extends Record
|
||||
return $passwordHash->hash($password);
|
||||
}
|
||||
|
||||
protected function filterInput($data)
|
||||
{
|
||||
parent::filterInput($data);
|
||||
|
||||
if (!$this->getUser()->get('isSuperAdmin')) {
|
||||
unset($data->isSuperAdmin);
|
||||
}
|
||||
}
|
||||
|
||||
public function createEntity($data)
|
||||
{
|
||||
$newPassword = null;
|
||||
@@ -219,9 +228,6 @@ class User extends Record
|
||||
$newPassword = $data->password;
|
||||
$data->password = $this->hashPassword($data->password);
|
||||
}
|
||||
if (!$this->getUser()->get('isSuperAdmin')) {
|
||||
unset($data->isSuperAdmin);
|
||||
}
|
||||
|
||||
$user = parent::createEntity($data);
|
||||
|
||||
@@ -251,9 +257,6 @@ class User extends Record
|
||||
unset($data->isActive);
|
||||
unset($data->isPortalUser);
|
||||
}
|
||||
if (!$this->getUser()->get('isSuperAdmin')) {
|
||||
unset($data->isSuperAdmin);
|
||||
}
|
||||
|
||||
$user = parent::updateEntity($id, $data);
|
||||
|
||||
@@ -470,6 +473,25 @@ class User extends Record
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function checkEntityForMassUpdate(Entity $entity, $data)
|
||||
{
|
||||
if ($entity->id == 'system') {
|
||||
return false;
|
||||
}
|
||||
if ($entity->id == $this->getUser()->id) {
|
||||
if (property_exists($data, 'isActive')) {
|
||||
return false;
|
||||
}
|
||||
if (property_exists($data, 'isPortalUser')) {
|
||||
return false;
|
||||
}
|
||||
if (property_exists($data, 'isSuperAdmin')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function afterUpdate(Entity $entity, array $data = array())
|
||||
{
|
||||
parent::afterUpdate($entity, $data);
|
||||
|
||||
Vendored
+1920
-2
File diff suppressed because one or more lines are too long
+5
-1
@@ -1158,7 +1158,11 @@ var Bull = Bull || {};
|
||||
|
||||
var proceed = function (layoutTemplate) {
|
||||
var injection = _.extend(layoutDefs, data || {});
|
||||
callback(_.template(layoutTemplate, injection));
|
||||
var template = _.template(layoutTemplate, injection);
|
||||
if (typeof template === 'function') {
|
||||
template = template(injection);
|
||||
}
|
||||
callback(template);
|
||||
}.bind(this);
|
||||
|
||||
var layoutTemplate = this._getCachedLayoutTemplate(layoutType);
|
||||
|
||||
Vendored
+3
-3
File diff suppressed because one or more lines are too long
@@ -158,6 +158,10 @@ 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) {
|
||||
@@ -376,6 +380,7 @@ Espo.define('crm:views/calendar/calendar', ['view', 'lib!full-calendar'], functi
|
||||
},
|
||||
|
||||
adjustSize: function () {
|
||||
if (this.isRemoved) return;
|
||||
var height = this.getCalculatedHeight();
|
||||
this.$calendar.fullCalendar('option', 'contentHeight', height);
|
||||
},
|
||||
|
||||
@@ -36,17 +36,38 @@ Espo.define('crm:views/dashlets/abstract/chart', ['views/dashlets/abstract/base'
|
||||
|
||||
thousandSeparator: ',',
|
||||
|
||||
colors: ['#6FA8D6', '#4E6CAD', '#EDC555', '#ED8F42', '#DE6666', '#7CC4A4', '#8A7CC2', '#D4729B'],
|
||||
defaultColorList: ['#6FA8D6', '#4E6CAD', '#EDC555', '#ED8F42', '#DE6666', '#7CC4A4', '#8A7CC2', '#D4729B'],
|
||||
|
||||
successColor: '#85b75f',
|
||||
|
||||
outlineColor: '#333',
|
||||
gridColor: '#ddd',
|
||||
|
||||
tickColor: '#e8eced',
|
||||
|
||||
textColor: '#333',
|
||||
|
||||
hoverColor: '#FF3F19',
|
||||
|
||||
legendColumnWidth: 90,
|
||||
|
||||
legendColumnNumber: 6,
|
||||
|
||||
labelFormatter: function (v) {
|
||||
return '<span style="color:'+this.textColor+'">' + v + '</span>';
|
||||
},
|
||||
|
||||
init: function () {
|
||||
Dep.prototype.init.call(this);
|
||||
|
||||
this.flotr = Flotr;
|
||||
|
||||
this.successColor = this.getThemeManager().getParam('chartSuccessColor') || this.successColor;
|
||||
this.colorList = this.getThemeManager().getParam('chartColorList') || this.defaultColorList;
|
||||
this.tickColor = this.getThemeManager().getParam('chartTickColor') || this.tickColor;
|
||||
this.gridColor = this.getThemeManager().getParam('chartGridColor') || this.gridColor;
|
||||
this.textColor = this.getThemeManager().getParam('textColor') || this.textColor;
|
||||
this.hoverColor = this.getThemeManager().getParam('hoverColor') || this.hoverColor;
|
||||
|
||||
if (this.getPreferences().has('decimalMark')) {
|
||||
this.decimalMark = this.getPreferences().get('decimalMark')
|
||||
} else {
|
||||
@@ -64,7 +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.drow();
|
||||
this.draw();
|
||||
}.bind(this));
|
||||
}, this);
|
||||
|
||||
@@ -73,34 +94,86 @@ Espo.define('crm:views/dashlets/abstract/chart', ['views/dashlets/abstract/base'
|
||||
}, this);
|
||||
},
|
||||
|
||||
formatNumber: function (value) {
|
||||
formatNumber: function (value, isCurrency) {
|
||||
if (value !== null) {
|
||||
var maxDecimalPlaces = 2;
|
||||
var currencyDecimalPlaces = this.getConfig().get('currencyDecimalPlaces');
|
||||
|
||||
if (isCurrency) {
|
||||
if (currencyDecimalPlaces === 0) {
|
||||
value = Math.round(value);
|
||||
} else if (currencyDecimalPlaces) {
|
||||
value = Math.round(value * Math.pow(10, currencyDecimalPlaces)) / (Math.pow(10, currencyDecimalPlaces));
|
||||
} else {
|
||||
value = Math.round(value * Math.pow(10, maxDecimalPlaces)) / (Math.pow(10, maxDecimalPlaces));
|
||||
}
|
||||
} else {
|
||||
var maxDecimalPlaces = 4;
|
||||
value = Math.round(value * Math.pow(10, maxDecimalPlaces)) / (Math.pow(10, maxDecimalPlaces));
|
||||
}
|
||||
|
||||
var parts = value.toString().split(".");
|
||||
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, this.thousandSeparator);
|
||||
if (parts[1] == 0) {
|
||||
parts.splice(1, 1);
|
||||
|
||||
if (isCurrency) {
|
||||
if (currencyDecimalPlaces === 0) {
|
||||
delete parts[1];
|
||||
} else if (currencyDecimalPlaces) {
|
||||
var decimalPartLength = 0;
|
||||
if (parts.length > 1) {
|
||||
decimalPartLength = parts[1].length;
|
||||
} else {
|
||||
parts[1] = '';
|
||||
}
|
||||
|
||||
if (currencyDecimalPlaces && decimalPartLength < currencyDecimalPlaces) {
|
||||
var limit = currencyDecimalPlaces - decimalPartLength;
|
||||
for (var i = 0; i < limit; i++) {
|
||||
parts[1] += '0';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return parts.join(this.decimalMark);
|
||||
|
||||
var value = parts.join(this.decimalMark);
|
||||
return value;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
|
||||
getLegentColumnNumber: 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());
|
||||
var legendHeight = 0;
|
||||
|
||||
var lineHeight = this.getThemeManager().getParam('dashletChartLegentRowHeight') || 22;
|
||||
if (lineNumber > 0) {
|
||||
legendHeight = lineHeight * lineNumber;
|
||||
}
|
||||
return legendHeight;
|
||||
},
|
||||
|
||||
afterRender: function () {
|
||||
this.$el.closest('.panel-body').css({
|
||||
'overflow-y': 'visible',
|
||||
'overflow-x': 'visible'
|
||||
});
|
||||
this.fetch(function (data) {
|
||||
this.chartData = this.prepareData(data);
|
||||
|
||||
var $container = this.$container = this.$el.find('.chart-container');
|
||||
|
||||
var height = 'calc(100% - 22px)';
|
||||
if (this.chartData.length > 5) {
|
||||
height = 'calc(100% - 42px)';
|
||||
}
|
||||
|
||||
$container.css('height', height);
|
||||
var legendHeight = this.getLegentHeight();
|
||||
var heightCss = 'calc(100% - '+legendHeight.toString()+'px)';
|
||||
$container.css('height', heightCss);
|
||||
|
||||
setTimeout(function () {
|
||||
if (!$container.size() || !$container.is(":visible")) return;
|
||||
this.drow();
|
||||
this.draw();
|
||||
}.bind(this), 1);
|
||||
});
|
||||
},
|
||||
@@ -121,6 +194,9 @@ Espo.define('crm:views/dashlets/abstract/chart', ['views/dashlets/abstract/base'
|
||||
});
|
||||
},
|
||||
|
||||
getDateFilter: function () {
|
||||
return this.getOption('dateFilter') || 'currentYear';
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -33,7 +33,12 @@ Espo.define('crm:views/dashlets/opportunities-by-lead-source', 'crm:views/dashle
|
||||
name: 'OpportunitiesByLeadSource',
|
||||
|
||||
url: function () {
|
||||
return 'Opportunity/action/reportByLeadSource?dateFrom=' + this.getOption('dateFrom') + '&dateTo=' + this.getOption('dateTo');
|
||||
var url = 'Opportunity/action/reportByLeadSource?dateFilter='+ this.getDateFilter();
|
||||
|
||||
if (this.getDateFilter() === 'between') {
|
||||
url += '&dateFrom=' + this.getOption('dateFrom') + '&dateTo=' + this.getOption('dateTo');
|
||||
}
|
||||
return url;
|
||||
},
|
||||
|
||||
prepareData: function (response) {
|
||||
@@ -55,13 +60,13 @@ Espo.define('crm:views/dashlets/opportunities-by-lead-source', 'crm:views/dashle
|
||||
|
||||
setup: function () {
|
||||
this.currency = this.getConfig().get('defaultCurrency');
|
||||
this.currencySymbol = '';
|
||||
this.currencySymbol = this.getMetadata().get(['app', 'currency', 'symbolMap', this.currency]) || '';
|
||||
},
|
||||
|
||||
drow: function () {
|
||||
draw: function () {
|
||||
var self = this;
|
||||
this.flotr.draw(this.$container.get(0), this.chartData, {
|
||||
colors: this.colors,
|
||||
colors: this.colorList,
|
||||
shadowSize: false,
|
||||
pie: {
|
||||
show: true,
|
||||
@@ -69,18 +74,25 @@ Espo.define('crm:views/dashlets/opportunities-by-lead-source', 'crm:views/dashle
|
||||
lineWidth: 1,
|
||||
fillOpacity: 1,
|
||||
sizeRatio: 0.8,
|
||||
labelFormatter: function (total, value) {
|
||||
var percentage = (100 * value / total).toFixed(2);
|
||||
if (percentage < 7) return '';
|
||||
return '<span class="small" style="font-size: 0.8em;color:'+this.textColor+'">'+ percentage.toString() +'%' + '</span>';
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
horizontalLines: false,
|
||||
verticalLines: false,
|
||||
outline: '',
|
||||
color: this.outlineColor
|
||||
tickColor: this.tickColor
|
||||
},
|
||||
yaxis: {
|
||||
showLabels: false,
|
||||
color: this.textColor
|
||||
},
|
||||
xaxis: {
|
||||
showLabels: false,
|
||||
color: this.textColor
|
||||
},
|
||||
legend: {
|
||||
show: false,
|
||||
@@ -88,20 +100,26 @@ Espo.define('crm:views/dashlets/opportunities-by-lead-source', 'crm:views/dashle
|
||||
mouse: {
|
||||
track: true,
|
||||
relative: true,
|
||||
lineColor: this.hoverColor,
|
||||
trackFormatter: function (obj) {
|
||||
return self.formatNumber(obj.y) + ' ' + self.currency;
|
||||
},
|
||||
var value = self.currencySymbol + self.formatNumber(obj.y, true);
|
||||
|
||||
var fraction = obj.fraction || 0;
|
||||
var percentage = (100 * fraction).toFixed(2).toString();
|
||||
|
||||
return (obj.series.label || self.translate('None')) + ':<br>' + value + ' / ' + percentage + '%';
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: true,
|
||||
noColumns: 8,
|
||||
noColumns: this.getLegentColumnNumber(),
|
||||
container: this.$el.find('.legend-container'),
|
||||
labelBoxMargin: 0
|
||||
},
|
||||
labelBoxMargin: 0,
|
||||
labelFormatter: self.labelFormatter.bind(self),
|
||||
labelBoxBorderColor: 'transparent',
|
||||
backgroundOpacity: 0
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -38,7 +38,12 @@ Espo.define('crm:views/dashlets/opportunities-by-stage', 'crm:views/dashlets/abs
|
||||
},
|
||||
|
||||
url: function () {
|
||||
return 'Opportunity/action/reportByStage?dateFrom=' + this.getOption('dateFrom') + '&dateTo=' + this.getOption('dateTo');
|
||||
var url = 'Opportunity/action/reportByStage?dateFilter='+ this.getDateFilter();
|
||||
|
||||
if (this.getDateFilter() === 'between') {
|
||||
url += '&dateFrom=' + this.getOption('dateFrom') + '&dateTo=' + this.getOption('dateTo');
|
||||
}
|
||||
return url;
|
||||
},
|
||||
|
||||
prepareData: function (response) {
|
||||
@@ -60,7 +65,7 @@ Espo.define('crm:views/dashlets/opportunities-by-stage', 'crm:views/dashlets/abs
|
||||
data: [[item.value, d.length - i]],
|
||||
label: this.getLanguage().translateOption(item.stage, 'stage', 'Opportunity'),
|
||||
}
|
||||
if (item.stage == 'Closed Won') {
|
||||
if (item.stagsuccessColore == 'Closed Won') {
|
||||
o.color = this.successColor;
|
||||
}
|
||||
data.push(o);
|
||||
@@ -73,13 +78,13 @@ Espo.define('crm:views/dashlets/opportunities-by-stage', 'crm:views/dashlets/abs
|
||||
|
||||
setup: function () {
|
||||
this.currency = this.getConfig().get('defaultCurrency');
|
||||
this.currencySymbol = '';
|
||||
this.currencySymbol = this.getMetadata().get(['app', 'currency', 'symbolMap', this.currency]) || '';
|
||||
},
|
||||
|
||||
drow: function () {
|
||||
draw: function () {
|
||||
var self = this;
|
||||
this.flotr.draw(this.$container.get(0), this.chartData, {
|
||||
colors: this.colors,
|
||||
colors: this.colorList,
|
||||
shadowSize: false,
|
||||
bars: {
|
||||
show: true,
|
||||
@@ -87,44 +92,53 @@ Espo.define('crm:views/dashlets/opportunities-by-stage', 'crm:views/dashlets/abs
|
||||
shadowSize: 0,
|
||||
lineWidth: 1,
|
||||
fillOpacity: 1,
|
||||
barWidth: 0.5,
|
||||
barWidth: 0.5
|
||||
},
|
||||
grid: {
|
||||
horizontalLines: false,
|
||||
outline: 'sw',
|
||||
color: this.outlineColor
|
||||
color: this.gridColor,
|
||||
tickColor: this.tickColor
|
||||
},
|
||||
yaxis: {
|
||||
min: 0,
|
||||
showLabels: false,
|
||||
color: this.textColor
|
||||
},
|
||||
xaxis: {
|
||||
min: 0,
|
||||
color: this.textColor,
|
||||
tickFormatter: function (value) {
|
||||
if (value != 0) {
|
||||
return self.formatNumber(value) + ' ' + self.currency;
|
||||
if (value == 0) {
|
||||
return '';
|
||||
}
|
||||
if (value % 1 == 0) {
|
||||
return self.currencySymbol + self.formatNumber(Math.floor(value)).toString();
|
||||
}
|
||||
return '';
|
||||
},
|
||||
}
|
||||
},
|
||||
mouse: {
|
||||
track: true,
|
||||
relative: true,
|
||||
position: 's',
|
||||
lineColor: this.hoverColor,
|
||||
trackFormatter: function (obj) {
|
||||
return self.formatNumber(obj.x) + ' ' + self.currency;
|
||||
},
|
||||
var label = (obj.series.label || self.translate('None'));
|
||||
var value = label + ':<br>' + self.currencySymbol + self.formatNumber(obj.x, true);
|
||||
return value;
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: true,
|
||||
noColumns: 5,
|
||||
noColumns: this.getLegentColumnNumber(),
|
||||
container: this.$el.find('.legend-container'),
|
||||
labelBoxMargin: 0
|
||||
},
|
||||
labelBoxMargin: 0,
|
||||
labelFormatter: self.labelFormatter.bind(self),
|
||||
labelBoxBorderColor: 'transparent',
|
||||
backgroundOpacity: 0
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/************************************************************************
|
||||
* 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/dashlets/options/chart', 'views/dashlets/options/base', function (Dep) {
|
||||
|
||||
return Dep.extend({
|
||||
|
||||
setupBeforeFinal: function () {
|
||||
this.listenTo(this.model, 'change:dateFilter', this.controlDateFilter);
|
||||
this.controlDateFilter();
|
||||
},
|
||||
|
||||
controlDateFilter: function () {
|
||||
if (this.model.get('dateFilter') === 'between') {
|
||||
this.showField('dateFrom');
|
||||
this.showField('dateTo');
|
||||
} else {
|
||||
this.hideField('dateFrom');
|
||||
this.hideField('dateTo');
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
@@ -38,17 +38,28 @@ Espo.define('crm:views/dashlets/sales-by-month', 'crm:views/dashlets/abstract/ch
|
||||
},
|
||||
|
||||
url: function () {
|
||||
return 'Opportunity/action/reportSalesByMonth?dateFrom=' + this.getOption('dateFrom') + '&dateTo=' + this.getOption('dateTo');
|
||||
var url = 'Opportunity/action/reportSalesByMonth?dateFilter='+ this.getDateFilter();
|
||||
|
||||
if (this.getDateFilter() === 'between') {
|
||||
url += '&dateFrom=' + this.getOption('dateFrom') + '&dateTo=' + this.getOption('dateTo');
|
||||
}
|
||||
return url;
|
||||
},
|
||||
|
||||
getLegentHeight: function () {
|
||||
return 0;
|
||||
},
|
||||
|
||||
prepareData: function (response) {
|
||||
var months = this.months = Object.keys(response).sort();
|
||||
var monthList = this.monthList = response.keyList;
|
||||
|
||||
var dataMap = response.dataMap || {};
|
||||
|
||||
var values = [];
|
||||
|
||||
for (var month in response) {
|
||||
values.push(response[month]);
|
||||
}
|
||||
monthList.forEach(function (month) {
|
||||
values.push(dataMap[month]);
|
||||
}, this);
|
||||
|
||||
this.chartData = [];
|
||||
|
||||
@@ -71,12 +82,12 @@ Espo.define('crm:views/dashlets/sales-by-month', 'crm:views/dashlets/abstract/ch
|
||||
|
||||
setup: function () {
|
||||
this.currency = this.getConfig().get('defaultCurrency');
|
||||
this.currencySymbol = '';
|
||||
this.currencySymbol = this.getMetadata().get(['app', 'currency', 'symbolMap', this.currency]) || '';
|
||||
|
||||
this.colorBad = this.successColor;
|
||||
},
|
||||
|
||||
drow: function () {
|
||||
draw: function () {
|
||||
var self = this;
|
||||
this.flotr.draw(this.$container.get(0), this.chartData, {
|
||||
shadowSize: false,
|
||||
@@ -86,31 +97,37 @@ Espo.define('crm:views/dashlets/sales-by-month', 'crm:views/dashlets/abstract/ch
|
||||
shadowSize: 0,
|
||||
lineWidth: 1,
|
||||
fillOpacity: 1,
|
||||
barWidth: 0.5,
|
||||
barWidth: 0.5
|
||||
},
|
||||
grid: {
|
||||
horizontalLines: true,
|
||||
verticalLines: false,
|
||||
outline: 'sw',
|
||||
color: this.outlineColor
|
||||
color: this.gridColor,
|
||||
tickColor: this.tickColor
|
||||
},
|
||||
yaxis: {
|
||||
min: 0,
|
||||
showLabels: true,
|
||||
color: this.textColor,
|
||||
tickFormatter: function (value) {
|
||||
if (value == 0) {
|
||||
return '';
|
||||
}
|
||||
return self.formatNumber(value) + ' ' + self.currency;
|
||||
},
|
||||
if (value % 1 == 0) {
|
||||
return self.currencySymbol + self.formatNumber(Math.floor(value)).toString();
|
||||
}
|
||||
return '';
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
min: 0,
|
||||
color: this.textColor,
|
||||
tickFormatter: function (value) {
|
||||
if (value % 1 == 0) {
|
||||
var i = parseInt(value);
|
||||
if (i in self.months) {
|
||||
return moment(self.months[i] + '-01').format('MMM YYYY');
|
||||
if (i in self.monthList) {
|
||||
return moment(self.monthList[i] + '-01').format('MMM YYYY');
|
||||
}
|
||||
}
|
||||
return '';
|
||||
@@ -119,13 +136,17 @@ Espo.define('crm:views/dashlets/sales-by-month', 'crm:views/dashlets/abstract/ch
|
||||
mouse: {
|
||||
track: true,
|
||||
relative: true,
|
||||
lineColor: this.hoverColor,
|
||||
trackFormatter: function (obj) {
|
||||
return self.formatNumber(obj.y) + ' ' + self.currency;
|
||||
},
|
||||
var i = parseInt(obj.x);
|
||||
var value = '';
|
||||
if (i in self.monthList) {
|
||||
value += moment(self.monthList[i] + '-01').format('MMM YYYY') + ':<br>';
|
||||
}
|
||||
return value + self.currencySymbol + self.formatNumber(obj.y, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
})
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -38,7 +38,12 @@ Espo.define('crm:views/dashlets/sales-pipeline', 'crm:views/dashlets/abstract/ch
|
||||
},
|
||||
|
||||
url: function () {
|
||||
return 'Opportunity/action/reportSalesPipeline?dateFrom=' + this.getOption('dateFrom') + '&dateTo=' + this.getOption('dateTo');
|
||||
var url = 'Opportunity/action/reportSalesPipeline?dateFilter='+ this.getDateFilter();
|
||||
|
||||
if (this.getDateFilter() === 'between') {
|
||||
url += '&dateFrom=' + this.getOption('dateFrom') + '&dateTo=' + this.getOption('dateTo');
|
||||
}
|
||||
return url;
|
||||
},
|
||||
|
||||
prepareData: function (response) {
|
||||
@@ -80,63 +85,15 @@ Espo.define('crm:views/dashlets/sales-pipeline', 'crm:views/dashlets/abstract/ch
|
||||
|
||||
setup: function () {
|
||||
this.currency = this.getConfig().get('defaultCurrency');
|
||||
this.currencySymbol = '';
|
||||
|
||||
var data = [
|
||||
{
|
||||
value: 12000,
|
||||
stage: 'Prospecting'
|
||||
},
|
||||
{
|
||||
value: 5050,
|
||||
stage: 'Qualification'
|
||||
},
|
||||
{
|
||||
value: 4050,
|
||||
stage: 'Needs Analysis'
|
||||
},
|
||||
{
|
||||
value: 3230,
|
||||
stage: 'Value Proposition'
|
||||
},
|
||||
{
|
||||
value: 2000,
|
||||
stage: 'Proposal/Price Quote'
|
||||
},
|
||||
{
|
||||
value: 1200.5,
|
||||
stage: 'Negotiation/Review'
|
||||
},
|
||||
{
|
||||
value: 700,
|
||||
stage: 'Closed Won'
|
||||
},
|
||||
];
|
||||
this.currencySymbol = this.getMetadata().get(['app', 'currency', 'symbolMap', this.currency]) || '';
|
||||
|
||||
this.chartData = [];
|
||||
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var item = data[i];
|
||||
var value = item.value;
|
||||
var nextValue = ((i + 1) < data.length) ? data[i + 1].value : value;
|
||||
var o = {
|
||||
data: [[i, value], [i + 1, nextValue]],
|
||||
label: item.stage
|
||||
};
|
||||
|
||||
this.chartData.push(o);
|
||||
}
|
||||
|
||||
this.maxY = 1000;
|
||||
if (data.length) {
|
||||
this.maxY = data[0].value + (data[0].value / 20);
|
||||
}
|
||||
},
|
||||
|
||||
drow: function () {
|
||||
draw: function () {
|
||||
var self = this;
|
||||
|
||||
var colors = Espo.Utils.clone(this.colors);
|
||||
var colors = Espo.Utils.clone(this.colorList);
|
||||
|
||||
this.chartData.forEach(function (item, i) {
|
||||
if (i + 1 > colors.length) {
|
||||
@@ -154,46 +111,51 @@ Espo.define('crm:views/dashlets/sales-pipeline', 'crm:views/dashlets/abstract/ch
|
||||
lines: {
|
||||
show: true,
|
||||
fill: true,
|
||||
fillOpacity: 1,
|
||||
fillOpacity: 1
|
||||
},
|
||||
points: {
|
||||
show: true,
|
||||
show: true
|
||||
},
|
||||
grid: {
|
||||
horizontalLines: false,
|
||||
outline: 'sw',
|
||||
color: this.outlineColor
|
||||
color: this.tickColor,
|
||||
verticalLines: false,
|
||||
outline: 'ew',
|
||||
tickColor: this.tickColor
|
||||
},
|
||||
yaxis: {
|
||||
min: 0,
|
||||
max: this.maxY,
|
||||
showLabels: false,
|
||||
showLabels: false
|
||||
},
|
||||
xaxis: {
|
||||
min: 0,
|
||||
showLabels: false,
|
||||
showLabels: false
|
||||
},
|
||||
mouse: {
|
||||
track: true,
|
||||
relative: true,
|
||||
position: 'ne',
|
||||
lineColor: this.hoverColor,
|
||||
trackFormatter: function (obj) {
|
||||
if (obj.x >= self.chartData.length) {
|
||||
return null;
|
||||
}
|
||||
return self.formatNumber(obj.y) + ' ' + self.currency;
|
||||
},
|
||||
var label = self.chartData[parseInt(obj.x)].label;
|
||||
var label = (label || self.translate('None'));
|
||||
return label + ':<br>' + self.currencySymbol + self.formatNumber(obj.y, true);
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: true,
|
||||
noColumns: 5,
|
||||
noColumns: this.getLegentColumnNumber(),
|
||||
container: this.$el.find('.legend-container'),
|
||||
labelBoxMargin: 0
|
||||
},
|
||||
labelBoxMargin: 0,
|
||||
labelFormatter: self.labelFormatter.bind(self),
|
||||
labelBoxBorderColor: 'transparent',
|
||||
backgroundOpacity: 0
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -19,8 +19,55 @@
|
||||
<% _.each(panel.rows, function (row, rowNumber) { %>
|
||||
<div class="row">
|
||||
<% _.each(row, function (cell, cellNumber) { %>
|
||||
|
||||
<%
|
||||
var spanClassBase;
|
||||
if (columnCount === 1) {
|
||||
spanClassBase = 'col-sm-12';
|
||||
} else if (columnCount === 2) {
|
||||
spanClassBase = 'col-sm-6';
|
||||
} else if (columnCount === 3) {
|
||||
spanClassBase = 'col-sm-4';
|
||||
} else if (columnCount === 4) {
|
||||
spanClassBase = 'col-md-3 col-sm-6';
|
||||
} else {
|
||||
spanClass = 'col-sm-12';
|
||||
}
|
||||
%>
|
||||
<% if (cell != false) { %>
|
||||
<div class="cell<% if (columnCount == 1 || cell.fullWidth) { %> col-sm-12<% } else {%> col-sm-6<% } %> form-group<% if (cell.name) { %>{{#if hiddenFields.<%= cell.name %>}} hidden-cell{{/if}}<% } %>" data-name="<%= cell.name %>">
|
||||
<%
|
||||
var spanClass;
|
||||
if (columnCount === 1 || cell.fullWidth) {
|
||||
spanClass = 'col-sm-12';
|
||||
} else if (columnCount === 2) {
|
||||
if (cell.span === 2) {
|
||||
spanClass = 'col-sm-12';
|
||||
} else {
|
||||
spanClass = 'col-sm-6';
|
||||
}
|
||||
} else if (columnCount === 3) {
|
||||
if (cell.span === 2) {
|
||||
spanClass = 'col-sm-8';
|
||||
} else if (cell.span === 3) {
|
||||
spanClass = 'col-sm-12';
|
||||
} else {
|
||||
spanClass = 'col-sm-4';
|
||||
}
|
||||
} else if (columnCount === 4) {
|
||||
if (cell.span === 2) {
|
||||
spanClass = 'col-sm-6';
|
||||
} else if (cell.span === 3) {
|
||||
spanClass = 'col-sm-9';
|
||||
} else if (cell.span === 4) {
|
||||
spanClass = 'col-sm-12';
|
||||
} else {
|
||||
spanClass = 'col-md-3 col-sm-6';
|
||||
}
|
||||
} else {
|
||||
spanClass = 'col-sm-12';
|
||||
}
|
||||
%>
|
||||
<div class="cell <%= spanClass %> form-group<% if (cell.name) { %>{{#if hiddenFields.<%= cell.name %>}} hidden-cell{{/if}}<% } %>" data-name="<%= cell.name %>">
|
||||
<% if (!cell.noLabel) { %><label class="control-label<% if (cell.name) { %>{{#if hiddenFields.<%= cell.name %>}} hidden{{/if}}<% } %>" data-name="<%= cell.name %>"><span class="label-text"><%
|
||||
if ('customLabel' in cell) {
|
||||
print (cell.customLabel);
|
||||
@@ -37,7 +84,7 @@
|
||||
%></div>
|
||||
</div>
|
||||
<% } else { %>
|
||||
<div class="col-sm-6"></div>
|
||||
<div class="<%= spanClassBase %>"></div>
|
||||
<% } %>
|
||||
<% }); %>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
<div class="col-md-8">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body">
|
||||
<section>
|
||||
<h5>Version {{version}}</h5>
|
||||
<p>
|
||||
Copyright © 2014-2018 EspoCRM: Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko.
|
||||
@@ -39,7 +38,6 @@
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="panel">
|
||||
<div class="panel-body">
|
||||
<div class="panel-body panel-body-form">
|
||||
<div class="cell form-group" data-name="enabled">
|
||||
<label class="control-label" data-name="enabled">{{translate 'enabled' scope='Integration' category='fields'}}</label>
|
||||
<div class="field" data-name="enabled">{{{enabled}}}</div>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="panel">
|
||||
<div class="panel-body">
|
||||
<div class="panel-body panel-body-form">
|
||||
<div class="cell form-group" data-name="enabled">
|
||||
<label class="control-label" data-name="enabled">{{translate 'enabled' scope='Integration' category='fields'}}</label>
|
||||
<div class="field" data-name="enabled">{{{enabled}}}</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<header data-style="{{style}}">
|
||||
<header data-style="{{style}}" data-name="{{name}}">
|
||||
<label data-is-custom="{{#if isCustomLabel}}true{{/if}}">{{label}}</label>
|
||||
<a href="javascript:" data-action="edit-panel-label" class="edit-panel-label"><i class="glyphicon glyphicon-pencil"></i></a>
|
||||
<a href="javascript:" style="float: right;" data-action="removePanel" class="remove-panel" data-number="{{number}}"><i class="glyphicon glyphicon-remove"></i></a>
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
|
||||
<div id="layout-panel-tpl" style="display: none;">
|
||||
<li>
|
||||
<header data-style="<%= style %>">
|
||||
<header data-style="<%= style %>" data-name="<%= name %>">
|
||||
<label data-is-custom="<%= isCustomLabel ? 'true' : '' %>"><%= label %></label>
|
||||
<a href="javascript:" data-action="edit-panel-label" class="edit-panel-label"><i class="glyphicon glyphicon-pencil"></i></a>
|
||||
<a href="javascript:" style="float: right;" data-action="removePanel" class="remove-panel"><i class="glyphicon glyphicon-remove"></i></a>
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div style="margin-bottom: 10px;" class="hidden message-container"><span class="text-success"></span></div>
|
||||
<div class="button-container">
|
||||
<div class="hidden message-container margin-bottom"><span class="text-success"></span></div>
|
||||
<div>
|
||||
<button class="btn btn-default action {{#if cacheIsEnabled}}hidden{{/if}}" data-action="returnToApplication">{{translate 'Return to Application'}}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{{#if value}}
|
||||
<span title="{{value}} {{currencyValue}}">{{value}} {{currencyValue}}</span>
|
||||
{{else}}
|
||||
{{translate 'None'}}
|
||||
{{/if}}
|
||||
@@ -0,0 +1,5 @@
|
||||
{{#if value}}
|
||||
<span title="{{currencySymbol}}{{value}}">{{currencySymbol}}{{value}}</span>
|
||||
{{else}}
|
||||
{{translate 'None'}}
|
||||
{{/if}}
|
||||
@@ -0,0 +1,5 @@
|
||||
{{#if value}}
|
||||
<span title="{{value}} {{currencyValue}}">{{value}} {{currencyValue}}</span>
|
||||
{{else}}
|
||||
{{translate 'None'}}
|
||||
{{/if}}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user