diff --git a/application/Espo/Core/Record/Duplicator/EntityDuplicator.php b/application/Espo/Core/Record/Duplicator/EntityDuplicator.php index a7d110748a..64a5062e24 100644 --- a/application/Espo/Core/Record/Duplicator/EntityDuplicator.php +++ b/application/Espo/Core/Record/Duplicator/EntityDuplicator.php @@ -29,6 +29,7 @@ namespace Espo\Core\Record\Duplicator; +use Espo\Core\Utils\Metadata; use Espo\ORM\Entity; use Espo\ORM\Defs; use Espo\ORM\Defs\FieldDefs; @@ -44,7 +45,8 @@ class EntityDuplicator public function __construct( private Defs $defs, private FieldDuplicatorFactory $fieldDuplicatorFactory, - private FieldUtil $fieldUtil + private FieldUtil $fieldUtil, + private Metadata $metadata ) {} public function duplicate(Entity $entity): stdClass @@ -68,7 +70,7 @@ class EntityDuplicator $entityType = $entity->getEntityType(); $field = $fieldDefs->getName(); - if ($fieldDefs->getParam('duplicateIgnore')) { + if ($this->toIgnoreField($entityType, $fieldDefs)) { $attributeList = $this->fieldUtil->getAttributeList($entityType, $field); foreach ($attributeList as $attribute) { @@ -90,4 +92,20 @@ class EntityDuplicator $valueMap->$attribute = $value; } } + + private function toIgnoreField(string $entityType, FieldDefs $fieldDefs): bool + { + $type = $fieldDefs->getType(); + + // @todo Use FieldType constants. + if (in_array($type, ['autoincrement', 'number'])) { + return true; + } + + if ($this->metadata->get(['scopes', $entityType, 'statusField']) === $fieldDefs->getName()) { + return true; + } + + return (bool) $fieldDefs->getParam('duplicateIgnore'); + } } diff --git a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Account.json b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Account.json index 55bf271855..d9c1c67d58 100644 --- a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Account.json +++ b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Account.json @@ -194,7 +194,6 @@ "type": "linkMultiple", "layoutDetailDisabled": true, "layoutListDisabled": true, - "layoutMassUpdateDisabled": true, "importDisabled": true, "exportDisabled": true, "noLoad": true diff --git a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Contact.json b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Contact.json index 860946f7cc..815a5b65dc 100644 --- a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Contact.json +++ b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Contact.json @@ -466,7 +466,6 @@ "type": "linkMultiple", "layoutDetailDisabled": true, "layoutListDisabled": true, - "layoutMassUpdateDisabled": true, "importDisabled": true, "noLoad": true }, diff --git a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Lead.json b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Lead.json index 868d52e354..de974cb9cd 100644 --- a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Lead.json +++ b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Lead.json @@ -216,7 +216,6 @@ "type": "linkMultiple", "layoutDetailDisabled": true, "layoutListDisabled": true, - "layoutMassUpdateDisabled": true, "importDisabled": true, "noLoad": true }, diff --git a/client/src/ui.js b/client/src/ui.js index 08e54f803a..5b468028af 100644 --- a/client/src/ui.js +++ b/client/src/ui.js @@ -886,6 +886,7 @@ Espo.Ui = { * @property {'manual'|'click'|'hover'|'focus'} [trigger='manual'] A trigger type. * @property {boolean} [noToggleInit=false] Skip init toggle on click. * @property {boolean} [preventDestroyOnRender=false] Don't destroy on re-render. + * @property {boolean} [noHideOnOutsideClick=false] Don't hide on clicking outside. * @property {function(): void} [onShow] On-show callback. * @property {function(): void} [onHide] On-hide callback. */ @@ -896,18 +897,18 @@ Espo.Ui = { * @param {Element|JQuery} element An element. * @param {Espo.Ui~PopoverOptions} o Options. * @param {module:view} [view] A view. + * @return {{hide: function(), destroy: function(), show: function(), detach: function()}} */ popover: function (element, o, view) { - let $el = $(element); - - let $body = $('body') - let content = o.content || Handlebars.Utils.escapeExpression(o.text || ''); + const $el = $(element); + const $body = $('body'); + const content = o.content || Handlebars.Utils.escapeExpression(o.text || ''); let isShown = false; let container = o.container; if (!container) { - let $modalBody = $el.closest('.modal-body'); + const $modalBody = $el.closest('.modal-body'); container = $modalBody.length ? $modalBody : 'body'; } @@ -929,25 +930,27 @@ Espo.Ui = { return; } - $body.off('click.popover-' + view.cid); - - $body.on('click.popover-' + view.cid, e => { - if ($(e.target).closest('.popover-content').get(0)) { - return; - } - - if ($.contains($el.get(0), e.target)) { - return; - } - - if ($el.get(0) === e.target) { - return; - } - + if (view && !o.noHideOnOutsideClick) { $body.off('click.popover-' + view.cid); - // noinspection JSUnresolvedReference - $el.popover('hide'); - }); + + $body.on('click.popover-' + view.cid, e => { + if ($(e.target).closest('.popover-content').get(0)) { + return; + } + + if ($.contains($el.get(0), e.target)) { + return; + } + + if ($el.get(0) === e.target) { + return; + } + + $body.off('click.popover-' + view.cid); + // noinspection JSUnresolvedReference + $el.popover('hide'); + }); + } if (o.onShow) { o.onShow(); @@ -968,26 +971,46 @@ Espo.Ui = { }); } - if (view) { - let hide = () => { - if (!isShown) { - return; - } + let isDetached = false; - // noinspection JSUnresolvedReference - $el.popover('hide'); - }; - - let destroy = () => { - // noinspection JSUnresolvedReference - $el.popover('destroy'); + const detach = () => { + if (view) { $body.off('click.popover-' + view.cid); view.off('remove', destroy); view.off('render', destroy); view.off('render', hide); - }; + } + isDetached = true; + }; + + const destroy = () => { + if (isDetached) { + return; + } + + // noinspection JSUnresolvedReference + $el.popover('destroy'); + + detach(); + }; + + const hide = () => { + if (!isShown) { + return; + } + + // noinspection JSUnresolvedReference + $el.popover('hide'); + }; + + const show = () => { + // noinspection JSUnresolvedReference + $el.popover('show'); + }; + + if (view) { view.once('remove', destroy); if (!o.preventDestroyOnRender) { @@ -998,6 +1021,13 @@ Espo.Ui = { view.on('render', hide); } } + + return { + hide: () => hide(), + destroy: () => destroy(), + show: () => show(), + detach: () => detach(), + }; }, /** diff --git a/client/src/ui/select.js b/client/src/ui/select.js index ed3659cf72..b5c15e0b79 100644 --- a/client/src/ui/select.js +++ b/client/src/ui/select.js @@ -74,21 +74,22 @@ const Select = { * @param {Element|JQuery} element An element. * @param {module:ui/select~Options} [options] Options. */ - init: function (element, options) { - let $el = $(element); + init: function (element, options = {}) { + const score = options.score; + const $el = $(element); options = Select.applyDefaultOptions(options || {}); - let plugins = []; + const plugins = []; Select.loadEspoSelectPlugin(); plugins.push('espo_select'); - let itemClasses = {}; + const itemClasses = {}; - let allowedValues = $el.children().toArray().map(item => { - let value = item.getAttributeNode('value').value; + const allowedValues = $el.children().toArray().map(item => { + const value = item.getAttributeNode('value').value; if (item.classList) { itemClasses[value] = item.classList.toString(); @@ -99,13 +100,13 @@ const Select = { let $relativeParent = null; - let $modalBody = $el.closest('.modal-body'); + const $modalBody = $el.closest('.modal-body'); if ($modalBody.length) { $relativeParent = $modalBody; } - let selectizeOptions = { + const selectizeOptions = { sortField: [{field: options.sortBy, direction: options.sortDirection}], load: options.load, loadThrottle: 1, @@ -125,7 +126,7 @@ const Select = { .get(0).outerHTML; }, option: function (data) { - let $div = $('