diff --git a/application/Espo/Controllers/EntityManager.php b/application/Espo/Controllers/EntityManager.php
index 83e4a59e1c..7101a16631 100644
--- a/application/Espo/Controllers/EntityManager.php
+++ b/application/Espo/Controllers/EntityManager.php
@@ -60,7 +60,7 @@ class EntityManager extends \Espo\Core\Controllers\Base
$name = filter_var($name, \FILTER_SANITIZE_STRING);
$type = filter_var($type, \FILTER_SANITIZE_STRING);
- $params = array();
+ $params = [];
if (!empty($data['labelSingular'])) {
$params['labelSingular'] = $data['labelSingular'];
@@ -187,19 +187,19 @@ class EntityManager extends \Espo\Core\Controllers\Base
$paramList = [
'entity',
- 'entityForeign',
'link',
'linkForeign',
'label',
- 'labelForeign',
- 'linkType'
+ 'linkType',
];
$additionalParamList = [
+ 'entityForeign',
'relationName',
+ 'labelForeign',
];
- $params = array();
+ $params = [];
foreach ($paramList as $item) {
if (empty($data[$item])) {
@@ -209,9 +209,11 @@ class EntityManager extends \Espo\Core\Controllers\Base
}
foreach ($additionalParamList as $item) {
- $params[$item] = filter_var($data[$item], \FILTER_SANITIZE_STRING);
+ $params[$item] = filter_var($data[$item] ?? null, \FILTER_SANITIZE_STRING);
}
+ $params['labelForeign'] = $params['labelForeign'] ?? $params['linkForeign'];
+
if (array_key_exists('linkMultipleField', $data)) {
$params['linkMultipleField'] = $data['linkMultipleField'];
}
@@ -225,6 +227,12 @@ class EntityManager extends \Espo\Core\Controllers\Base
if (array_key_exists('auditedForeign', $data)) {
$params['auditedForeign'] = $data['auditedForeign'];
}
+ if (array_key_exists('parentEntityTypeList', $data)) {
+ $params['parentEntityTypeList'] = $data['parentEntityTypeList'];
+ }
+ if (array_key_exists('foreignLinkEntityTypeList', $data)) {
+ $params['foreignLinkEntityTypeList'] = $data['foreignLinkEntityTypeList'];
+ }
$result = $this->getContainer()->get('entityManagerUtil')->createLink($params);
@@ -251,12 +259,12 @@ class EntityManager extends \Espo\Core\Controllers\Base
'link',
'linkForeign',
'label',
- 'labelForeign'
+ 'labelForeign',
];
$additionalParamList = [];
- $params = array();
+ $params = [];
foreach ($paramList as $item) {
if (array_key_exists($item, $data)) {
$params[$item] = filter_var($data[$item], \FILTER_SANITIZE_STRING);
@@ -280,6 +288,12 @@ class EntityManager extends \Espo\Core\Controllers\Base
if (array_key_exists('auditedForeign', $data)) {
$params['auditedForeign'] = $data['auditedForeign'];
}
+ if (array_key_exists('parentEntityTypeList', $data)) {
+ $params['parentEntityTypeList'] = $data['parentEntityTypeList'];
+ }
+ if (array_key_exists('foreignLinkEntityTypeList', $data)) {
+ $params['foreignLinkEntityTypeList'] = $data['foreignLinkEntityTypeList'];
+ }
$result = $this->getContainer()->get('entityManagerUtil')->updateLink($params);
diff --git a/application/Espo/Core/Utils/EntityManager.php b/application/Espo/Core/Utils/EntityManager.php
index 311606767e..822cb28bff 100644
--- a/application/Espo/Core/Utils/EntityManager.php
+++ b/application/Espo/Core/Utils/EntityManager.php
@@ -672,18 +672,23 @@ class EntityManager
if (empty($linkType)) {
throw new Error();
}
- if (empty($entity) || empty($entityForeign)) {
- throw new Error();
- }
- if (empty($entityForeign) || empty($linkForeign)) {
+ if (empty($entity)) {
throw new Error();
}
+ if ($linkType !== 'childrenToParent') {
+ if (empty($entityForeign)) {
+ throw new Error();
+ }
+ }
if ($this->getMetadata()->get('entityDefs.' . $entity . '.links.' . $link)) {
throw new Conflict('Link ['.$entity.'::'.$link.'] already exists.');
}
- if ($this->getMetadata()->get('entityDefs.' . $entityForeign . '.links.' . $linkForeign)) {
- throw new Conflict('Link ['.$entityForeign.'::'.$linkForeign.'] already exists.');
+
+ if ($entityForeign) {
+ if ($this->getMetadata()->get('entityDefs.' . $entityForeign . '.links.' . $linkForeign)) {
+ throw new Conflict('Link ['.$entityForeign.'::'.$linkForeign.'] already exists.');
+ }
}
if ($entity === $entityForeign) {
@@ -926,28 +931,60 @@ class EntityManager
$dataRight['links'][$linkForeign]['midKeys'] = ['rightId', 'leftId'];
}
break;
+
+ case 'childrenToParent':
+ $dataLeft = [
+ 'fields' => [
+ $link => [
+ 'type' => 'linkParent',
+ 'entityList' => $params['parentEntityTypeList'] ?? [],
+ ],
+ ],
+ 'links' => [
+ $link => [
+ 'type' => 'belongsToParent',
+ 'foreign' => $linkForeign,
+ 'isCustom' => true,
+ ],
+ ],
+ ];
+ break;
+
default:
throw new BadRequest();
}
$this->getMetadata()->set('entityDefs', $entity, $dataLeft);
- $this->getMetadata()->set('entityDefs', $entityForeign, $dataRight);
+ if ($entityForeign) {
+ $this->getMetadata()->set('entityDefs', $entityForeign, $dataRight);
+ }
$this->getMetadata()->save();
$this->getLanguage()->set($entity, 'fields', $link, $label);
$this->getLanguage()->set($entity, 'links', $link, $label);
- $this->getLanguage()->set($entityForeign, 'fields', $linkForeign, $labelForeign);
- $this->getLanguage()->set($entityForeign, 'links', $linkForeign, $labelForeign);
+ if ($entityForeign) {
+ $this->getLanguage()->set($entityForeign, 'fields', $linkForeign, $labelForeign);
+ $this->getLanguage()->set($entityForeign, 'links', $linkForeign, $labelForeign);
+ }
$this->getLanguage()->save();
if ($this->getLanguage()->getLanguage() !== $this->getBaseLanguage()->getLanguage()) {
$this->getBaseLanguage()->set($entity, 'fields', $link, $label);
$this->getBaseLanguage()->set($entity, 'links', $link, $label);
- $this->getBaseLanguage()->set($entityForeign, 'fields', $linkForeign, $labelForeign);
- $this->getBaseLanguage()->set($entityForeign, 'links', $linkForeign, $labelForeign);
+ if ($entityForeign) {
+ $this->getBaseLanguage()->set($entityForeign, 'fields', $linkForeign, $labelForeign);
+ $this->getBaseLanguage()->set($entityForeign, 'links', $linkForeign, $labelForeign);
+ }
$this->getBaseLanguage()->save();
}
+ if ($linkType === 'childrenToParent') {
+ $foreignLinkEntityTypeList = $params['foreignLinkEntityTypeList'] ?? null;
+ if ($foreignLinkEntityTypeList && is_array($foreignLinkEntityTypeList)) {
+ $this->updateParentForeignLinks($entity, $link, $linkForeign, $foreignLinkEntityTypeList);
+ }
+ }
+
return true;
}
@@ -955,18 +992,22 @@ class EntityManager
{
$entity = $params['entity'];
$link = $params['link'];
- $entityForeign = $params['entityForeign'];
+ $entityForeign = $params['entityForeign'] ?? null;
$linkForeign = $params['linkForeign'];
- if (empty($entity) || empty($entityForeign)) {
- throw new Error();
- }
- if (empty($entityForeign) || empty($linkForeign)) {
- throw new Error();
- }
+ if (empty($link)) throw new BadRequest();
+ if (empty($entity)) throw new BadRequest();
+ $linkType = $this->getMetadata()->get("entityDefs.{$entity}.links.{$link}.type");
$isCustom = $this->getMetadata()->get("entityDefs.{$entity}.links.{$link}.isCustom");
+ if ($linkType === 'belongsToParent') {
+
+ } else {
+ if (empty($entityForeign)) throw new BadRequest();
+ if (empty($linkForeign)) throw new BadRequest();
+ }
+
if (
$this->getMetadata()->get("entityDefs.{$entity}.links.{$link}.type") == 'hasMany'
&&
@@ -1033,6 +1074,7 @@ class EntityManager
}
if (
+ $linkForeign &&
in_array($this->getMetadata()->get("entityDefs.{$entityForeign}.links.{$linkForeign}.type"), ['hasMany', 'hasChildren'])
) {
if (array_key_exists('auditedForeign', $params)) {
@@ -1049,23 +1091,44 @@ class EntityManager
}
}
+ if ($linkType === 'belongsToParent') {
+ $parentEntityTypeList = $params['parentEntityTypeList'] ?? null;
+ if ($parentEntityTypeList && is_array($parentEntityTypeList)) {
+ $data = [
+ 'fields' => [
+ $link => [
+ 'entityList' => $parentEntityTypeList,
+ ],
+ ],
+ ];
+ $this->getMetadata()->set('entityDefs', $entity, $data);
+ $this->getMetadata()->save();
+ }
+
+ $foreignLinkEntityTypeList = $params['foreignLinkEntityTypeList'] ?? null;
+ if ($foreignLinkEntityTypeList && is_array($foreignLinkEntityTypeList)) {
+ $this->updateParentForeignLinks($entity, $link, $linkForeign, $foreignLinkEntityTypeList);
+ }
+ }
+
$label = null;
if (isset($params['label'])) {
$label = $params['label'];
}
- $labelForeign = null;
- if (isset($params['labelForeign'])) {
- $labelForeign = $params['labelForeign'];
- }
-
if ($label) {
$this->getLanguage()->set($entity, 'fields', $link, $label);
$this->getLanguage()->set($entity, 'links', $link, $label);
}
- if ($labelForeign) {
- $this->getLanguage()->set($entityForeign, 'fields', $linkForeign, $labelForeign);
- $this->getLanguage()->set($entityForeign, 'links', $linkForeign, $labelForeign);
+ if ($linkType !== 'belongsToParent') {
+ $labelForeign = null;
+ if (isset($params['labelForeign'])) {
+ $labelForeign = $params['labelForeign'];
+ }
+ if ($labelForeign) {
+ $this->getLanguage()->set($entityForeign, 'fields', $linkForeign, $labelForeign);
+ $this->getLanguage()->set($entityForeign, 'links', $linkForeign, $labelForeign);
+ }
}
$this->getLanguage()->save();
@@ -1076,7 +1139,7 @@ class EntityManager
$this->getBaseLanguage()->set($entity, 'fields', $link, $label);
$this->getBaseLanguage()->set($entity, 'links', $link, $label);
}
- if ($labelForeign) {
+ if ($labelForeign && $linkType !== 'belongsToParent') {
$this->getBaseLanguage()->set($entityForeign, 'fields', $linkForeign, $labelForeign);
$this->getBaseLanguage()->set($entityForeign, 'links', $linkForeign, $labelForeign);
}
@@ -1096,14 +1159,38 @@ class EntityManager
throw new Error("Could not delete link {$entity}.{$link}. Not isCustom.");
}
+ if (empty($entity) || empty($link)) {
+ throw new BadRequest();
+ }
+
$entityForeign = $this->getMetadata()->get("entityDefs.{$entity}.links.{$link}.entity");
$linkForeign = $this->getMetadata()->get("entityDefs.{$entity}.links.{$link}.foreign");
+ $linkType = $this->getMetadata()->get("entityDefs.{$entity}.links.{$link}.type");
- if (empty($entity) || empty($entityForeign)) {
- throw new Error();
+ if (!$this->getMetadata()->get('entityDefs', $entity, 'links', $link, 'isCustom')) {
+ throw new Error("Can't remove not custom link.");
}
+
+ if ($linkType === 'hasChildren') {
+ $this->getMetadata()->delete('entityDefs', $entity, [
+ 'links.' . $link,
+ ]);
+ $this->getMetadata()->save();
+ return true;
+ }
+
+ if ($linkType === 'belongsToParent') {
+ $this->getMetadata()->delete('entityDefs', $entity, [
+ 'fields.' . $link,
+ 'links.' . $link,
+ ]);
+ $this->getMetadata()->save();
+ $this->updateParentForeignLinks($entity, $link, $linkForeign, []);
+ return true;
+ }
+
if (empty($entityForeign) || empty($linkForeign)) {
- throw new Error();
+ throw new BadRequest();
}
$this->getMetadata()->delete('entityDefs', $entity, array(
@@ -1148,8 +1235,6 @@ class EntityManager
$className = '\\Espo\\Modules\\'.$normalizedTemplateModuleName.'\\Core\\Utils\\EntityManager\\Hooks\\' . $type . 'Type';
}
-
-
$className = $this->getMetadata()->get(['app', 'entityTemplates', $type, 'hookClassName'], $className);
if (class_exists($className)) {
@@ -1192,4 +1277,89 @@ class EntityManager
$this->getLanguage()->delete('Global', 'scopeNamesPlural', $scope);
$this->getLanguage()->save();
}
+
+ protected function updateParentForeignLinks(
+ string $entityType, string $link, string $linkForeign, array $foreignLinkEntityTypeList
+ )
+ {
+ $toCreateList = [];
+
+ foreach ($foreignLinkEntityTypeList as $foreignEntityType) {
+ $linkDefs = $this->getMetadata()->get(['entityDefs', $foreignEntityType, 'links']) ?? [];
+
+ foreach ($linkDefs as $kLink => $defs) {
+ $kForeign = $defs['foreign'] ?? null;
+ $kIsCustom = $defs['isCustom'] ?? false;
+ $kEntity = $defs['entity'] ?? null;
+
+ if (
+ $kForeign === $link && !$kIsCustom && $kEntity == $entityType
+ ) continue 2;
+
+ if ($kLink == $linkForeign) {
+ if ($defs['type'] !== 'hasChildren') continue 2;
+ }
+ }
+
+ $toCreateList[] = $foreignEntityType;
+ }
+
+ $entityTypeList = array_keys($this->getMetadata()->get('entityDefs') ?? []);
+
+ foreach ($entityTypeList as $itemEntityType) {
+ $linkDefs = $this->getMetadata()->get(['entityDefs', $itemEntityType, 'links']) ?? [];
+
+ foreach ($linkDefs as $kLink => $defs) {
+ $kForeign = $defs['foreign'] ?? null;
+ $kIsCustom = $defs['isCustom'] ?? false;
+ $kEntity = $defs['entity'] ?? null;
+
+ if (
+ $kForeign === $link && $kIsCustom && $kEntity == $entityType && $defs['type'] == 'hasChildren' &&
+ $kLink === $linkForeign
+ ) {
+ if (!in_array($itemEntityType, $toCreateList)) {
+ $this->getMetadata()->delete('entityDefs', $itemEntityType, [
+ 'links.' . $linkForeign,
+ ]);
+
+ $this->getLanguage()->delete($itemEntityType, 'links', $linkForeign);
+
+ if ($this->getLanguage()->getLanguage() !== $this->getBaseLanguage()->getLanguage()) {
+ $this->getBaseLanguage()->delete($itemEntityType, 'links', $linkForeign);
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ foreach ($toCreateList as $itemEntityType) {
+ $this->getMetadata()->set('entityDefs', $itemEntityType, [
+ 'links' => [
+ $linkForeign => [
+ 'type' => 'hasChildren',
+ 'foreign' => $link,
+ 'entity' => $entityType,
+ 'isCustom' => true,
+ ],
+ ],
+ ]);
+
+ $label = $this->getLanguage()->translate($entityType, 'scopeNamesPlural');
+
+ $this->getLanguage()->set($itemEntityType, 'links', $linkForeign, $label);
+ if ($this->getLanguage()->getLanguage() !== $this->getBaseLanguage()->getLanguage()) {
+ $this->getBaseLanguage()->set($itemEntityType, 'links', $linkForeign, $label);
+ }
+ }
+
+ $this->getMetadata()->save();
+
+ $this->getLanguage()->save();
+
+ if ($this->getLanguage()->getLanguage() !== $this->getBaseLanguage()->getLanguage()) {
+ $this->getBaseLanguage()->save();
+ }
+ }
}
diff --git a/application/Espo/Resources/i18n/en_US/EntityManager.json b/application/Espo/Resources/i18n/en_US/EntityManager.json
index 7261fcf93a..ff687c140e 100644
--- a/application/Espo/Resources/i18n/en_US/EntityManager.json
+++ b/application/Espo/Resources/i18n/en_US/EntityManager.json
@@ -34,7 +34,9 @@
"kanbanStatusIgnoreList": "Ignored groups in Kanban view",
"iconClass": "Icon",
"countDisabled": "Disable record count",
- "fullTextSearch": "Full-Text Search"
+ "fullTextSearch": "Full-Text Search",
+ "parentEntityTypeList": "Parent Entity Types",
+ "foreignLinkEntityTypeList": "Foreign Links"
},
"options": {
"type": {
diff --git a/client/res/templates/admin/link-manager/index.tpl b/client/res/templates/admin/link-manager/index.tpl
index 8cf3a1c044..9664e6068f 100644
--- a/client/res/templates/admin/link-manager/index.tpl
+++ b/client/res/templates/admin/link-manager/index.tpl
@@ -48,7 +48,7 @@
- {{#if isCustom}}
+ {{#if isRemovable}}
{{translate 'Remove'}}
diff --git a/client/res/templates/admin/link-manager/modals/edit.tpl b/client/res/templates/admin/link-manager/modals/edit.tpl
index 44297ba730..2e47d12ebf 100644
--- a/client/res/templates/admin/link-manager/modals/edit.tpl
+++ b/client/res/templates/admin/link-manager/modals/edit.tpl
@@ -81,3 +81,27 @@
+
+
+
diff --git a/client/res/templates/fields/checklist/detail.tpl b/client/res/templates/fields/checklist/detail.tpl
index 43f191d953..3c9417148b 100644
--- a/client/res/templates/fields/checklist/detail.tpl
+++ b/client/res/templates/fields/checklist/detail.tpl
@@ -5,3 +5,4 @@
{{/each}}
+{{#unless optionDataList.length}}{{translate 'None'}}{{/unless}}
diff --git a/client/res/templates/fields/checklist/edit.tpl b/client/res/templates/fields/checklist/edit.tpl
index 29fc72c813..38f2d94e83 100644
--- a/client/res/templates/fields/checklist/edit.tpl
+++ b/client/res/templates/fields/checklist/edit.tpl
@@ -5,3 +5,4 @@
{{/each}}
+{{#unless optionDataList.length}}{{translate 'None'}}{{/unless}}
diff --git a/client/src/views/admin/link-manager/fields/foreign-link-entity-type-list.js b/client/src/views/admin/link-manager/fields/foreign-link-entity-type-list.js
new file mode 100644
index 0000000000..21bad1b290
--- /dev/null
+++ b/client/src/views/admin/link-manager/fields/foreign-link-entity-type-list.js
@@ -0,0 +1,67 @@
+/************************************************************************
+ * This file is part of EspoCRM.
+ *
+ * EspoCRM - Open Source CRM application.
+ * Copyright (C) 2014-2019 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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.
+ ************************************************************************/
+
+define('views/admin/link-manager/fields/foreign-link-entity-type-list', 'views/fields/checklist', function (Dep) {
+
+ return Dep.extend({
+
+ setup: function () {
+ this.params.translation = 'Global.scopeNames';
+ Dep.prototype.setup.call(this);
+ },
+
+ afterRender: function () {
+ Dep.prototype.afterRender.call(this);
+ this.controlOptionsAviability();
+ },
+
+ controlOptionsAviability: function () {
+ this.params.options.forEach(function (item) {
+ var link = this.model.get('link');
+ var linkForeign = this.model.get('linkForeign');
+ var entityType = this.model.get('entity');
+
+ var linkDefs = this.getMetadata().get(['entityDefs', item, 'links']) || {};
+
+ var isFound = false;
+ for (var i in linkDefs) {
+ if (linkDefs[i].foreign == link && !linkDefs[i].isCustom && linkDefs[i].entity == entityType) {
+ isFound = true;
+ } else if (i === linkForeign && linkDefs[i].type !== 'hasChildren') {
+ isFound = true;
+ }
+ }
+
+ if (isFound) {
+ this.$el.find('input[data-name="checklistItem-foreignLinkEntityTypeList-'+item+'"]').attr('disabled', 'disabled');
+ }
+ }, this);
+ },
+
+ });
+});
diff --git a/client/src/views/admin/link-manager/index.js b/client/src/views/admin/link-manager/index.js
index 0b939b1825..89da7f990f 100644
--- a/client/src/views/admin/link-manager/index.js
+++ b/client/src/views/admin/link-manager/index.js
@@ -105,25 +105,30 @@ define('views/admin/link-manager/index', 'view', function (Dep) {
linkList.forEach(function (link) {
var d = links[link];
- if (!d.foreign) return;
- if (!d.entity) return;
+ var linkForeign = d.foreign;
- var foreignType = this.getMetadata().get('entityDefs.' + d.entity + '.links.' + d.foreign + '.type');
-
- var type = this.computeRelationshipType(d.type, foreignType);
+ if (d.type === 'belongsToParent') {
+ var type = 'childrenToParent';
+ } else {
+ if (!d.entity) return;
+ if (!linkForeign) return;
+ var foreignType = this.getMetadata().get('entityDefs.' + d.entity + '.links.' + d.foreign + '.type');
+ var type = this.computeRelationshipType(d.type, foreignType);
+ }
if (!type) return;
this.linkDataList.push({
link: link,
isCustom: d.isCustom,
+ isRemovable: d.isCustom,
customizable: d.customizable,
type: type,
entityForeign: d.entity,
entity: this.scope,
- linkForeign: d.foreign,
+ linkForeign: linkForeign,
label: this.getLanguage().translate(link, 'links', this.scope),
- labelForeign: this.getLanguage().translate(d.foreign, 'links', d.entity)
+ labelForeign: this.getLanguage().translate(d.foreign, 'links', d.entity),
});
}, this);
@@ -182,7 +187,7 @@ define('views/admin/link-manager/index', 'view', function (Dep) {
type: 'POST',
data: JSON.stringify({
entity: this.scope,
- link: link
+ link: link,
})
}).done(function () {
this.$el.find('table tr[data-link="'+link+'"]').remove();
diff --git a/client/src/views/admin/link-manager/modals/edit.js b/client/src/views/admin/link-manager/modals/edit.js
index 8596cae346..2342bbf598 100644
--- a/client/src/views/admin/link-manager/modals/edit.js
+++ b/client/src/views/admin/link-manager/modals/edit.js
@@ -85,7 +85,18 @@ define('views/admin/link-manager/modals/edit',
var type = this.getMetadata().get('entityDefs.' + entity + '.links.' + link + '.type');
var foreignType = this.getMetadata().get('entityDefs.' + entityForeign + '.links.' + linkForeign + '.type');
- var linkType = Index.prototype.computeRelationshipType.call(this, type, foreignType);
+ if (type === 'belongsToParent') {
+ var linkType = 'childrenToParent';
+ labelForeign = null;
+
+ var entityTypeList = this.getMetadata().get(['entityDefs', entity, 'fields', link, 'entityList']) || [];
+ this.model.set('parentEntityTypeList', entityTypeList);
+
+ var foreignLinkEntityTypeList = this.getForeignLinkEntityTypeList(entity, link, entityTypeList);
+ this.model.set('foreignLinkEntityTypeList', foreignLinkEntityTypeList);
+ } else {
+ var linkType = Index.prototype.computeRelationshipType.call(this, type, foreignType);
+ }
this.model.set('linkType', linkType);
this.model.set('entityForeign', entityForeign);
@@ -166,7 +177,7 @@ define('views/admin/link-manager/modals/edit',
name: 'linkType',
params: {
required: true,
- options: ['', 'oneToMany', 'manyToOne', 'manyToMany', 'oneToOneRight', 'oneToOneLeft']
+ options: ['', 'oneToMany', 'manyToOne', 'manyToMany', 'oneToOneRight', 'oneToOneLeft', 'childrenToParent']
}
},
readOnly: !isNew
@@ -285,8 +296,55 @@ define('views/admin/link-manager/modals/edit',
tooltipText: this.translate('linkAudited', 'tooltips', 'EntityManager')
});
+ this.createView('parentEntityTypeList', 'views/fields/entity-type-list', {
+ model: model,
+ mode: 'edit',
+ el: this.options.el + ' .field[data-name="parentEntityTypeList"]',
+ defs: {
+ name: 'parentEntityTypeList',
+ },
+ });
+
+ this.createView('foreignLinkEntityTypeList', 'views/admin/link-manager/fields/foreign-link-entity-type-list', {
+ model: model,
+ mode: 'edit',
+ el: this.options.el + ' .field[data-name="foreignLinkEntityTypeList"]',
+ defs: {
+ name: 'foreignLinkEntityTypeList',
+ params: {
+ options: this.model.get('parentEntityTypeList') || [],
+ },
+ },
+ });
this.model.fetchedAttributes = this.model.getClonedAttributes();
+
+ this.listenTo(this.model, 'change', function () {
+ if (
+ !this.model.hasChanged('parentEntityTypeList')
+ &&
+ !this.model.hasChanged('linkForeign')
+ &&
+ !this.model.hasChanged('link')
+ ) return;
+
+ var view = this.getView('foreignLinkEntityTypeList');
+ if (view) {
+ view.setOptionList(this.model.get('parentEntityTypeList') || []);
+ }
+ var checkedList = Espo.Utils.clone(this.model.get('foreignLinkEntityTypeList') || []);
+
+ this.getForeignLinkEntityTypeList(
+ this.model.get('entity'), this.model.get('link'), this.model.get('parentEntityTypeList') || [], true
+ ).forEach(function (item) {
+ if (!~checkedList.indexOf(item)) {
+ checkedList.push(item);
+ }
+ }, this);
+
+ this.model.set('foreignLinkEntityTypeList', checkedList)
+
+ }, this);
},
toPlural: function (string) {
@@ -301,13 +359,32 @@ define('views/admin/link-manager/modals/edit',
var entityForeign = this.model.get('entityForeign');
var linkType = this.model.get('linkType');
- if (!entityForeign || !linkType) {
- this.model.set('link', '');
- this.model.set('linkForeign', '');
+ if (linkType === 'childrenToParent') {
+ this.model.set('link', 'parent');
+ this.model.set('label', 'Parent');
- this.model.set('label', '');
- this.model.set('labelForeign', '');
- return;
+ var linkForeign = this.toPlural(Espo.Utils.lowerCaseFirst(this.scope));
+
+ if (this.getMetadata().get(['entityDefs', this.scope, 'links', 'parent'])) {
+ this.model.set('link', 'parentAnother');
+ this.model.set('label', 'Parent Another');
+ linkForeign += 'Another';
+ }
+
+ this.model.set('linkForeign', linkForeign);
+
+ this.model.set('labelForeign', '');
+ this.model.set('entityForeign', null);
+ return;
+ } else {
+ if (!entityForeign || !linkType) {
+ this.model.set('link', '');
+ this.model.set('linkForeign', '');
+
+ this.model.set('label', '');
+ this.model.set('labelForeign', '');
+ return;
+ }
}
var link;
@@ -423,10 +500,16 @@ define('views/admin/link-manager/modals/edit',
handleLinkTypeChange: function () {
var linkType = this.model.get('linkType');
+
+ this.showField('entityForeign');
+ this.showField('labelForeign');
+
+ this.hideField('parentEntityTypeList');
+ this.hideField('foreignLinkEntityTypeList');
+
if (linkType === 'manyToMany') {
var relationNameView = this.getView('relationName');
this.showField('relationName');
- this.showField('relationName');
this.showField('linkMultipleField');
this.showField('linkMultipleFieldForeign');
@@ -457,6 +540,11 @@ define('views/admin/link-manager/modals/edit',
} else if (linkType == 'childrenToParent') {
this.hideField('audited');
this.showField('auditedForeign');
+ this.hideField('entityForeign');
+ this.hideField('labelForeign');
+
+ this.showField('parentEntityTypeList');
+ this.showField('foreignLinkEntityTypeList');
} else {
this.hideField('audited');
this.hideField('auditedForeign');
@@ -504,6 +592,8 @@ define('views/admin/link-manager/modals/edit',
'linkMultipleFieldForeign',
'audited',
'auditedForeign',
+ 'parentEntityTypeList',
+ 'foreignLinkEntityTypeList',
];
var notValid = false;
@@ -548,6 +638,8 @@ define('views/admin/link-manager/modals/edit',
var audited = this.model.get('audited');
var auditedForeign = this.model.get('auditedForeign');
+ var linkType = this.model.get('linkType');
+
var attributes = {
entity: entity,
entityForeign: entityForeign,
@@ -555,14 +647,15 @@ define('views/admin/link-manager/modals/edit',
linkForeign: linkForeign,
label: label,
labelForeign: labelForeign,
- linkType: this.model.get('linkType'),
+ linkType: linkType,
relationName: relationName,
linkMultipleField: linkMultipleField,
linkMultipleFieldForeign: linkMultipleFieldForeign,
audited: audited,
- auditedForeign: auditedForeign
+ auditedForeign: auditedForeign,
};
+
if (!this.isNew) {
if (attributes.label === this.model.fetchedAttributes.label) {
delete attributes.label;
@@ -572,6 +665,13 @@ define('views/admin/link-manager/modals/edit',
}
}
+ if (linkType === 'childrenToParent') {
+ delete attributes.entityForeign;
+ delete attributes.labelForeign;
+ attributes.parentEntityTypeList = this.model.get('parentEntityTypeList');
+ attributes.foreignLinkEntityTypeList = this.model.get('foreignLinkEntityTypeList');
+ }
+
$.ajax({
url: url,
type: 'POST',
@@ -603,9 +703,14 @@ define('views/admin/link-manager/modals/edit',
(data.fields || {})[link] = label;
(data.links || {})[link] = label;
- data = ((this.getLanguage().data || {}) || {})[entityForeign];
- (data.fields || {})[linkForeign] = labelForeign;
- (data.links || {})[linkForeign] = labelForeign;
+ if (entityForeign) {
+ data = ((this.getLanguage().data || {}) || {})[entityForeign];
+
+ if (linkForeign) {
+ (data.fields || {})[linkForeign] = labelForeign;
+ (data.links || {})[linkForeign] = labelForeign;
+ }
+ }
this.getMetadata().load(function () {
this.trigger('after:save');
@@ -613,5 +718,32 @@ define('views/admin/link-manager/modals/edit',
}.bind(this), true);
}.bind(this));
},
+
+ getForeignLinkEntityTypeList: function (entityType, link, entityTypeList, onlyNotCustom) {
+ var list = [];
+
+ entityTypeList.forEach(function (item) {
+ var linkDefs = this.getMetadata().get(['entityDefs', item, 'links']) || {};
+
+ var isFound = false;
+ for (var i in linkDefs) {
+ if (linkDefs[i].foreign == link && linkDefs[i].entity == entityType && linkDefs[i].type === 'hasChildren') {
+ if (onlyNotCustom) {
+ if (linkDefs[i].isCustom) {
+ continue;
+ }
+ }
+ isFound = true;
+ break;
+ }
+ }
+
+ if (isFound) {
+ list.push(item);
+ }
+ }, this);
+
+ return list;
+ },
});
});
diff --git a/client/src/views/fields/entity-type-list.js b/client/src/views/fields/entity-type-list.js
index ee2329e114..f4b17d208f 100644
--- a/client/src/views/fields/entity-type-list.js
+++ b/client/src/views/fields/entity-type-list.js
@@ -26,7 +26,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
-Espo.define('views/fields/entity-type-list', 'views/fields/multi-enum', function (Dep) {
+define('views/fields/entity-type-list', 'views/fields/multi-enum', function (Dep) {
return Dep.extend({
@@ -46,7 +46,6 @@ Espo.define('views/fields/entity-type-list', 'views/fields/multi-enum', function
}.bind(this)).sort(function (v1, v2) {
return this.translate(v1, 'scopeNames').localeCompare(this.translate(v2, 'scopeNames'));
}.bind(this));
- this.params.options.unshift('');
},
setup: function () {
@@ -55,8 +54,7 @@ Espo.define('views/fields/entity-type-list', 'views/fields/multi-enum', function
}
this.setupOptions();
Dep.prototype.setup.call(this);
- }
+ },
});
});
-
|