/*
* Pim
* Free Extension
* Copyright (c) TreoLabs GmbH
* Copyright (c) Kenner Soft Service GmbH
*
* This program 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.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*/
Espo.define('pim:views/product-family/record/panels/product-family-attributes', ['views/record/panels/relationship', 'views/record/panels/bottom', 'search-manager'],
(Dep, BottomPanel, SearchManager) => Dep.extend({
template: 'pim:product-family/record/panels/product-family-attributes',
groupKey: 'attributeGroupId',
groupLabel: 'attributeGroupName',
groupScope: 'AttributeGroup',
noGroup: {
key: 'no_group',
label: 'No Group'
},
boolFilterData: {
notLinkedProductFamilyAttributes() {
return {
productFamilyId: this.model.id,
scope: 'Global'
}
}
},
events: _.extend({
'click [data-action="unlinkAttributeGroup"]': function(e) {
e.preventDefault();
e.stopPropagation();
let data = $(e.currentTarget).data();
this.unlinkAttributeGroup(data);
}
}, Dep.prototype.events),
data() {
return _.extend({
groups: this.groups,
groupScope: this.groupScope
}, Dep.prototype.data.call(this));
},
setup() {
let bottomPanel = new BottomPanel();
bottomPanel.setup.call(this);
this.link = this.link || this.defs.link || this.panelName;
if (!this.scope && !(this.link in this.model.defs.links)) {
throw new Error('Link \'' + this.link + '\' is not defined in model \'' + this.model.name + '\'');
}
this.title = this.title || this.translate(this.link, 'links', this.model.name);
this.scope = this.scope || this.model.defs.links[this.link].entity;
if (!this.getConfig().get('scopeColorsDisabled')) {
var iconHtml = this.getHelper().getScopeColorIconHtml(this.scope);
if (iconHtml) {
if (this.defs.label) {
this.titleHtml = iconHtml + this.translate(this.defs.label, 'labels', this.scope);
} else {
this.titleHtml = iconHtml + this.title;
}
}
}
var url = this.url || this.model.name + '/' + this.model.id + '/' + this.link;
if (!this.readOnly && !this.defs.readOnly) {
if (!('create' in this.defs)) {
this.defs.create = true;
}
if (!('select' in this.defs)) {
this.defs.select = true;
}
}
this.filterList = this.defs.filterList || this.filterList || null;
if (this.filterList && this.filterList.length) {
this.filter = this.getStoredFilter();
}
if (this.defs.create && this.getAcl().check('ProductFamilyAttribute', 'create')) {
this.buttonList.push({
title: 'Create',
action: this.defs.createAction || 'createRelated',
link: this.link,
acl: 'create',
aclScope: this.scope,
html: '<span class="fas fa-plus"></span>',
data: {
link: this.link,
}
});
}
if (this.defs.select && this.getAcl().check('ProductFamilyAttribute', 'create')) {
var data = {link: this.link};
if (this.defs.selectPrimaryFilterName) {
data.primaryFilterName = this.defs.selectPrimaryFilterName;
}
if (this.defs.selectBoolFilterList) {
data.boolFilterList = this.defs.selectBoolFilterList;
}
data.boolFilterListCallback = 'getSelectBoolFilterList';
data.boolFilterDataCallback = 'getSelectBoolFilterData';
data.afterSelectCallback = 'createProductFamilyAttribute';
data.scope = 'Attribute';
this.actionList.unshift({
label: 'Select',
action: this.defs.selectAction || 'selectRelated',
data: data,
acl: 'edit',
aclScope: this.model.name
});
if (this.getAcl().check('AttributeGroup', 'read')) {
this.actionList.push({
label: 'Select Attribute Group',
action: 'selectAttributeGroup'
});
}
}
this.setupActions();
var layoutName = 'listSmall';
this.setupListLayout();
if (this.listLayoutName) {
layoutName = this.listLayoutName;
}
var listLayout = null;
var layout = this.defs.layout || null;
if (layout) {
if (typeof layout == 'string') {
layoutName = layout;
} else {
layoutName = 'listRelationshipCustom';
listLayout = layout;
}
}
this.layoutName = layoutName;
this.listLayout = listLayout;
var sortBy = this.defs.sortBy || null;
var asc = this.defs.asc || null;
if (this.defs.orderBy) {
sortBy = this.defs.orderBy;
asc = true;
if (this.defs.orderDirection) {
if (this.defs.orderDirection && (this.defs.orderDirection === true || this.defs.orderDirection.toLowerCase() === 'DESC')) {
asc = false;
}
}
}
this.wait(true);
this.getCollectionFactory().create(this.scope, function (collection) {
collection.maxSize = 200;
if (this.defs.filters) {
var searchManager = new SearchManager(collection, 'listRelationship', false, this.getDateTime());
searchManager.setAdvanced(this.defs.filters);
collection.where = searchManager.getWhere();
}
collection.url = collection.urlRoot = url;
if (sortBy) {
collection.sortBy = sortBy;
}
if (asc) {
collection.asc = asc;
}
this.collection = collection;
this.setFilter(this.filter);
this.listenTo(this.model, 'update-all after:relate after:unrelate', () => {
this.actionRefresh();
});
this.listenTo(collection, 'change:isRequired', model => {
if (!model.hasChanged('modifiedAt')) {
this.notify('Saving...');
model.save({isRequired: model.get('isRequired')}, {patch: true}).then(() => {
this.notify('Saved', 'success');
});
}
});
this.fetchCollectionGroups(() => this.wait(false));
}, this);
this.setupFilterActions();
},
createProductFamilyAttribute(selectObj) {
let promises = [];
selectObj.forEach(attributeModel => {
this.getModelFactory().create(this.scope, model => {
model.setRelate({
model: this.model,
link: this.model.defs.links[this.link].foreign
});
model.setRelate({
model: attributeModel,
link: attributeModel.defs.links[this.link].foreign
});
model.set({
assignedUserId: this.getUser().id,
assignedUserName: this.getUser().get('name'),
scope: 'Global'
});
promises.push(model.save());
});
});
Promise.all(promises).then(() => {
this.notify('Linked', 'success');
this.actionRefresh();
});
},
actionSelectAttributeGroup() {
const scope = 'AttributeGroup';
const viewName = this.getMetadata().get(['clientDefs', scope, 'modalViews', 'select']) || 'views/modals/select-records';
this.notify('Loading...');
this.createView('dialog', viewName, {
scope: scope,
multiple: true,
createButton: false,
massRelateEnabled: false,
boolFilterList: ['withNotLinkedAttributesToProductFamily'],
boolFilterData: {withNotLinkedAttributesToProductFamily: this.model.id},
whereAdditional: [
{
type: 'isLinked',
attribute: 'attributes'
}
]
}, dialog => {
dialog.render();
this.notify(false);
dialog.once('select', selectObj => {
if (!Array.isArray(selectObj)) {
return;
}
let boolFilterList = this.getSelectBoolFilterList() || [];
this.getFullEntityList('Attribute', {
where: [
{
type: 'bool',
value: boolFilterList,
data: this.getSelectBoolFilterData(boolFilterList)
},
{
attribute: 'attributeGroupId',
type: 'in',
value: selectObj.map(model => model.id)
}
]
}, list => {
let models = [];
list.forEach(attributes => {
this.getModelFactory().create('Attribute', model => {
model.set(attributes);
models.push(model);
});
});
this.createProductFamilyAttribute(models);
});
});
});
},
getFullEntityList(url, params, callback, container) {
if (url) {
container = container || [];
let options = params || {};
options.maxSize = options.maxSize || 200;
options.offset = options.offset || 0;
this.ajaxGetRequest(url, options).then(response => {
container = container.concat(response.list || []);
options.offset = container.length;
if (response.total > container.length || response.total === -1) {
this.getFullEntity(url, options, callback, container);
} else {
callback(container);
}
});
}
},
afterRender() {
Dep.prototype.afterRender.call(this);
this.buildGroups();
},
fetchCollectionGroups(callback) {
this.getHelper().layoutManager.get(this.scope, this.layoutName, layout => {
let list = [];
layout.forEach(item => {
if (item.name) {
let field = item.name;
let fieldType = this.getMetadata().get(['entityDefs', this.scope, 'fields', field, 'type']);
if (fieldType) {
this.getFieldManager().getAttributeList(fieldType, field).forEach(attribute => {
list.push(attribute);
});
}
}
});
this.collection.data.select = list.join(',');
this.collection.reset();
this.fetchCollectionPart(() => {
this.groups = [];
this.groups = this.getGroupsFromCollection();
let valueKeys = this.groups.map(group => group.key);
this.getCollectionFactory().create('AttributeGroup', collection => {
this.attributeGroupCollection = collection;
collection.select = 'sortOrder';
collection.maxSize = 200;
collection.offset = 0;
collection.whereAdditional = [
{
attribute: 'id',
type: 'in',
value: valueKeys
}
];
collection.fetch().then(() => {
let orderArray = [];
let noGroup;
this.groups.forEach(item => {
if (item.key === 'no_group') {
item.sortOrder = 0;
noGroup = item;
} else {
this.attributeGroupCollection.forEach(model => {
if (model.id === item.key) {
item.sortOrder = model.get('sortOrder');
}
});
}
orderArray.push(item.sortOrder);
});
if (noGroup) {
noGroup.sortOrder = Math.max(...orderArray) + 1;
}
this.groups.sort(function(a, b) {
return a.sortOrder - b.sortOrder ;
});
if (callback) {
callback();
}
});
});
});
});
},
fetchCollectionPart(callback) {
this.collection.fetch({remove: false, more: true}).then((response) => {
if (this.collection.total > this.collection.length) {
this.fetchCollectionPart(callback);
} else if (callback) {
callback();
}
});
},
getGroupsFromCollection() {
let groups = [];
this.collection.forEach(model => {
// prepare key
let key = model.get(this.groupKey);
if (key === null || typeof key === 'undefined') {
key = this.noGroup.key;
}
// prepare label
let label = model.get(this.groupLabel);
if (label === null || typeof label === 'undefined') {
label = this.translate(this.noGroup.label, 'labels', 'Global');
}
// prepare is inherited param
let isInherited = model.get('isInherited');
if (isInherited === null || typeof isInherited === 'undefined') {
isInherited = false;
}
let group = groups.find(item => item.key === key);
if (group) {
group.rowList.push(model.id);
group.rowList.sort((a, b) => this.collection.get(a).get('sortOrder') - this.collection.get(b).get('sortOrder'));
group.editable = (!group.editable) ? !isInherited : group.editable;
} else {
groups.push({
key: key,
id: key !== this.noGroup.key ? key : null,
label: label,
rowList: [model.id],
editable: !isInherited
});
}
});
return groups;
},
buildGroups() {
this.groups.forEach(group => {
this.getCollectionFactory().create(this.scope, collection => {
group.rowList.forEach(id => {
collection.add(this.collection.get(id));
});
collection.url = `ProductFamily/${this.model.id}/productFamilyAttributes`;
collection.where = [
{
type: 'bool',
value: ['linkedWithAttributeGroup'],
data: {
linkedWithAttributeGroup: {
productFamilyId: this.model.id,
attributeGroupId: group.key !== 'no_group' ? group.key : null
}
}
}
];
collection.data.select = 'attributeId,attributeName,value,valueEnUs,valueDeDe,scope,channelsIds,channelsNames';
this.listenTo(collection, 'sync', () => {
collection.models.sort((a, b) => a.get('sortOrder') - b.get('sortOrder'));
});
let viewName = this.defs.recordListView || this.getMetadata().get('clientDefs.' + this.scope + '.recordViews.list') || 'Record.List';
this.createView(group.key, viewName, {
collection: collection,
layoutName: this.layoutName,
listLayout: this.listLayout,
checkboxes: false,
rowActionsView: this.defs.readOnly ? false : (this.defs.rowActionsView || this.rowActionsView),
buttonsDisabled: true,
el: `${this.options.el} .group[data-name="${group.key}"] .list-container`,
showMore: false
}, view => {
this.listenTo(view, 'after:render', () => {
(view.rowList || []).forEach(id => {
const rowView = view.getView(id);
if (rowView) {
const fieldView = rowView.getView('isRequiredField');
if (fieldView && !rowView.model.get('isInherited')) {
fieldView.setMode('edit');
fieldView.reRender();
}
}
});
});
view.render();
});
});
});
},
getSelectBoolFilterData(boolFilterList) {
let data = {};
if (Array.isArray(boolFilterList)) {
boolFilterList.forEach(item => {
if (this.boolFilterData && typeof this.boolFilterData[item] === 'function') {
data[item] = this.boolFilterData[item].call(this);
}
});
}
return data;
},
getSelectBoolFilterList() {
return this.defs.selectBoolFilterList || null
},
actionRefresh() {
this.fetchCollectionGroups(() => this.reRender());
},
unlinkAttributeGroup(data) {
let id = data.id;
if (!id) {
return;
}
let group = this.groups.find(group => group.id === id);
if (!group || !group.rowList) {
return;
}
// prepare ids
let ids = [];
group.rowList.forEach(id => {
if (!this.collection.get(id).get('isInherited') && this.collection.get(id).get('locale') === null){
ids.push(id);
}
});
if (!ids) {
return;
}
this.confirm({
message: this.translate('unlinkAttributeGroupConfirmation', 'messages', 'AttributeGroup'),
confirmText: this.translate('Unlink')
}, function () {
this.notify('Unlinking...');
$.ajax({
url: `${this.model.name}/${this.link}/relation`,
data: JSON.stringify({
ids: [this.model.id],
foreignIds: ids
}),
type: 'DELETE',
contentType: 'application/json',
success: function () {
this.notify('Unlinked', 'success');
this.model.trigger('after:unrelate');
this.actionRefresh();
}.bind(this),
error: function () {
this.notify('Error occurred', 'error');
}.bind(this),
});
}, this);
},
actionRemoveRelated: function (data) {
var id = data.id;
this.ajaxGetRequest(`ProductFamily/${this.model.id}/productsCount`, {attributeId: id}).then(response => {
Espo.TreoUi.confirmWithBody('', {
message: this.translate('removeRecordConfirmation', 'messages'),
confirmText: this.translate('Remove'),
cancelText: this.translate('Cancel'),
body: this.getUnlinkHtml(response)
}, function () {
var model = this.collection.get(id);
this.notify('Removing...');
$.ajax({
url: 'ProductFamilyAttribute/' + id,
type: 'DELETE',
contentType: 'application/json',
success: function () {
this.notify('Removed', 'success');
this.collection.fetch();
this.model.trigger('after:unrelate');
}.bind(this),
error: function () {
this.notify('Error occurred', 'error');
}.bind(this),
});
}, this);
});
},
getUnlinkHtml(count) {
return `
<div class="row">
<div class="col-xs-12">
<span class="confirm-message">${this.translate('removeRecordConfirmation', 'messages')}</span>
</div>
<div class="col-xs-12">
<div style="margin-top: 15px;">
<span class="product-counts-message">${this.translate('productsCountWithAttribute', 'messages', 'Attribute').replace('{count}', count)}</span>
</div>
</div>
</div>`;
}
})
);
|