Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e7067447b6 | |||
| 4e8f771ef7 | |||
| 53ecb36705 | |||
| 4faaa2d082 | |||
| 8dda1321b5 | |||
| bb59cda131 | |||
| 0d9f1a225f | |||
| 8518a44935 | |||
| b4a5d28f53 | |||
| aa5427d976 | |||
| 04218e0291 | |||
| c7c6cfe27f | |||
| 98d457dae6 | |||
| 007cf8c2aa | |||
| 8b73912c3d | |||
| 1ac2f489ab | |||
| 773f929b01 | |||
| f3da3e0fb3 | |||
| 0a57e538d5 | |||
| e8dae0cfd1 | |||
| e9c55bbd43 | |||
| 7a98325eae | |||
| b0a811c51d | |||
| a1d920f003 | |||
| 07443e54de | |||
| 717a678c9a | |||
| 73533080f6 | |||
| 1c7a6d9825 | |||
| c553bc8d0a | |||
| 4f2ade5f7f | |||
| b882aad4ab | |||
| 8541c12441 | |||
| 2a967261b3 | |||
| 12e40352fe | |||
| 2b0bde8c0b | |||
| 931e8b934c | |||
| a07cb5ef38 | |||
| 66a6f70a30 | |||
| d7a629c5a3 |
@@ -609,8 +609,6 @@ class Cleanup implements JobDataLess
|
||||
|
||||
$repository->deleteFromDb($entity->getId());
|
||||
|
||||
$query = $this->entityManager->getQueryComposer();
|
||||
|
||||
foreach ($entity->getRelationList() as $relation) {
|
||||
if ($entity->getRelationType($relation) !== Entity::MANY_MANY) {
|
||||
continue;
|
||||
@@ -763,6 +761,15 @@ class Cleanup implements JobDataLess
|
||||
'deleted' => true,
|
||||
];
|
||||
|
||||
if (
|
||||
!$this->entityManager
|
||||
->getDefs()
|
||||
->getEntity($scope)
|
||||
->hasAttribute('deleted')
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->metadata->get(['entityDefs', $scope, 'fields', 'modifiedAt'])) {
|
||||
$whereClause['modifiedAt<'] = $datetime->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT);
|
||||
}
|
||||
|
||||
@@ -162,6 +162,10 @@ class LinkCheck
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->metadata->get(['recordDefs', $entityType, 'relationships', $link, 'linkCheckDisabled'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$foreignEntityType = $defs->getForeignEntityType();
|
||||
|
||||
$foreignEntity = $this->entityManager->getEntityById($foreignEntityType, $id);
|
||||
|
||||
@@ -569,7 +569,10 @@ class Service implements Crud,
|
||||
$list = [];
|
||||
|
||||
foreach ($duplicateList as $e) {
|
||||
$list[] = $e->getValueMap();
|
||||
$list[] = (object) [
|
||||
'id' => $e->getId(),
|
||||
'name' => $e->get('name'),
|
||||
];
|
||||
}
|
||||
|
||||
throw ConflictSilent::createWithBody('duplicate', Json::encode($list));
|
||||
|
||||
@@ -512,7 +512,7 @@ class SearchParams
|
||||
|
||||
unset($where[$i]);
|
||||
}
|
||||
else if ($type == 'textFilter' && $value) {
|
||||
else if ($type === 'textFilter') {
|
||||
$params['textFilter'] = $value;
|
||||
|
||||
unset($where[$i]);
|
||||
|
||||
@@ -208,7 +208,7 @@ class SelectBuilder
|
||||
|
||||
$textFilter = $searchParams->getTextFilter();
|
||||
|
||||
if ($textFilter) {
|
||||
if ($textFilter !== null) {
|
||||
$this->withTextFilter($textFilter);
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
use DateTime;
|
||||
use DateInterval;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Converts a where item to a where clause (for ORM).
|
||||
@@ -1159,7 +1160,6 @@ class ItemGeneralConverter implements ItemConverter
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @return array<string|int, mixed>
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function processLastFiscalQuarter(QueryBuilder $queryBuilder, string $attribute, $value): array
|
||||
{
|
||||
@@ -1203,13 +1203,13 @@ class ItemGeneralConverter implements ItemConverter
|
||||
|
||||
$defs = $this->ormDefs->getEntity($this->entityType)->getRelation($link);
|
||||
|
||||
$key = $defs->getForeignMidKey();
|
||||
$nearKey = $defs->getMidKey();
|
||||
$middleEntityType = ucfirst($defs->getRelationshipName());
|
||||
|
||||
$relationType = $defs->getType();
|
||||
|
||||
if ($relationType == Entity::MANY_MANY) {
|
||||
$key = $defs->getForeignMidKey();
|
||||
$nearKey = $defs->getMidKey();
|
||||
$middleEntityType = ucfirst($defs->getRelationshipName());
|
||||
|
||||
// The foreign table is not joined as it would perform much slower.
|
||||
// Trade off is that if a foreign record is deleted but the middle table
|
||||
// is not yet deleted, it will give a non-actual result.
|
||||
@@ -1229,7 +1229,8 @@ class ItemGeneralConverter implements ItemConverter
|
||||
if (
|
||||
$relationType == Entity::HAS_MANY ||
|
||||
$relationType == Entity::HAS_ONE ||
|
||||
$relationType == Entity::BELONGS_TO
|
||||
$relationType == Entity::BELONGS_TO ||
|
||||
$relationType === Entity::HAS_CHILDREN
|
||||
) {
|
||||
$subQuery = QueryBuilder::create()
|
||||
->select('id')
|
||||
@@ -1241,7 +1242,7 @@ class ItemGeneralConverter implements ItemConverter
|
||||
return ['id=s' => $subQuery];
|
||||
}
|
||||
|
||||
throw new Error("Bad where item. Not supported relation type.");
|
||||
throw new RuntimeException("Bad where item. Not supported relation type.");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1254,13 +1255,13 @@ class ItemGeneralConverter implements ItemConverter
|
||||
|
||||
$defs = $this->ormDefs->getEntity($this->entityType)->getRelation($link);
|
||||
|
||||
$key = $defs->getForeignMidKey();
|
||||
$nearKey = $defs->getMidKey();
|
||||
$middleEntityType = ucfirst($defs->getRelationshipName());
|
||||
|
||||
$relationType = $defs->getType();
|
||||
|
||||
if ($relationType == Entity::MANY_MANY) {
|
||||
$key = $defs->getForeignMidKey();
|
||||
$nearKey = $defs->getMidKey();
|
||||
$middleEntityType = ucfirst($defs->getRelationshipName());
|
||||
|
||||
// The foreign table is not joined as it would perform much slower.
|
||||
// Trade off is that if a foreign record is deleted but the middle table
|
||||
// is not yet deleted, it will give a non-actual result.
|
||||
@@ -1280,7 +1281,8 @@ class ItemGeneralConverter implements ItemConverter
|
||||
if (
|
||||
$relationType == Entity::HAS_MANY ||
|
||||
$relationType == Entity::HAS_ONE ||
|
||||
$relationType == Entity::BELONGS_TO
|
||||
$relationType == Entity::BELONGS_TO ||
|
||||
$relationType == Entity::HAS_CHILDREN
|
||||
) {
|
||||
$subQuery = QueryBuilder::create()
|
||||
->select('id')
|
||||
@@ -1292,7 +1294,7 @@ class ItemGeneralConverter implements ItemConverter
|
||||
return ['id=s' => $subQuery];
|
||||
}
|
||||
|
||||
throw new Error("Bad where item. Not supported relation type.");
|
||||
throw new RuntimeException("Bad where item. Not supported relation type.");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -55,6 +55,19 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"reminders": {
|
||||
"visible": {
|
||||
"conditionGroup": [
|
||||
{
|
||||
"type": "isNotEmpty",
|
||||
"attribute": "assignedUserId",
|
||||
"data": {
|
||||
"field": "assignedUser"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
[
|
||||
{"name":"name", "width": 35,"link":"true", "view": "crm:views/contact/fields/name-for-account"},
|
||||
{"name":"accountRole", "width": 35, "notSortable": true},
|
||||
{"name":"emailAddress"}
|
||||
{
|
||||
"name": "name",
|
||||
"link": "true",
|
||||
"view": "crm:views/contact/fields/name-for-account"
|
||||
},
|
||||
{
|
||||
"name": "accountRole",
|
||||
"width": 28,
|
||||
"notSortable": true
|
||||
},
|
||||
{
|
||||
"name":"emailAddress",
|
||||
"width": 33
|
||||
}
|
||||
]
|
||||
|
||||
@@ -119,9 +119,12 @@ class ConvertService
|
||||
|
||||
if ($rDuplicateList) {
|
||||
foreach ($rDuplicateList as $e) {
|
||||
$item = $e->getValueMap();
|
||||
$item->_entityType = $e->getEntityType();
|
||||
$duplicateList[] = $item;
|
||||
$duplicateList[] = (object) [
|
||||
'id' => $e->getId(),
|
||||
'name' => $e->getName(),
|
||||
'_entityType' => $e->getEntityType(),
|
||||
];
|
||||
|
||||
$skipSave = true;
|
||||
}
|
||||
}
|
||||
@@ -151,10 +154,11 @@ class ConvertService
|
||||
|
||||
if ($rDuplicateList) {
|
||||
foreach ($rDuplicateList as $e) {
|
||||
$item = $e->getValueMap();
|
||||
$item->_entityType = $e->getEntityType();
|
||||
|
||||
$duplicateList[] = $item;
|
||||
$duplicateList[] = (object) [
|
||||
'id' => $e->getId(),
|
||||
'name' => $e->getName(),
|
||||
'_entityType' => $e->getEntityType(),
|
||||
];
|
||||
|
||||
$skipSave = true;
|
||||
}
|
||||
@@ -189,9 +193,11 @@ class ConvertService
|
||||
|
||||
if ($rDuplicateList) {
|
||||
foreach ($rDuplicateList as $e) {
|
||||
$item = $e->getValueMap();
|
||||
$item->_entityType = $e->getEntityType();
|
||||
$duplicateList[] = $item;
|
||||
$duplicateList[] = (object) [
|
||||
'id' => $e->getId(),
|
||||
'name' => $e->getName(),
|
||||
'_entityType' => $e->getEntityType(),
|
||||
];
|
||||
|
||||
$skipSave = true;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"ico": ["image/vnd.microsoft.icon"],
|
||||
"ics": ["text/calendar"],
|
||||
"jar": ["application/java-archive"],
|
||||
"jpeg ": ["image/jpeg"],
|
||||
"jpeg": ["image/jpeg"],
|
||||
"jpg": ["image/jpeg"],
|
||||
"js": ["text/javascript"],
|
||||
"json": ["application/json"],
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"controller": "controllers/record",
|
||||
"searchPanelDisabled": true,
|
||||
"massUpdateDisabled": true,
|
||||
"mergeDisabled": true,
|
||||
"massRemoveDisabled": true,
|
||||
"iconClass": "fas fa-calendar-week",
|
||||
"menu": {
|
||||
"list": {
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
"viewSetupHandlers": {
|
||||
"record/edit": "handlers/working-time-range"
|
||||
},
|
||||
"mergeDisabled": true,
|
||||
"massUpdateDisabled": true,
|
||||
"menu": {
|
||||
"list": {
|
||||
"buttons": [
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
"hasSeconds": true
|
||||
},
|
||||
"user": {
|
||||
"type": "link"
|
||||
"type": "link",
|
||||
"view": "views/fields/user"
|
||||
},
|
||||
"userType": {
|
||||
"type": "foreign",
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
},
|
||||
"user": {
|
||||
"type": "link",
|
||||
"readOnly": true
|
||||
"readOnly": true,
|
||||
"view": "views/fields/user"
|
||||
},
|
||||
"portal": {
|
||||
"type": "link",
|
||||
|
||||
@@ -5,10 +5,18 @@
|
||||
],
|
||||
"fields": {
|
||||
"from": {
|
||||
"type": "currency"
|
||||
"type": "currency",
|
||||
"layoutAvailabilityList": [
|
||||
"filters",
|
||||
"massUpdate"
|
||||
]
|
||||
},
|
||||
"to": {
|
||||
"type": "currency"
|
||||
"type": "currency",
|
||||
"layoutAvailabilityList": [
|
||||
"filters",
|
||||
"massUpdate"
|
||||
]
|
||||
}
|
||||
},
|
||||
"naming": "prefix",
|
||||
|
||||
@@ -5,10 +5,18 @@
|
||||
],
|
||||
"fields": {
|
||||
"from": {
|
||||
"type": "float"
|
||||
"type": "float",
|
||||
"layoutAvailabilityList": [
|
||||
"filters",
|
||||
"massUpdate"
|
||||
]
|
||||
},
|
||||
"to": {
|
||||
"type": "float"
|
||||
"type": "float",
|
||||
"layoutAvailabilityList": [
|
||||
"filters",
|
||||
"massUpdate"
|
||||
]
|
||||
}
|
||||
},
|
||||
"naming": "prefix",
|
||||
|
||||
@@ -5,10 +5,18 @@
|
||||
],
|
||||
"fields": {
|
||||
"from": {
|
||||
"type": "int"
|
||||
"type": "int",
|
||||
"layoutAvailabilityList": [
|
||||
"filters",
|
||||
"massUpdate"
|
||||
]
|
||||
},
|
||||
"to": {
|
||||
"type": "int"
|
||||
"type": "int",
|
||||
"layoutAvailabilityList": [
|
||||
"filters",
|
||||
"massUpdate"
|
||||
]
|
||||
}
|
||||
},
|
||||
"naming": "prefix",
|
||||
|
||||
@@ -624,7 +624,7 @@ class EntityManager
|
||||
$this->fileManager->removeInDir("custom/Espo/Custom/Resources/layouts/{$normalizedName}");
|
||||
$this->fileManager->removeDir("custom/Espo/Custom/Resources/layouts/{$normalizedName}");
|
||||
|
||||
$languageList = $this->config->get('languageList', []);
|
||||
$languageList = $this->metadata->get(['app', 'language', 'list'], []);
|
||||
|
||||
foreach ($languageList as $language) {
|
||||
$filePath = 'custom/Espo/Custom/Resources/i18n/' . $language . '/' . $normalizedName . '.json';
|
||||
|
||||
@@ -52,7 +52,7 @@ class DompdfInitializer
|
||||
$pdf = new Dompdf($options);
|
||||
|
||||
$size = $template->getPageFormat() === Template::PAGE_FORMAT_CUSTOM ?
|
||||
[$template->getPageWidth(), $template->getPageHeight()] :
|
||||
[0.0, 0.0, $template->getPageWidth(), $template->getPageHeight()] :
|
||||
$template->getPageFormat();
|
||||
|
||||
$orientation = $template->getPageOrientation() === Template::PAGE_ORIENTATION_PORTRAIT ?
|
||||
|
||||
@@ -1 +1 @@
|
||||
{{#if accountIsInactive}}<del>{{/if}}{{value}}{{#if accountIsInactive}}</del>{{/if}}
|
||||
{{#if accountIsInactive}}<del>{{/if}}<span title="{{value}}">{{value}}</span>{{#if accountIsInactive}}</del>{{/if}}
|
||||
|
||||
@@ -28,31 +28,48 @@
|
||||
|
||||
define('crm:view-setup-handlers/document/record-list-drag-n-drop', [], function () {
|
||||
|
||||
var Handler = function (view) {
|
||||
let Handler = function (view) {
|
||||
this.view = view;
|
||||
};
|
||||
|
||||
_.extend(Handler.prototype, {
|
||||
|
||||
process: function () {
|
||||
this.listenTo(this.view, 'after:render', function () {
|
||||
this.initDragDrop();
|
||||
}, this);
|
||||
this.listenTo(this.view, 'after:render', () => this.initDragDrop());
|
||||
this.listenTo(this.view, 'remove', () => this.disable());
|
||||
},
|
||||
|
||||
disable: function () {
|
||||
let $el = this.view.$el.parent();
|
||||
/** @type {Element} */
|
||||
let el = $el.get(0);
|
||||
|
||||
$el.off('drop');
|
||||
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.onDragoverBind) {
|
||||
return;
|
||||
}
|
||||
|
||||
el.removeEventListener('dragover', this.onDragoverBind);
|
||||
el.removeEventListener('dragenter', this.onDragenterBind);
|
||||
el.removeEventListener('dragleave', this.onDragleaveBind);
|
||||
},
|
||||
|
||||
initDragDrop: function () {
|
||||
var $el = this.view.$el.parent();
|
||||
this.disable();
|
||||
|
||||
$el.off('drop');
|
||||
$el.off('dragover');
|
||||
$el.off('dragleave');
|
||||
let $el = this.view.$el.parent();
|
||||
let el = $el.get(0);
|
||||
|
||||
$el.on('drop', function (e) {
|
||||
$el.on('drop', e => {
|
||||
e.preventDefault();
|
||||
|
||||
e.stopPropagation();
|
||||
|
||||
var e = e.originalEvent;
|
||||
e = e.originalEvent;
|
||||
|
||||
if (
|
||||
e.dataTransfer &&
|
||||
@@ -68,65 +85,31 @@ define('crm:view-setup-handlers/document/record-list-drag-n-drop', [], function
|
||||
}
|
||||
|
||||
this.removeDrop($el);
|
||||
}.bind(this));
|
||||
|
||||
$el.get(0).addEventListener('dragover', function (e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
|
||||
this.dropEntered = false;
|
||||
|
||||
$el.get(0).addEventListener('dragenter', function (e) {
|
||||
e.preventDefault();
|
||||
this.onDragoverBind = this.onDragover.bind(this);
|
||||
this.onDragenterBind = this.onDragenter.bind(this);
|
||||
this.onDragleaveBind = this.onDragleave.bind(this);
|
||||
|
||||
if (!e.dataTransfer.types || !e.dataTransfer.types.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!~e.dataTransfer.types.indexOf('Files')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.dropEntered) {
|
||||
this.renderDrop();
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
$el.get(0).addEventListener('dragleave', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!this.dropEntered) {
|
||||
return;
|
||||
}
|
||||
|
||||
var fromElement = e.fromElement || e.relatedTarget;
|
||||
|
||||
if (fromElement && $.contains($el.get(0), fromElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
fromElement &&
|
||||
fromElement.parentNode &&
|
||||
fromElement.parentNode.toString() === '[object ShadowRoot]'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.removeDrop();
|
||||
|
||||
}.bind(this));
|
||||
el.addEventListener('dragover', this.onDragoverBind);
|
||||
el.addEventListener('dragenter', this.onDragenterBind);
|
||||
el.addEventListener('dragleave', this.onDragleaveBind);
|
||||
},
|
||||
|
||||
renderDrop: function () {
|
||||
this.dropEntered = true;
|
||||
|
||||
let $backdrop =
|
||||
$('<div class="dd-backdrop" />')
|
||||
$('<div class="dd-backdrop">')
|
||||
.css('pointer-events', 'none')
|
||||
.html(
|
||||
'<span class="fas fa-paperclip"></span> ' +
|
||||
this.view.getLanguage().translate('Create Document', 'labels', 'Document')
|
||||
.append('<span class="fas fa-paperclip"></span>')
|
||||
.append(' ')
|
||||
.append(
|
||||
$('<span>')
|
||||
.text(this.view.getLanguage().translate('Create Document', 'labels', 'Document'))
|
||||
);
|
||||
|
||||
this.view.$el.append($backdrop);
|
||||
@@ -141,31 +124,84 @@ define('crm:view-setup-handlers/document/record-list-drag-n-drop', [], function
|
||||
create: function (file) {
|
||||
this.view
|
||||
.actionQuickCreate()
|
||||
.then(
|
||||
function (view) {
|
||||
var fileView = view.getRecordView().getFieldView('file');
|
||||
.then(view => {
|
||||
let fileView = view.getRecordView().getFieldView('file');
|
||||
|
||||
if (!fileView) {
|
||||
var msg = "No 'file' field on the layout.";
|
||||
if (!fileView) {
|
||||
let msg = "No 'file' field on the layout.";
|
||||
|
||||
Espo.Ui.error(msg);
|
||||
console.error(msg);
|
||||
Espo.Ui.error(msg);
|
||||
console.error(msg);
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (fileView.isRendered()) {
|
||||
fileView.uploadFile(file);
|
||||
if (fileView.isRendered()) {
|
||||
fileView.uploadFile(file);
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.listenToOnce(fileView, 'after:render', function () {
|
||||
fileView.uploadFile(file);
|
||||
});
|
||||
}.bind(this)
|
||||
);
|
||||
this.listenToOnce(fileView, 'after:render', () => {
|
||||
fileView.uploadFile(file);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {DragEvent} e
|
||||
*/
|
||||
onDragover: function (e) {
|
||||
e.preventDefault();
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {DragEvent} e
|
||||
*/
|
||||
onDragenter: function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!e.dataTransfer.types || !e.dataTransfer.types.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!~e.dataTransfer.types.indexOf('Files')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.dropEntered) {
|
||||
this.renderDrop();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {DragEvent} e
|
||||
*/
|
||||
onDragleave: function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!this.dropEntered) {
|
||||
return;
|
||||
}
|
||||
|
||||
let fromElement = e.fromElement || e.relatedTarget;
|
||||
|
||||
if (
|
||||
fromElement &&
|
||||
$.contains(this.view.$el.parent().get(0), fromElement)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
fromElement &&
|
||||
fromElement.parentNode &&
|
||||
fromElement.parentNode.toString() === '[object ShadowRoot]'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.removeDrop();
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ define(() => {
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
if (!this.acl.checkScope(model.entityType, 'read')) {
|
||||
if (model && !this.acl.checkScope(model.entityType, 'read')) {
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
|
||||
@@ -707,7 +707,7 @@ function (marked, DOMPurify, /** typeof Handlebars */Handlebars) {
|
||||
|
||||
text = text.replace(
|
||||
/<a href="mailto:(.*)"/gm,
|
||||
'<a type="button" class="selectable" data-email-address="$1" data-action="mailTo"'
|
||||
'<a role="button" class="selectable" data-email-address="$1" data-action="mailTo"'
|
||||
);
|
||||
|
||||
return new Handlebars.SafeString(text);
|
||||
|
||||
@@ -68,6 +68,8 @@ define('views/email/list', ['views/list'], function (Dep) {
|
||||
'drafts',
|
||||
],
|
||||
|
||||
stickableTop: null,
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@@ -299,6 +301,29 @@ define('views/email/list', ['views/list'], function (Dep) {
|
||||
return data;
|
||||
},
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
createSearchView: function () {
|
||||
/** @type {Promise<module:view.Class>}*/
|
||||
let promise = Dep.prototype.createSearchView.call(this);
|
||||
|
||||
promise.then(view => {
|
||||
this.listenTo(view, 'update-ui', () => {
|
||||
this.stickableTop = null;
|
||||
|
||||
setTimeout(() => {
|
||||
$(window).trigger('scroll')
|
||||
|
||||
// If search fields are not yet rendered, the value may be wrong.
|
||||
this.stickableTop = null;
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
|
||||
return promise;
|
||||
},
|
||||
|
||||
initEmailShortcuts: function () {
|
||||
this.shortcutKeys['Control+Delete'] = e => {
|
||||
if (!this.hasSelectedRecords()) {
|
||||
@@ -587,9 +612,15 @@ define('views/email/list', ['views/list'], function (Dep) {
|
||||
return value - offset;
|
||||
};
|
||||
|
||||
let start = getOffsetTop($list);
|
||||
this.stickableTop = getOffsetTop($list);
|
||||
|
||||
let control = () => {
|
||||
let start = this.stickableTop;
|
||||
|
||||
if (start === null) {
|
||||
start = this.stickableTop = getOffsetTop($list);
|
||||
}
|
||||
|
||||
let scrollTop = $window.scrollTop();
|
||||
|
||||
if (scrollTop <= start || isSmallScreen) {
|
||||
@@ -606,17 +637,18 @@ define('views/email/list', ['views/list'], function (Dep) {
|
||||
}
|
||||
|
||||
if (scrollTop > start) {
|
||||
let maxHeight = $window.height() - start - bottomSpaceHeight;
|
||||
|
||||
let scroll = $window.scrollTop() - start;
|
||||
|
||||
$container
|
||||
.addClass('sticked')
|
||||
.width($left.outerWidth(true))
|
||||
.scrollTop(scroll)
|
||||
.css({
|
||||
maxHeight: maxHeight,
|
||||
});
|
||||
.scrollTop(scroll);
|
||||
|
||||
let topStickPosition = parseInt(window.getComputedStyle($container.get(0)).top);
|
||||
|
||||
let maxHeight = $window.height() - topStickPosition - bottomSpaceHeight;
|
||||
|
||||
$container.css({maxHeight: maxHeight});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -155,56 +155,7 @@ function (Dep, /** module:ui/select*/Select) {
|
||||
},
|
||||
|
||||
formatNumber: function (value) {
|
||||
if (this.isReadMode()) {
|
||||
return this.formatNumberDetail(value);
|
||||
}
|
||||
|
||||
return this.formatNumberEdit(value);
|
||||
},
|
||||
|
||||
formatNumberEdit: function (value) {
|
||||
let currencyDecimalPlaces = this.decimalPlaces;
|
||||
|
||||
if (value !== null) {
|
||||
var parts = value.toString().split(".");
|
||||
|
||||
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, this.thousandSeparator);
|
||||
|
||||
if (parts.length > 1) {
|
||||
if (
|
||||
currencyDecimalPlaces &&
|
||||
parts[1].length < currencyDecimalPlaces
|
||||
) {
|
||||
var limit = currencyDecimalPlaces - parts[1].length;
|
||||
|
||||
for (var i = 0; i < limit; i++) {
|
||||
parts[1] += '0';
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
this.params.decimal &&
|
||||
currencyDecimalPlaces &&
|
||||
parts[1].length > currencyDecimalPlaces
|
||||
) {
|
||||
let i = parts[1].length - 1;
|
||||
|
||||
while (i >= currencyDecimalPlaces) {
|
||||
if (parts[1][i] !== '0') {
|
||||
break;
|
||||
}
|
||||
|
||||
i--;
|
||||
}
|
||||
|
||||
parts[1] = parts[1].substring(0, i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return parts.join(this.decimalMark);
|
||||
}
|
||||
|
||||
return '';
|
||||
return this.formatNumberDetail(value);
|
||||
},
|
||||
|
||||
formatNumberDetail: function (value) {
|
||||
|
||||
@@ -60,7 +60,7 @@ define('views/fields/file', ['views/fields/link', 'helpers/file-upload'], functi
|
||||
|
||||
searchTypeList: ['isNotEmpty', 'isEmpty'],
|
||||
|
||||
ROW_HEIGHT: 35,
|
||||
ROW_HEIGHT: 37,
|
||||
|
||||
events: {
|
||||
'click a.remove-attachment': function (e) {
|
||||
|
||||
@@ -98,23 +98,7 @@ define('views/fields/float', ['views/fields/int'], function (Dep) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (this.isReadMode()) {
|
||||
return this.formatNumberDetail(value);
|
||||
}
|
||||
|
||||
return this.formatNumberEdit(value);
|
||||
},
|
||||
|
||||
formatNumberEdit: function (value) {
|
||||
if (value === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let parts = value.toString().split(".");
|
||||
|
||||
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, this.thousandSeparator);
|
||||
|
||||
return parts.join(this.decimalMark);
|
||||
return this.formatNumberDetail(value);
|
||||
},
|
||||
|
||||
formatNumberDetail: function (value) {
|
||||
|
||||
@@ -99,8 +99,16 @@ define('views/fields/int', ['views/fields/base', 'lib!autonumeric'], function (D
|
||||
* @protected
|
||||
*/
|
||||
setupAutoNumericOptions: function () {
|
||||
let separator = (!this.disableFormatting ? this.thousandSeparator : null) || '';
|
||||
let decimalCharacter = '.';
|
||||
|
||||
if (separator === '.') {
|
||||
decimalCharacter = ',';
|
||||
}
|
||||
|
||||
this.autoNumericOptions = {
|
||||
digitGroupSeparator: (!this.disableFormatting ? this.thousandSeparator : null) || '',
|
||||
digitGroupSeparator: separator,
|
||||
decimalCharacter: decimalCharacter,
|
||||
modifyValueOnWheel: false,
|
||||
decimalPlaces: 0,
|
||||
selectOnFocus: false,
|
||||
@@ -162,6 +170,10 @@ define('views/fields/int', ['views/fields/base', 'lib!autonumeric'], function (D
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isEditMode()) {
|
||||
data.value = this.model.get(this.name);
|
||||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
@@ -176,11 +188,7 @@ define('views/fields/int', ['views/fields/base', 'lib!autonumeric'], function (D
|
||||
return value;
|
||||
}
|
||||
|
||||
if (this.isReadMode()) {
|
||||
return this.formatNumberDetail(value);
|
||||
}
|
||||
|
||||
return this.formatNumberEdit(value);
|
||||
return this.formatNumberDetail(value);
|
||||
},
|
||||
|
||||
formatNumberDetail: function (value) {
|
||||
@@ -195,18 +203,6 @@ define('views/fields/int', ['views/fields/base', 'lib!autonumeric'], function (D
|
||||
return stringValue;
|
||||
},
|
||||
|
||||
formatNumberEdit: function (value) {
|
||||
if (value === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let stringValue = value.toString();
|
||||
|
||||
stringValue = stringValue.replace(/\B(?=(\d{3})+(?!\d))/g, this.thousandSeparator);
|
||||
|
||||
return stringValue;
|
||||
},
|
||||
|
||||
setupSearch: function () {
|
||||
this.events = _.extend({
|
||||
'change select.search-type': (e) => {
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
define('views/fields/range-currency', ['views/fields/range-float'], function (Dep, Float) {
|
||||
define('views/fields/range-currency',
|
||||
['views/fields/range-float', 'views/fields/currency', 'ui/select'], function (Dep, Currency, Select) {
|
||||
|
||||
return Dep.extend({
|
||||
|
||||
@@ -55,46 +56,74 @@ define('views/fields/range-currency', ['views/fields/range-float'], function (De
|
||||
|
||||
this.currencyField = this.name + 'Currency';
|
||||
this.currencyList = this.getConfig().get('currencyList') || ['USD'];
|
||||
this.decimalPlaces = this.getConfig().get('currencyDecimalPlaces');
|
||||
},
|
||||
|
||||
setupAutoNumericOptions: function () {
|
||||
this.autoNumericOptions = {
|
||||
digitGroupSeparator: this.thousandSeparator || '',
|
||||
decimalCharacter: this.decimalMark,
|
||||
modifyValueOnWheel: false,
|
||||
selectOnFocus: false,
|
||||
decimalPlaces: this.decimalPlaces,
|
||||
allowDecimalPadding: true,
|
||||
showWarnings: false,
|
||||
formulaMode: true,
|
||||
};
|
||||
|
||||
if (this.decimalPlaces === null) {
|
||||
this.autoNumericOptions.decimalPlaces = this.decimalPlacesRawValue;
|
||||
this.autoNumericOptions.decimalPlacesRawValue = this.decimalPlacesRawValue;
|
||||
this.autoNumericOptions.allowDecimalPadding = false;
|
||||
}
|
||||
},
|
||||
|
||||
afterRender: function () {
|
||||
Dep.prototype.afterRender.call(this);
|
||||
|
||||
if (this.mode === 'edit') {
|
||||
if (this.mode === this.MODE_EDIT) {
|
||||
this.$currency = this.$el.find('[data-name="' + this.currencyField + '"]');
|
||||
|
||||
Select.init(this.$currency);
|
||||
}
|
||||
},
|
||||
|
||||
formatNumber: function (value) {
|
||||
return Currency.prototype.formatNumberDetail.call(this, value);
|
||||
},
|
||||
|
||||
getValueForDisplay: function () {
|
||||
var fromValue = this.model.get(this.fromField);
|
||||
var toValue = this.model.get(this.toField);
|
||||
let fromValue = this.model.get(this.fromField);
|
||||
let toValue = this.model.get(this.toField);
|
||||
|
||||
fromValue = isNaN(fromValue) ? null : fromValue;
|
||||
toValue = isNaN(toValue) ? null : toValue;
|
||||
|
||||
var currencyValue = this.model.get(this.fromCurrencyField) ||
|
||||
let currencyValue = this.model.get(this.fromCurrencyField) ||
|
||||
this.model.get(this.toCurrencyField);
|
||||
|
||||
let symbol = this.getMetadata().get(['app', 'currency', 'symbolMap', currencyValue]) || currencyValue;
|
||||
|
||||
if (fromValue !== null && toValue !== null) {
|
||||
return this.formatNumber(fromValue) + ' – ' +
|
||||
this.formatNumber(toValue) + ' ' + currencyValue + '';
|
||||
this.formatNumber(toValue) + ' ' + symbol + '';
|
||||
}
|
||||
else if (fromValue) {
|
||||
return '>= ' + this.formatNumber(fromValue) + ' '+currencyValue+'';
|
||||
|
||||
if (fromValue) {
|
||||
return '>= ' + this.formatNumber(fromValue) + ' ' + symbol+'';
|
||||
}
|
||||
else if (toValue) {
|
||||
return '<= ' + this.formatNumber(toValue) + ' '+currencyValue+'';
|
||||
}
|
||||
else {
|
||||
return this.translate('None');
|
||||
|
||||
if (toValue) {
|
||||
return '<= ' + this.formatNumber(toValue) + ' ' + symbol+'';
|
||||
}
|
||||
|
||||
return this.translate('None');
|
||||
},
|
||||
|
||||
fetch: function () {
|
||||
var data = Dep.prototype.fetch.call(this);
|
||||
|
||||
var currencyValue = this.$currency.val();
|
||||
|
||||
let currencyValue = this.$currency.val();
|
||||
|
||||
if (data[this.fromField] !== null) {
|
||||
data[this.fromCurrencyField] = currencyValue;
|
||||
|
||||
@@ -34,10 +34,26 @@ define('views/fields/range-float', ['views/fields/range-int', 'views/fields/floa
|
||||
|
||||
validations: ['required', 'float', 'range', 'order'],
|
||||
|
||||
decimalPlacesRawValue: 10,
|
||||
|
||||
setupAutoNumericOptions: function () {
|
||||
this.autoNumericOptions = {
|
||||
digitGroupSeparator: this.thousandSeparator || '',
|
||||
decimalCharacter: this.decimalMark,
|
||||
modifyValueOnWheel: false,
|
||||
selectOnFocus: false,
|
||||
decimalPlaces: this.decimalPlacesRawValue,
|
||||
decimalPlacesRawValue: this.decimalPlacesRawValue,
|
||||
allowDecimalPadding: false,
|
||||
showWarnings: false,
|
||||
formulaMode: true,
|
||||
};
|
||||
},
|
||||
|
||||
validateFloat: function () {
|
||||
var validate = (name) => {
|
||||
let validate = (name) => {
|
||||
if (isNaN(this.model.get(name))) {
|
||||
var msg = this.translate('fieldShouldBeFloat', 'messages')
|
||||
let msg = this.translate('fieldShouldBeFloat', 'messages')
|
||||
.replace('{field}', this.getLabelText());
|
||||
|
||||
this.showValidationMessage(msg, '[data-name="'+name+'"]');
|
||||
@@ -46,7 +62,7 @@ define('views/fields/range-float', ['views/fields/range-int', 'views/fields/floa
|
||||
}
|
||||
};
|
||||
|
||||
var result = false;
|
||||
let result = false;
|
||||
|
||||
result = validate(this.fromField) || result;
|
||||
result = validate(this.toField) || result;
|
||||
@@ -59,16 +75,8 @@ define('views/fields/range-float', ['views/fields/range-int', 'views/fields/floa
|
||||
},
|
||||
|
||||
formatNumber: function (value) {
|
||||
return Float.prototype.formatNumber.call(this, value);
|
||||
},
|
||||
|
||||
formatNumberDetail: function (value) {
|
||||
return Float.prototype.formatNumberDetail.call(this, value);
|
||||
},
|
||||
|
||||
formatNumberEdit: function (value) {
|
||||
return Float.prototype.formatNumberEdit.call(this, value);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -26,22 +26,21 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
define('views/fields/range-int', ['views/fields/base', 'views/fields/int'], function (Dep, Int) {
|
||||
define('views/fields/range-int',
|
||||
['views/fields/base', 'views/fields/int', 'lib!autonumeric'], function (Dep, Int, AutoNumeric) {
|
||||
|
||||
return Dep.extend({
|
||||
|
||||
type: 'rangeInt',
|
||||
|
||||
listTemplate: 'fields/range-int/detail',
|
||||
|
||||
detailTemplate: 'fields/range-int/detail',
|
||||
|
||||
editTemplate: 'fields/range-int/edit',
|
||||
|
||||
validations: ['required', 'int', 'range', 'order'],
|
||||
|
||||
data: function () {
|
||||
var data = Dep.prototype.data.call(this);
|
||||
let data = Dep.prototype.data.call(this);
|
||||
|
||||
data.ucName = Espo.Utils.upperCaseFirst(this.name);
|
||||
data.fromValue = this.model.get(this.fromField);
|
||||
@@ -60,8 +59,8 @@ define('views/fields/range-int', ['views/fields/base', 'views/fields/int'], func
|
||||
},
|
||||
|
||||
getValueForDisplay: function () {
|
||||
var fromValue = this.model.get(this.fromField);
|
||||
var toValue = this.model.get(this.toField);
|
||||
let fromValue = this.model.get(this.fromField);
|
||||
let toValue = this.model.get(this.toField);
|
||||
|
||||
fromValue = isNaN(fromValue) ? null : fromValue;
|
||||
toValue = isNaN(toValue) ? null : toValue;
|
||||
@@ -100,10 +99,37 @@ define('views/fields/range-int', ['views/fields/base', 'views/fields/int'], func
|
||||
}
|
||||
},
|
||||
|
||||
setupFinal: function () {
|
||||
Dep.prototype.setupFinal.call(this);
|
||||
|
||||
this.setupAutoNumericOptions();
|
||||
},
|
||||
|
||||
/**
|
||||
* @protected
|
||||
*/
|
||||
setupAutoNumericOptions: function () {
|
||||
let separator = (!this.disableFormatting ? this.thousandSeparator : null) || '';
|
||||
let decimalCharacter = '.';
|
||||
|
||||
if (separator === '.') {
|
||||
decimalCharacter = ',';
|
||||
}
|
||||
|
||||
this.autoNumericOptions = {
|
||||
digitGroupSeparator: separator,
|
||||
decimalCharacter: decimalCharacter,
|
||||
modifyValueOnWheel: false,
|
||||
decimalPlaces: 0,
|
||||
selectOnFocus: false,
|
||||
formulaMode: true,
|
||||
};
|
||||
},
|
||||
|
||||
afterRender: function () {
|
||||
Dep.prototype.afterRender.call(this);
|
||||
|
||||
if (this.mode === 'edit') {
|
||||
if (this.mode === this.MODE_EDIT) {
|
||||
this.$from = this.$el.find('[data-name="' + this.fromField + '"]');
|
||||
this.$to = this.$el.find('[data-name="' + this.toField + '"]');
|
||||
|
||||
@@ -114,6 +140,11 @@ define('views/fields/range-int', ['views/fields/base', 'views/fields/int'], func
|
||||
this.$to.on('change', () => {
|
||||
this.trigger('change');
|
||||
});
|
||||
|
||||
if (this.autoNumericOptions) {
|
||||
this.autoNumericInstance1 = new AutoNumeric(this.$from.get(0), this.autoNumericOptions);
|
||||
this.autoNumericInstance2 = new AutoNumeric(this.$to.get(0), this.autoNumericOptions);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -131,7 +162,7 @@ define('views/fields/range-int', ['views/fields/base', 'views/fields/int'], func
|
||||
}
|
||||
};
|
||||
|
||||
var result = false;
|
||||
let result = false;
|
||||
|
||||
result = validate(this.fromField) || result;
|
||||
result = validate(this.toField) || result;
|
||||
@@ -140,7 +171,7 @@ define('views/fields/range-int', ['views/fields/base', 'views/fields/int'], func
|
||||
},
|
||||
|
||||
validateInt: function () {
|
||||
var validate = (name) => {
|
||||
let validate = (name) => {
|
||||
if (isNaN(this.model.get(name))) {
|
||||
var msg = this.translate('fieldShouldBeInt', 'messages')
|
||||
.replace('{field}', this.getLabelText());
|
||||
@@ -151,7 +182,7 @@ define('views/fields/range-int', ['views/fields/base', 'views/fields/int'], func
|
||||
}
|
||||
};
|
||||
|
||||
var result = false;
|
||||
let result = false;
|
||||
|
||||
result = validate(this.fromField) || result;
|
||||
result = validate(this.toField) || result;
|
||||
@@ -160,7 +191,7 @@ define('views/fields/range-int', ['views/fields/base', 'views/fields/int'], func
|
||||
},
|
||||
|
||||
validateRange: function () {
|
||||
var validate = (name) => {
|
||||
let validate = (name) => {
|
||||
var value = this.model.get(name);
|
||||
|
||||
if (value === null) {
|
||||
@@ -172,7 +203,7 @@ define('views/fields/range-int', ['views/fields/base', 'views/fields/int'], func
|
||||
|
||||
if (minValue !== null && maxValue !== null) {
|
||||
if (value < minValue || value > maxValue ) {
|
||||
var msg = this.translate('fieldShouldBeBetween', 'messages')
|
||||
let msg = this.translate('fieldShouldBeBetween', 'messages')
|
||||
.replace('{field}', this.translate(name, 'fields', this.model.name))
|
||||
.replace('{min}', minValue)
|
||||
.replace('{max}', maxValue);
|
||||
@@ -184,7 +215,7 @@ define('views/fields/range-int', ['views/fields/base', 'views/fields/int'], func
|
||||
} else {
|
||||
if (minValue !== null) {
|
||||
if (value < minValue) {
|
||||
var msg = this.translate('fieldShouldBeLess', 'messages')
|
||||
let msg = this.translate('fieldShouldBeLess', 'messages')
|
||||
.replace('{field}', this.translate(name, 'fields', this.model.name))
|
||||
.replace('{value}', minValue);
|
||||
|
||||
@@ -194,7 +225,7 @@ define('views/fields/range-int', ['views/fields/base', 'views/fields/int'], func
|
||||
}
|
||||
} else if (maxValue !== null) {
|
||||
if (value > maxValue) {
|
||||
var msg = this.translate('fieldShouldBeGreater', 'messages')
|
||||
let msg = this.translate('fieldShouldBeGreater', 'messages')
|
||||
.replace('{field}', this.translate(name, 'fields', this.model.name))
|
||||
.replace('{value}', maxValue);
|
||||
|
||||
@@ -206,7 +237,7 @@ define('views/fields/range-int', ['views/fields/base', 'views/fields/int'], func
|
||||
}
|
||||
};
|
||||
|
||||
var result = false;
|
||||
let result = false;
|
||||
|
||||
result = validate(this.fromField) || result;
|
||||
result = validate(this.toField) || result;
|
||||
@@ -215,12 +246,12 @@ define('views/fields/range-int', ['views/fields/base', 'views/fields/int'], func
|
||||
},
|
||||
|
||||
validateOrder: function () {
|
||||
var fromValue = this.model.get(this.fromField);
|
||||
var toValue = this.model.get(this.toField);
|
||||
let fromValue = this.model.get(this.fromField);
|
||||
let toValue = this.model.get(this.toField);
|
||||
|
||||
if (fromValue !== null && toValue !== null) {
|
||||
if (fromValue > toValue) {
|
||||
var msg = this.translate('fieldShouldBeGreater', 'messages')
|
||||
let msg = this.translate('fieldShouldBeGreater', 'messages')
|
||||
.replace('{field}', this.translate(this.toField, 'fields', this.model.name))
|
||||
.replace('{value}', this.translate(this.fromField, 'fields', this.model.name));
|
||||
|
||||
@@ -241,11 +272,11 @@ define('views/fields/range-int', ['views/fields/base', 'views/fields/int'], func
|
||||
},
|
||||
|
||||
formatNumber: function (value) {
|
||||
return value;
|
||||
return Int.prototype.formatNumberDetail.call(this, value);
|
||||
},
|
||||
|
||||
fetch: function (form) {
|
||||
var data = {};
|
||||
fetch: function () {
|
||||
let data = {};
|
||||
|
||||
data[this.fromField] = this.parse(this.$from.val().trim());
|
||||
data[this.toField] = this.parse(this.$to.val().trim());
|
||||
|
||||
@@ -36,17 +36,15 @@ define('views/fields/user',[ 'views/fields/link'], function (Dep) {
|
||||
Dep.prototype.setupSearch.call(this);
|
||||
|
||||
this.searchTypeList = Espo.Utils.clone(this.searchTypeList);
|
||||
|
||||
this.searchTypeList.push('isFromTeams');
|
||||
|
||||
this.searchData.teamIdList = this.getSearchParamsData().teamIdList ||
|
||||
this.searchParams.teamIdList || [];
|
||||
|
||||
this.searchData.teamNameHash = this.getSearchParamsData().teamNameHash ||
|
||||
this.searchParams.teamNameHash || {};
|
||||
|
||||
this.events['click a[data-action="clearLinkTeams"]'] = function (e) {
|
||||
var id = $(e.currentTarget).data('id').toString();
|
||||
let id = $(e.currentTarget).data('id').toString();
|
||||
|
||||
this.deleteLinkTeams(id);
|
||||
};
|
||||
@@ -61,25 +59,25 @@ define('views/fields/user',[ 'views/fields/link'], function (Dep) {
|
||||
scope: 'Team',
|
||||
createButton: false,
|
||||
multiple: true,
|
||||
}, function (view) {
|
||||
}, (view) => {
|
||||
view.render();
|
||||
|
||||
this.notify(false);
|
||||
|
||||
this.listenToOnce(view, 'select', function (models) {
|
||||
this.listenToOnce(view, 'select', models => {
|
||||
if (Object.prototype.toString.call(models) !== '[object Array]') {
|
||||
models = [models];
|
||||
}
|
||||
|
||||
models.forEach(function (model) {
|
||||
models.forEach(model => {
|
||||
this.addLinkTeams(model.id, model.get('name'));
|
||||
}, this);
|
||||
});
|
||||
});
|
||||
}, this);
|
||||
});
|
||||
});
|
||||
|
||||
this.events['click a[data-action="clearLinkTeams"]'] = function (e) {
|
||||
var id = $(e.currentTarget).data('id').toString();
|
||||
let id = $(e.currentTarget).data('id').toString();
|
||||
|
||||
this.deleteLinkTeams(id);
|
||||
};
|
||||
@@ -100,10 +98,10 @@ define('views/fields/user',[ 'views/fields/link'], function (Dep) {
|
||||
Dep.prototype.afterRender.call(this);
|
||||
|
||||
if (this.mode === 'search') {
|
||||
var $elemeneTeams = this.$el.find('input.element-teams');
|
||||
let $elementTeams = this.$el.find('input.element-teams');
|
||||
|
||||
$elemeneTeams.autocomplete({
|
||||
serviceUrl: (q) => {
|
||||
$elementTeams.autocomplete({
|
||||
serviceUrl: () => {
|
||||
return 'Team?&maxSize=' + this.getAutocompleteMaxCount() + '&select=id,name';
|
||||
},
|
||||
minChars: 1,
|
||||
@@ -114,8 +112,8 @@ define('views/fields/user',[ 'views/fields/link'], function (Dep) {
|
||||
return this.getHelper().escapeString(suggestion.name);
|
||||
},
|
||||
transformResult: (response) => {
|
||||
var response = JSON.parse(response);
|
||||
var list = [];
|
||||
response = JSON.parse(response);
|
||||
let list = [];
|
||||
|
||||
response.list.forEach(item => {
|
||||
list.push({
|
||||
@@ -132,22 +130,22 @@ define('views/fields/user',[ 'views/fields/link'], function (Dep) {
|
||||
},
|
||||
onSelect: (s) => {
|
||||
this.addLinkTeams(s.id, s.name);
|
||||
$elemeneTeams.val('');
|
||||
$elemeneTeams.focus();
|
||||
$elementTeams.val('');
|
||||
$elementTeams.focus();
|
||||
},
|
||||
});
|
||||
|
||||
$elemeneTeams.attr('autocomplete', 'espo-' + this.name);
|
||||
$elementTeams.attr('autocomplete', 'espo-' + this.name);
|
||||
|
||||
this.once('render', () => {
|
||||
$elemeneTeams.autocomplete('dispose');
|
||||
$elementTeams.autocomplete('dispose');
|
||||
});
|
||||
|
||||
this.once('remove', () => {
|
||||
$elemeneTeams.autocomplete('dispose');
|
||||
$elementTeams.autocomplete('dispose');
|
||||
});
|
||||
|
||||
var type = this.$el.find('select.search-type').val();
|
||||
let type = this.$el.find('select.search-type').val();
|
||||
|
||||
if (type === 'isFromTeams') {
|
||||
this.searchData.teamIdList.forEach(id => {
|
||||
@@ -160,7 +158,7 @@ define('views/fields/user',[ 'views/fields/link'], function (Dep) {
|
||||
deleteLinkTeams: function (id) {
|
||||
this.deleteLinkTeamsHtml(id);
|
||||
|
||||
var index = this.searchData.teamIdList.indexOf(id);
|
||||
let index = this.searchData.teamIdList.indexOf(id);
|
||||
|
||||
if (index > -1) {
|
||||
this.searchData.teamIdList.splice(index, 1);
|
||||
@@ -191,8 +189,8 @@ define('views/fields/user',[ 'views/fields/link'], function (Dep) {
|
||||
id = Handlebars.Utils.escapeExpression(id);
|
||||
name = Handlebars.Utils.escapeExpression(name);
|
||||
|
||||
var $container = this.$el.find('.link-teams-container');
|
||||
var $el = $('<div />').addClass('link-' + id).addClass('list-group-item');
|
||||
let $container = this.$el.find('.link-teams-container');
|
||||
let $el = $('<div />').addClass('link-' + id).addClass('list-group-item');
|
||||
|
||||
$el.html(name + ' ');
|
||||
|
||||
@@ -207,7 +205,7 @@ define('views/fields/user',[ 'views/fields/link'], function (Dep) {
|
||||
},
|
||||
|
||||
fetchSearch: function () {
|
||||
var type = this.$el.find('select.search-type').val();
|
||||
let type = this.$el.find('select.search-type').val();
|
||||
|
||||
if (type === 'isFromTeams') {
|
||||
return {
|
||||
@@ -218,7 +216,7 @@ define('views/fields/user',[ 'views/fields/link'], function (Dep) {
|
||||
type: type,
|
||||
teamIdList: this.searchData.teamIdList,
|
||||
teamNameHash: this.searchData.teamNameHash,
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -326,8 +326,20 @@ function (Dep, /** typeof module:search-manager.Class */SearchManager) {
|
||||
|
||||
/**
|
||||
* Set up a search panel.
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
setupSearchPanel: function () {
|
||||
this.createSearchView();
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a search view.
|
||||
*
|
||||
* @return {Promise<module:view.Class>}
|
||||
* @protected
|
||||
*/
|
||||
createSearchView: function () {
|
||||
let filterList = Espo.Utils
|
||||
.clone(this.getMetadata().get(['clientDefs', this.foreignScope, 'filterList']) || []);
|
||||
|
||||
@@ -358,7 +370,7 @@ function (Dep, /** typeof module:search-manager.Class */SearchManager) {
|
||||
filterList = [];
|
||||
}
|
||||
|
||||
this.createView('search', this.searchView, {
|
||||
return this.createView('search', this.searchView, {
|
||||
collection: this.collection,
|
||||
el: '#main > .search-container',
|
||||
searchManager: this.searchManager,
|
||||
@@ -369,7 +381,7 @@ function (Dep, /** typeof module:search-manager.Class */SearchManager) {
|
||||
filterList: filterList,
|
||||
}, view => {
|
||||
if (this.viewModeList.length > 1) {
|
||||
this.listenTo(view, 'change-view-mode', this.switchViewMode, this);
|
||||
this.listenTo(view, 'change-view-mode', () => this.switchViewMode());
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -338,9 +338,21 @@ function (Dep, /** typeof module:search-manager.Class */SearchManager) {
|
||||
|
||||
/**
|
||||
* Set up a search panel.
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
setupSearchPanel: function () {
|
||||
this.createView('search', this.searchView, {
|
||||
this.createSearchView();
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a search view.
|
||||
*
|
||||
* @return {Promise<module:view.Class>}
|
||||
* @protected
|
||||
*/
|
||||
createSearchView: function () {
|
||||
return this.createView('search', this.searchView, {
|
||||
collection: this.collection,
|
||||
el: '#main > .search-container',
|
||||
searchManager: this.searchManager,
|
||||
@@ -348,13 +360,11 @@ function (Dep, /** typeof module:search-manager.Class */SearchManager) {
|
||||
viewMode: this.viewMode,
|
||||
viewModeList: this.viewModeList,
|
||||
isWide: true,
|
||||
}, (view) => {
|
||||
this.listenTo(view, 'reset', () => {
|
||||
this.resetSorting();
|
||||
});
|
||||
}, view => {
|
||||
this.listenTo(view, 'reset', () => this.resetSorting());
|
||||
|
||||
if (this.viewModeList.length > 1) {
|
||||
this.listenTo(view, 'change-view-mode', this.switchViewMode, this);
|
||||
this.listenTo(view, 'change-view-mode', mode => this.switchViewMode(mode));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -561,15 +561,23 @@ define('views/main', ['view'], function (Dep) {
|
||||
});
|
||||
});
|
||||
|
||||
let processUi = () => {
|
||||
this.$headerActionsContainer.find('li > .action[data-name="'+name+'"]').parent().removeClass('hidden');
|
||||
this.$headerActionsContainer.find('a.action[data-name="'+name+'"]').removeClass('hidden');
|
||||
|
||||
this.controlMenuDropdownVisibility();
|
||||
this.adjustButtons();
|
||||
};
|
||||
|
||||
if (!this.isRendered()) {
|
||||
if (this.isBeingRendered()) {
|
||||
this.whenRendered().then(() => processUi());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.$headerActionsContainer.find('li > .action[data-name="'+name+'"]').parent().removeClass('hidden');
|
||||
this.$headerActionsContainer.find('a.action[data-name="'+name+'"]').removeClass('hidden');
|
||||
|
||||
this.controlMenuDropdownVisibility();
|
||||
this.adjustButtons();
|
||||
processUi();
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -545,6 +545,8 @@ define('views/record/search', ['view'], function (Dep) {
|
||||
this.selectPreset(this.presetName, true);
|
||||
|
||||
this.hideApplyFiltersButton();
|
||||
|
||||
this.trigger('update-ui');
|
||||
},
|
||||
|
||||
savePreset: function (name) {
|
||||
@@ -629,6 +631,8 @@ define('views/record/search', ['view'], function (Dep) {
|
||||
else {
|
||||
this.$el.find('button.add-filter-button').removeClass('disabled');
|
||||
}
|
||||
|
||||
this.trigger('update-ui');
|
||||
},
|
||||
|
||||
afterRender: function () {
|
||||
|
||||
@@ -2346,9 +2346,10 @@ stream-head-text-container .span {
|
||||
table.audited-summary-table {
|
||||
margin-top: 8px;
|
||||
background-color: transparent;
|
||||
table-layout: fixed;
|
||||
|
||||
td {
|
||||
white-space: nowrap;
|
||||
//white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -2512,8 +2513,8 @@ table.table td.cell .html-container {
|
||||
h5:first-child,
|
||||
h6:first-child,
|
||||
p:first-child,
|
||||
ul:first-child,
|
||||
ol:first-child,
|
||||
> ul:first-child,
|
||||
> ol:first-child,
|
||||
pre:first-child,
|
||||
blockquote:first-child {
|
||||
margin-top: 0 !important;
|
||||
@@ -2525,7 +2526,7 @@ table.table td.cell .html-container {
|
||||
}
|
||||
h2, h3 {
|
||||
font-weight: 600;
|
||||
margin-top: (@line-height-computed / 2);
|
||||
margin-top: (@line-height-computed / 2) * 1.2;
|
||||
margin-bottom: (@line-height-computed / 2);
|
||||
}
|
||||
h4, h5, h6 {
|
||||
@@ -2536,7 +2537,10 @@ table.table td.cell .html-container {
|
||||
h1 {
|
||||
font-size: floor(@font-size-base * 1.2);
|
||||
}
|
||||
h2, h3, h4, h5, h6 {
|
||||
h2 {
|
||||
font-size: floor(@font-size-base * 1.1);
|
||||
}
|
||||
h3, h4, h5, h6 {
|
||||
font-size: floor(@font-size-base);
|
||||
}
|
||||
|
||||
@@ -2549,13 +2553,23 @@ table.table td.cell .html-container {
|
||||
}
|
||||
|
||||
p:last-child,
|
||||
ul:last-child,
|
||||
ol:last-child,
|
||||
> ul:last-child,
|
||||
> ol:last-child,
|
||||
blockquote:last-child,
|
||||
pre:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
> li {
|
||||
ul, ol{
|
||||
margin-top: 4px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ul > li {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
@@ -598,6 +598,7 @@ body {
|
||||
}
|
||||
|
||||
> .panel-body {
|
||||
.list.bottom-border-radius,
|
||||
> .list-container:last-child > .list {
|
||||
border-bottom-left-radius: var(--panel-border-radius);
|
||||
border-bottom-right-radius: var(--panel-border-radius);
|
||||
|
||||
@@ -15,6 +15,7 @@ body {
|
||||
}
|
||||
|
||||
background-color: var(--panel-bg);
|
||||
line-height: @line-height-base;
|
||||
}
|
||||
|
||||
body {
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "espocrm",
|
||||
"version": "7.4.2",
|
||||
"version": "7.4.6",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "espocrm",
|
||||
"version": "7.4.2",
|
||||
"version": "7.4.6",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "espocrm",
|
||||
"version": "7.4.2",
|
||||
"version": "7.4.6",
|
||||
"description": "Open-source CRM.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://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.
|
||||
************************************************************************/
|
||||
|
||||
use Espo\Core\Container;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Core\Templates\Entities\Event;
|
||||
|
||||
class AfterUpgrade
|
||||
{
|
||||
public function run(Container $container): void
|
||||
{
|
||||
$this->updateMetadata($container->get('metadata'));
|
||||
}
|
||||
|
||||
private function updateMetadata(Metadata $metadata): void
|
||||
{
|
||||
$this->updateEventMetadata($metadata);
|
||||
|
||||
$metadata->save();
|
||||
}
|
||||
|
||||
private function updateEventMetadata(Metadata $metadata): void
|
||||
{
|
||||
$defs = $metadata->get(['scopes']);
|
||||
|
||||
foreach ($defs as $entityType => $item) {
|
||||
$isCustom = $item['isCustom'] ?? false;
|
||||
$type = $item['type'] ?? false;
|
||||
|
||||
if (!$isCustom || $type !== Event::TEMPLATE_TYPE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!is_string($metadata->get(['entityDefs', $entityType, 'fields', 'duration', 'select']))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$metadata->delete('entityDefs', $entityType, 'fields.duration.orderBy');
|
||||
|
||||
$metadata->set('entityDefs', $entityType, [
|
||||
'fields' => [
|
||||
'duration' => [
|
||||
'select' => [
|
||||
'select' => "TIMESTAMPDIFF_SECOND:(dateStart, dateEnd)"
|
||||
],
|
||||
'order' => [
|
||||
'order' => [["TIMESTAMPDIFF_SECOND:(dateStart, dateEnd)", "{direction}"]]
|
||||
],
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
use Espo\Core\Container;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Core\Templates\Entities\Event;
|
||||
|
||||
class AfterUpgrade
|
||||
{
|
||||
@@ -44,6 +45,7 @@ class AfterUpgrade
|
||||
]);
|
||||
|
||||
$this->fixParent($metadata);
|
||||
$this->updateEventMetadata($metadata);
|
||||
|
||||
$metadata->save();
|
||||
}
|
||||
@@ -69,4 +71,37 @@ class AfterUpgrade
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function updateEventMetadata(Metadata $metadata): void
|
||||
{
|
||||
$defs = $metadata->get(['scopes']);
|
||||
|
||||
foreach ($defs as $entityType => $item) {
|
||||
$isCustom = $item['isCustom'] ?? false;
|
||||
$type = $item['type'] ?? false;
|
||||
|
||||
if (!$isCustom || $type !== Event::TEMPLATE_TYPE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!is_string($metadata->get(['entityDefs', $entityType, 'fields', 'duration', 'select']))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$metadata->delete('entityDefs', $entityType, 'fields.duration.orderBy');
|
||||
|
||||
$metadata->set('entityDefs', $entityType, [
|
||||
'fields' => [
|
||||
'duration' => [
|
||||
'select' => [
|
||||
'select' => "TIMESTAMPDIFF_SECOND:(dateStart, dateEnd)"
|
||||
],
|
||||
'order' => [
|
||||
'order' => [["TIMESTAMPDIFF_SECOND:(dateStart, dateEnd)", "{direction}"]]
|
||||
],
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user