diff --git a/application/Espo/Core/Controllers/Record.php b/application/Espo/Core/Controllers/Record.php index a44892c7de..e8e088ee33 100644 --- a/application/Espo/Core/Controllers/Record.php +++ b/application/Espo/Core/Controllers/Record.php @@ -231,27 +231,39 @@ class Record extends Base public function actionCreateLink($params, $data) { + if (empty($params['id']) || empty($params['link'])) { + throw BadRequest(); + } + $id = $params['id']; $link = $params['link']; - $foreignIds = array(); - if (isset($data['id'])) { - $foreignIds[] = $data['id']; - } - if (isset($data['ids']) && is_array($data['ids'])) { - foreach ($data['ids'] as $foreignId) { - $foreignIds[] = $foreignId; + if (!empty($data['massRelate'])) { + if (empty($data['where'])) { + throw new BadRequest(); + } + $where = json_decode(json_encode($data['where']), true); + return $this->getRecordService()->linkEntityMass($id, $link, $where); + } else { + $foreignIds = array(); + if (isset($data['id'])) { + $foreignIds[] = $data['id']; + } + if (isset($data['ids']) && is_array($data['ids'])) { + foreach ($data['ids'] as $foreignId) { + $foreignIds[] = $foreignId; + } } - } - $result = false; - foreach ($foreignIds as $foreignId) { - if ($this->getRecordService()->linkEntity($id, $link, $foreignId)) { - $result = $result || true; + $result = false; + foreach ($foreignIds as $foreignId) { + if ($this->getRecordService()->linkEntity($id, $link, $foreignId)) { + $result = true; + } + } + if ($result) { + return true; } - } - if ($result) { - return true; } throw new Error(); @@ -262,6 +274,10 @@ class Record extends Base $id = $params['id']; $link = $params['link']; + if (empty($params['id']) || empty($params['link'])) { + throw BadRequest(); + } + $foreignIds = array(); if (isset($data['id'])) { $foreignIds[] = $data['id']; diff --git a/application/Espo/Core/Utils/Database/Schema/Converter.php b/application/Espo/Core/Utils/Database/Schema/Converter.php index 04ae39eeea..7da2a448d6 100644 --- a/application/Espo/Core/Utils/Database/Schema/Converter.php +++ b/application/Espo/Core/Utils/Database/Schema/Converter.php @@ -241,13 +241,20 @@ class Converter $table->addColumn('id', 'int', array('length'=>$this->defaultLength['int'], 'autoincrement' => true, 'notnull' => true,)); //'unique' => true, //add midKeys to a schema + $uniqueIndex = array(); foreach($relationParams['midKeys'] as $index => $midKey) { $usMidKey = Util::toUnderScore($midKey); $table->addColumn($usMidKey, $this->idParams['dbType'], array('length'=>$this->idParams['len'])); $table->addIndex(array($usMidKey)); - } //END: add midKeys to a schema + $uniqueIndex[] = $usMidKey; + } + + if (!empty($uniqueIndex)) { + $table->addUniqueIndex($uniqueIndex); + } + //END: add midKeys to a schema //add additionalColumns if (isset($relationParams['additionalColumns'])) { diff --git a/application/Espo/ORM/DB/Mapper.php b/application/Espo/ORM/DB/Mapper.php index 12b2623ff5..fdf1562586 100644 --- a/application/Espo/ORM/DB/Mapper.php +++ b/application/Espo/ORM/DB/Mapper.php @@ -372,6 +372,69 @@ abstract class Mapper implements IMapper } } + public function massRelate(IEntity $entity, $relationName, array $params = array()) + { + if (!$entity) { + return false; + } + $id = $entity->id; + + if (empty($id) || empty($relationName)) { + return false; + } + + $relOpt = $entity->relations[$relationName]; + + if (!isset($relOpt['entity']) || !isset($relOpt['type'])) { + throw new \LogicException("Not appropriate defenition for relationship {$relationName} in " . $entity->getEntityType() . " entity"); + } + + $relType = $relOpt['type']; + + $className = (!empty($relOpt['class'])) ? $relOpt['class'] : $relOpt['entity']; + $relEntity = $this->entityFactory->create($className); + $foreignEntityType = $relEntity->getEntityType(); + + $keySet = $this->query->getKeys($entity, $relationName); + + switch ($relType) { + case IEntity::MANY_MANY: + $key = $keySet['key']; + $foreignKey = $keySet['foreignKey']; + $nearKey = $keySet['nearKey']; + $distantKey = $keySet['distantKey']; + + $relTable = $this->toDb($relOpt['relationName']); + + + $params['select'] = array('id'); + $subSql = $this->query->createSelectQuery($foreignEntityType, $params); + + $fieldsPart = $this->toDb($nearKey); + $valuesPart = $this->pdo->quote($entity->id); + + if (!empty($relOpt['conditions']) && is_array($relOpt['conditions'])) { + foreach ($relOpt['conditions'] as $f => $v) { + $fieldsPart .= ", " . $this->toDb($f); + $valuesPart .= ", " . $this->pdo->quote($v); + } + } + $fieldsPart .= ", " . $this->toDb($distantKey); + + $subSql = substr($subSql, 7); + + $subSql = "SELECT " . $valuesPart . ", " . $subSql; + + $sql = "INSERT INTO `".$relTable."` (".$fieldsPart.") (".$subSql.") ON DUPLICATE KEY UPDATE deleted = '0'"; + + if ($this->pdo->query($sql)) { + return true; + } + + break; + } + } + public function addRelation(IEntity $entity, $relationName, $id = null, $relEntity = null, $data = null) { if (!is_null($relEntity)) { diff --git a/application/Espo/ORM/Repositories/RDB.php b/application/Espo/ORM/Repositories/RDB.php index 10d549f3c9..fbc186729b 100644 --- a/application/Espo/ORM/Repositories/RDB.php +++ b/application/Espo/ORM/Repositories/RDB.php @@ -274,6 +274,10 @@ class RDB extends \Espo\ORM\Repository return false; } + public function massRelate(Entity $entity, $relationName, array $params = array()) + { + return $this->getMapper()->massRelate($entity, $relationName, $params); + } public function getAll() { diff --git a/application/Espo/Repositories/EmailAddress.php b/application/Espo/Repositories/EmailAddress.php index 0b26092b53..1ae307b99b 100644 --- a/application/Espo/Repositories/EmailAddress.php +++ b/application/Espo/Repositories/EmailAddress.php @@ -237,8 +237,7 @@ class EmailAddress extends \Espo\Core\ORM\Repositories\RDB $emailAddress = $this->getByAddress($address); if ($emailAddress) { $query = " - UPDATE entity_email_address - SET `deleted` = 1, `primary` = 0 + DELETE FROM entity_email_address WHERE entity_id = ".$pdo->quote($entity->id)." AND entity_type = ".$pdo->quote($entity->getEntityName())." AND diff --git a/application/Espo/Repositories/PhoneNumber.php b/application/Espo/Repositories/PhoneNumber.php index 312ef86358..ca7b0b8083 100644 --- a/application/Espo/Repositories/PhoneNumber.php +++ b/application/Espo/Repositories/PhoneNumber.php @@ -18,7 +18,7 @@ * * You should have received a copy of the GNU General Public License * along with EspoCRM. If not, see http://www.gnu.org/licenses/. - ************************************************************************/ + ************************************************************************/ namespace Espo\Repositories; @@ -27,8 +27,8 @@ use Espo\ORM\Entity; class PhoneNumber extends \Espo\Core\ORM\Repositories\RDB { public function getIds($arr = array()) - { - $ids = array(); + { + $ids = array(); if (!empty($arr)) { $a = array_map(function ($item) { return $item; @@ -58,19 +58,19 @@ class PhoneNumber extends \Espo\Core\ORM\Repositories\RDB } return $ids; } - + public function getPhoneNumberData(Entity $entity) { $data = array(); - - $pdo = $this->getEntityManager()->getPDO(); + + $pdo = $this->getEntityManager()->getPDO(); $sql = " - SELECT phone_number.name, phone_number.type, entity_phone_number.primary + SELECT phone_number.name, phone_number.type, entity_phone_number.primary FROM entity_phone_number JOIN phone_number ON phone_number.id = entity_phone_number.phone_number_id AND phone_number.deleted = 0 - WHERE - entity_phone_number.entity_id = ".$pdo->quote($entity->id)." AND - entity_phone_number.entity_type = ".$pdo->quote($entity->getEntityName())." AND + WHERE + entity_phone_number.entity_id = ".$pdo->quote($entity->id)." AND + entity_phone_number.entity_type = ".$pdo->quote($entity->getEntityName())." AND entity_phone_number.deleted = 0 ORDER BY entity_phone_number.primary DESC "; @@ -82,112 +82,111 @@ class PhoneNumber extends \Espo\Core\ORM\Repositories\RDB $obj->phoneNumber = $row['name']; $obj->primary = ($row['primary'] == '1') ? true : false; $obj->type = $row['type']; - + $data[] = $obj; } } - + return $data; } - + public function getByNumber($number) { return $this->where(array('name' => $number))->findOne(); } - + public function storeEntityPhoneNumber(Entity $entity) { $phone = trim($entity->get('phoneNumber')); $phoneNumberData = null; - + if ($entity->has('phoneNumberData')) { $phoneNumberData = $entity->get('phoneNumberData'); } - - $pdo = $this->getEntityManager()->getPDO(); - + + $pdo = $this->getEntityManager()->getPDO(); + if ($phoneNumberData !== null && is_array($phoneNumberData)) { $previousPhoneNumberData = array(); if (!$entity->isNew()) { $previousPhoneNumberData = $this->getPhoneNumberData($entity); } - + $hash = array(); foreach ($phoneNumberData as $row) { $key = $row->phoneNumber; if (!empty($key)) { $hash[$key] = array( 'primary' => $row->primary ? true : false, - 'type' => $row->type, + 'type' => $row->type ); } } - + $hashPrev = array(); foreach ($previousPhoneNumberData as $row) { $key = $row->phoneNumber; if (!empty($key)) { $hashPrev[$key] = array( 'primary' => $row->primary ? true : false, - 'type' => $row->type, + 'type' => $row->type, ); } - } - - $primary = false; + } + + $primary = false; $toCreate = array(); - $toUpdate = array(); + $toUpdate = array(); $toRemove = array(); - + foreach ($hash as $key => $data) { $new = true; $changed = false; - + if ($hash[$key]['primary']) { $primary = $key; } - + if (array_key_exists($key, $hashPrev)) { $new = false; - $changed = $hash[$key]['type'] != $hashPrev[$key]['type']; + $changed = $hash[$key]['type'] != $hashPrev[$key]['type']; if ($hash[$key]['primary']) { if ($hash[$key]['primary'] == $hashPrev[$key]['primary']) { $primary = false; } - } + } } - + if ($new) { $toCreate[] = $key; - } + } if ($changed) { $toUpdate[] = $key; - } + } } - - foreach ($hashPrev as $key => $data) { + + foreach ($hashPrev as $key => $data) { if (!array_key_exists($key, $hash)) { $toRemove[] = $key; } } - + foreach ($toRemove as $number) { $phoneNumber = $this->getByNumber($number); if ($phoneNumber) { $query = " - UPDATE entity_phone_number - SET `deleted` = 1, `primary` = 0 + DELETE FROM entity_phone_number WHERE entity_id = ".$pdo->quote($entity->id)." AND entity_type = ".$pdo->quote($entity->getEntityName())." AND phone_number_id = ".$pdo->quote($phoneNumber->id)." "; $sth = $pdo->prepare($query); - $sth->execute(); + $sth->execute(); } } - + foreach ($toUpdate as $number) { $phoneNumber = $this->getByNumber($number); if ($phoneNumber) { @@ -197,16 +196,16 @@ class PhoneNumber extends \Espo\Core\ORM\Repositories\RDB $this->save($phoneNumber); } } - + foreach ($toCreate as $number) { $phoneNumber = $this->getByNumber($number); if (!$phoneNumber) { $phoneNumber = $this->get(); - + $phoneNumber->set(array( 'name' => $number, 'type' => $hash[$number]['type'], - )); + )); $this->save($phoneNumber); } else { if ($phoneNumber->get('type') != $hash[$number]['type']) { @@ -216,9 +215,9 @@ class PhoneNumber extends \Espo\Core\ORM\Repositories\RDB $this->save($phoneNumber); } } - + $query = " - INSERT entity_phone_number + INSERT entity_phone_number (entity_id, entity_type, phone_number_id, `primary`) VALUES ( @@ -231,7 +230,7 @@ class PhoneNumber extends \Espo\Core\ORM\Repositories\RDB $sth = $pdo->prepare($query); $sth->execute(); } - + if ($primary) { $phoneNumber = $this->getByNumber($primary); if ($phoneNumber) { @@ -241,12 +240,12 @@ class PhoneNumber extends \Espo\Core\ORM\Repositories\RDB WHERE entity_id = ".$pdo->quote($entity->id)." AND entity_type = ".$pdo->quote($entity->getEntityName())." AND - `primary` = 1 AND + `primary` = 1 AND deleted = 0 "; $sth = $pdo->prepare($query); $sth->execute(); - + $query = " UPDATE entity_phone_number SET `primary` = 1 @@ -259,9 +258,7 @@ class PhoneNumber extends \Espo\Core\ORM\Repositories\RDB $sth = $pdo->prepare($query); $sth->execute(); } - } - - + } } else { $entityRepository = $this->getEntityManager()->getRepository($entity->getEntityName()); if (!empty($phone)) { @@ -271,11 +268,11 @@ class PhoneNumber extends \Espo\Core\ORM\Repositories\RDB $isNewPhoneNumber = false; if (!$phoneNumberNew) { $phoneNumberNew = $this->get(); - $phoneNumberNew->set('name', $phone); + $phoneNumberNew->set('name', $phone); $defaultType = $this->getEntityManager()->getEspoMetadata()->get('entityDefs.' . $entity->getEntityName() . '.fields.phoneNumber.defaultType'); - + $phoneNumberNew->set('type', $defaultType); - + $this->save($phoneNumberNew); $isNewPhoneNumber = true; } diff --git a/application/Espo/Resources/i18n/en_US/Global.json b/application/Espo/Resources/i18n/en_US/Global.json index 24427eb13b..9193071d15 100644 --- a/application/Espo/Resources/i18n/en_US/Global.json +++ b/application/Espo/Resources/i18n/en_US/Global.json @@ -140,7 +140,8 @@ "Close": "Close", "Yes": "Yes", "No": "No", - "View": "View" + "View": "View", + "Select All Result": "Select All Result" }, "messages": { "notModified": "You have not modified the record", diff --git a/application/Espo/Services/Record.php b/application/Espo/Services/Record.php index 65f5aed428..ef51e72cce 100644 --- a/application/Espo/Services/Record.php +++ b/application/Espo/Services/Record.php @@ -598,6 +598,34 @@ class Record extends \Espo\Core\Services\Base } } + public function linkEntityMass($id, $link, $where) + { + $entity = $this->getEntity($id); + + $entityType = $entity->getEntityType(); + $foreignEntityType = $entity->relations[$link]['entity']; + + if (!$this->getAcl()->check($entity, 'edit')) { + throw new Forbidden(); + } + if (empty($foreignEntityType)) { + throw new Error(); + } + if (!$this->getAcl()->check($foreignEntityType, 'edit')) { + throw new Forbidden(); + } + + if (!is_array($where)) { + $where = array(); + } + $params['where'] = $where; + + + $selectParams = $this->getRecordService($foreignEntityType)->getSelectParams($params); + + return $this->getRepository()->massRelate($entity, $link, $selectParams); + } + public function massUpdate($attributes = array(), $ids = array(), $where = array()) { $idsUpdated = array(); diff --git a/frontend/client/res/templates/record/list.tpl b/frontend/client/res/templates/record/list.tpl index de63cc8c22..c716551381 100644 --- a/frontend/client/res/templates/record/list.tpl +++ b/frontend/client/res/templates/record/list.tpl @@ -32,10 +32,22 @@ {{#if checkboxes}} - + + + {{#if checkAllResultEnabled}} +
+ + + + +
+ {{/if}} + {{/if}} {{#each headerDefs}} - + {{#if this.sortable}} {{#if this.hasCustomLabel}} diff --git a/frontend/client/src/search-manager.js b/frontend/client/src/search-manager.js index e85b6b6c85..32a8f754ef 100644 --- a/frontend/client/src/search-manager.js +++ b/frontend/client/src/search-manager.js @@ -27,12 +27,18 @@ this.type = type || 'list'; this.dateTime = dateTime; - this.data = this.defaultData = defaultData || { + this.emptyData = { textFilter: '', bool: {}, advanced: {}, }; + if (defaultData) { + defaultData = Espo.Utils.clone(defaultData); + } + + this.data = this.defaultData = defaultData || this.emptyData; + this.sanitizeData(); }; @@ -127,7 +133,7 @@ }, loadStored: function () { - this.data = this.storage.get(this.type + 'Search', this.scope) || _.clone(this.defaultData); + this.data = this.storage.get(this.type + 'Search', this.scope) || Espo.Utils.clone(this.defaultData); this.sanitizeData(); return this; }, @@ -137,6 +143,7 @@ }, setAdvanced: function (advanced) { + this.data = Espo.Utils.clone(this.data); this.data.advanced = advanced; }, @@ -147,8 +154,15 @@ } }, + empty: function () { + this.data = Espo.Utils.clone(this.emptyData); + if (this.storage) { + this.storage.clear(this.type + 'Search', this.scope); + } + }, + reset: function () { - this.data = _.clone(this.defaultData); + this.data = Espo.Utils.clone(this.defaultData); if (this.storage) { this.storage.clear(this.type + 'Search', this.scope); } diff --git a/frontend/client/src/views/detail.js b/frontend/client/src/views/detail.js index 45dce58f8b..52ecdd5b92 100644 --- a/frontend/client/src/views/detail.js +++ b/frontend/client/src/views/detail.js @@ -223,13 +223,25 @@ Espo.define('Views.Detail', 'Views.Main', function (Dep) { actionSelectRelated: function (data) { var link = data.link; + + if (!this.model.defs['links'][link]) { + throw new Error('Link ' + link + ' does not exist.'); + } var scope = this.model.defs['links'][link].entity; + var foreign = this.model.defs['links'][link].foreign; + + var massRelateEnabled = false; + if (foreign) { + var foreignType = this.getMetadata().get('entityDefs.' + scope + '.links.' + foreign + '.type'); + if (foreignType == 'hasMany') { + massRelateEnabled = true; + } + } var self = this; - var attributes = {}; - var filters = this.selectRelatedFilters[link] || null; + var filters = Espo.Utils.cloneDeep(this.selectRelatedFilters[link]) || null; for (var filterName in filters) { if (typeof filters[filterName] == 'function') { @@ -243,6 +255,7 @@ Espo.define('Views.Detail', 'Views.Main', function (Dep) { multiple: true, createButton: false, filters: filters, + massRelateEnabled: massRelateEnabled }, function (dialog) { dialog.render(); this.notify(false); @@ -255,7 +268,12 @@ Espo.define('Views.Detail', 'Views.Main', function (Dep) { }); data.ids = ids; } else { - data.id = selectObj.id; + if (selectObj.massRelate) { + data.massRelate = true; + data.where = selectObj.where; + } else { + data.id = selectObj.id; + } } $.ajax({ url: self.scope + '/' + self.model.id + '/' + link, diff --git a/frontend/client/src/views/modals/select-records.js b/frontend/client/src/views/modals/select-records.js index 3622de93bb..249b8c9dd4 100644 --- a/frontend/client/src/views/modals/select-records.js +++ b/frontend/client/src/views/modals/select-records.js @@ -59,6 +59,8 @@ Espo.define('Views.Modals.SelectRecords', 'Views.Modal', function (Dep) { this.createButton = this.options.createButton; } + this.massRelateEnabled = this.options.massRelateEnabled; + this.buttons = [ { name: 'cancel', @@ -66,7 +68,7 @@ Espo.define('Views.Modals.SelectRecords', 'Views.Modal', function (Dep) { onClick: function (dialog) { dialog.close(); } - } + } ]; if (this.multiple) { @@ -75,9 +77,19 @@ Espo.define('Views.Modals.SelectRecords', 'Views.Modal', function (Dep) { style: 'primary', label: 'Select', onClick: function (dialog) { - var list = this.getView('list').getSelected(); - if (list.length) { - this.trigger('select', list); + var listView = this.getView('list'); + + if (listView.allResultIsChecked) { + var where = this.collection.where; + this.trigger('select', { + massRelate: true, + where: where + }); + } else { + var list = listView.getSelected(); + if (list.length) { + this.trigger('select', list); + } } dialog.close(); }.bind(this), @@ -100,6 +112,8 @@ Espo.define('Views.Modals.SelectRecords', 'Views.Modal', function (Dep) { collection.maxSize = this.getConfig().get('recordsPerPageSmall') || 5; + this.collection = collection; + var searchManager = new SearchManager(collection, 'listSelect', null, this.getDateTime()); searchManager.setAdvanced(this.filters); collection.where = searchManager.getWhere(); @@ -121,6 +135,7 @@ Espo.define('Views.Modals.SelectRecords', 'Views.Modal', function (Dep) { rowActionsView: false, type: 'listSmall', searchManager: searchManager, + checkAllResultEnabled: this.massRelateEnabled }, function (list) { list.once('select', function (model) { this.trigger('select', model); diff --git a/frontend/client/src/views/record/list.js b/frontend/client/src/views/record/list.js index 650a4f6e65..a1c2a84e47 100644 --- a/frontend/client/src/views/record/list.js +++ b/frontend/client/src/views/record/list.js @@ -84,7 +84,7 @@ Espo.define('Views.Record.List', 'View', function (Dep) { this.collection.once('sync', function () { this.notify(false); this.trigger('sort', {sortBy: field, asc: asc}); - }.bind(this)); + }, this); this.collection.sort(field, asc); this.deactivate(); }, @@ -112,18 +112,21 @@ Espo.define('Views.Record.List', 'View', function (Dep) { var id = target.data('id'); this._checkRecord(id, e.currentTarget.checked, target); }, - 'click .selectAll': function (e) { + 'click .select-all': function (e) { this.checkedList = []; if (e.currentTarget.checked) { this.$el.find('input.record-checkbox').prop('checked', true); this.$el.find('.actions-button').removeAttr('disabled'); - _.each(this.collection.models, function (model) { + this.collection.models.forEach(function (model) { this.checkedList.push(model.id); - }.bind(this)); + }, this); this.$el.find('.list > table tbody tr').addClass('active'); } else { + if (this.allResultIsChecked) { + this.unselectAllResult(); + } this.$el.find('input.record-checkbox').prop('checked', false); this.$el.find('.actions-button').attr('disabled', true); this.$el.find('.list > table tbody tr').removeClass('active'); @@ -142,6 +145,9 @@ Espo.define('Views.Record.List', 'View', function (Dep) { var data = $target.data(); this.quickRemove(id, data); }, + 'click .checkbox-dropdown [data-action="selectAllResult"]': function (e) { + this.selectAllResult(); + }, }, actions: [], @@ -177,6 +183,10 @@ Espo.define('Views.Record.List', 'View', function (Dep) { checkedList: null, + checkAllResultEnabled: false, + + allResultIsChecked: false, + data: function () { var paginationTop = this.pagination === 'both' || this.pagination === true || this.pagination === 'top'; var paginationBottom = this.pagination === 'both' || this.pagination === true || this.pagination === 'bottom'; @@ -197,6 +207,7 @@ Espo.define('Views.Record.List', 'View', function (Dep) { rows: this.rows, topBar: paginationTop || this.checkboxes, bottomBar: paginationBottom, + checkAllResultEnabled: this.checkAllResultEnabled }; }, @@ -209,6 +220,24 @@ Espo.define('Views.Record.List', 'View', function (Dep) { this.selectable = _.isUndefined(this.options.selectable) ? this.selectable : this.options.selectable; this.rowActionsView = _.isUndefined(this.options.rowActionsView) ? this.rowActionsView : this.options.rowActionsView; this.showMore = _.isUndefined(this.options.showMore) ? this.showMore : this.options.showMore; + + if ('checkAllResultEnabled' in this.options) { + this.checkAllResultEnabled = this.options.checkAllResultEnabled; + } + }, + + selectAllResult: function () { + this.allResultIsChecked = true; + + this.$el.find('input.record-checkbox').prop('checked', true).attr('disabled', 'disabled'); + this.$el.find('input.select-all').prop('checked', true); + }, + + unselectAllResult: function () { + this.allResultIsChecked = false; + + this.$el.find('input.record-checkbox').prop('checked', false).removeAttr('disabled'); + this.$el.find('input.select-all').prop('checked', false); }, deactivate: function () { @@ -417,6 +446,8 @@ Espo.define('Views.Record.List', 'View', function (Dep) { this.noRebuild = null; return; } + this.checkedList = []; + this.allResultIsChecked = false; this.buildRows(function () { this.render(); }.bind(this)); @@ -711,6 +742,10 @@ Espo.define('Views.Record.List', 'View', function (Dep) { } $showMore.children('a').removeClass('disabled'); + if (this.allResultIsChecked) { + this.$el.find('input.record-checkbox').attr('disabled', 'disabled').prop('checked', true); + } + this.notify(false); }.bind(this); diff --git a/frontend/client/src/views/record/search.js b/frontend/client/src/views/record/search.js index 01cc2e5f52..ff3f27615f 100644 --- a/frontend/client/src/views/record/search.js +++ b/frontend/client/src/views/record/search.js @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with EspoCRM. If not, see http://www.gnu.org/licenses/. - ************************************************************************/ + ************************************************************************/ Espo.define('Views.Record.Search', 'View', function (Dep) { @@ -65,14 +65,14 @@ Espo.define('Views.Record.Search', 'View', function (Dep) { return this.fields != null && this.moreFields != null; }.bind(this)); - this.boolFilters = this.getMetadata().get('clientDefs.' + this.scope + '.boolFilters') || []; + this.boolFilters = Espo.Utils.clone(this.getMetadata().get('clientDefs.' + this.scope + '.boolFilters') || []); this._helper.layoutManager.get(this.scope, 'filters', function (list) { this.moreFields = list; this.tryReady(); }.bind(this)); - this.presetFilters = _.clone(this.getMetadata().get('clientDefs.' + this.scope + '.presetFilters') || []); + this.presetFilters = Espo.Utils.clone(this.getMetadata().get('clientDefs.' + this.scope + '.presetFilters') || []); ((this.getPreferences().get('presetFilters') || {})[this.scope] || []).forEach(function (item) { this.presetFilters.push(item); }, this); @@ -224,7 +224,7 @@ Espo.define('Views.Record.Search', 'View', function (Dep) { this.presetName = null; - this.searchManager.reset(); + this.searchManager.empty(); this.loadSearchData(); this.render(); diff --git a/frontend/less/espo/custom.less b/frontend/less/espo/custom.less index 6b55b1cd85..0d0790eb22 100644 --- a/frontend/less/espo/custom.less +++ b/frontend/less/espo/custom.less @@ -85,7 +85,12 @@ div.list-expanded > ul > li > div.expanded-row > .cell:first-child { border-left: 0; } -table.table > thead th a { +.list .checkbox-dropdown > a { + padding: 0; + margin-top: -8px; +} + +table.table > thead th > a { color: @gray-light; } diff --git a/tests/Espo/ORM/DB/MapperTest.php b/tests/Espo/ORM/DB/MapperTest.php index 9fc5354492..25b7aad6ac 100644 --- a/tests/Espo/ORM/DB/MapperTest.php +++ b/tests/Espo/ORM/DB/MapperTest.php @@ -1,4 +1,4 @@ -entityFactory = $this->getMockBuilder('\\Espo\\ORM\\EntityFactory')->disableOriginalConstructor()->getMock(); $this->entityFactory->expects($this->any()) @@ -64,7 +64,7 @@ class DBMapperTest extends PHPUnit_Framework_TestCase })); $this->query = new Query($this->pdo, $this->entityFactory); - + $this->db = new MysqlMapper($this->pdo, $this->entityFactory, $this->query); $this->db->setReturnCollection(true); @@ -432,6 +432,21 @@ class DBMapperTest extends PHPUnit_Framework_TestCase $this->assertEquals($value, 10); } + + public function testMassRelate() + { + $query = "INSERT INTO `post_tag` (post_id, tag_id) (SELECT '1', tag.id AS `id` FROM `tag` WHERE tag.name = 'test' AND tag.deleted = '0') ON DUPLICATE KEY UPDATE deleted = '0'"; + $return = true; + $this->mockQuery($query, $return); + + $this->post->id = '1'; + + $this->db->massRelate($this->post, 'tags', array( + 'whereClause' => array( + 'name' => 'test' + ) + )); + } }