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 @@ + +
+
+
+ +
+ {{{parentEntityTypeList}}} +
+
+ +
+
+
+
+ +
+ {{{foreignLinkEntityTypeList}}} +
+
+
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); - } + }, }); }); -