Files
ERP/sophal/js/view/view.search.js

857 lines
20 KiB
JavaScript

/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var ui = angular.module('axelor.ui');
ui.controller('SearchViewCtrl', SearchViewCtrl);
SearchViewCtrl.$inject = ['$scope', '$element', '$http', 'DataSource', 'ViewService', 'MenuService'];
function SearchViewCtrl($scope, $element, $http, DataSource, ViewService, MenuService) {
var view = $scope._views.search || {};
$scope._dataSource = DataSource.create('multi-search');
$scope.$applyAsync(function(){
if (view.deferred)
view.deferred.resolve($scope);
});
function fixFields(fields) {
_.each(fields, function(field){
if (field.type == 'reference') {
field.type = 'MANY_TO_ONE';
field.canNew = false;
field.canEdit = false;
}
if (field.type)
field.type = field.type.toUpperCase();
else
field.type = 'STRING';
});
return fields;
}
$scope.show = function(viewPromise) {
if (!viewPromise) {
viewPromise = $scope.loadView('search', view.name);
viewPromise.success(function(fields, schema){
$scope.initView(schema);
});
}
$scope.onShow(viewPromise);
};
$scope.onShow = function() {
};
$scope.initView = function(schema) {
var params = $scope._viewParams;
$scope._searchFields = fixFields(schema.searchFields);
$scope._resultFields = fixFields(schema.resultFields);
$scope._searchView = schema;
$scope._showSingle = params.params && params.params.showSingle;
$scope._forceEdit = params.params && params.params.forceEdit;
$scope._hideActions = params.params && params.params.hideActions;
$scope.updateRoute();
if (params.options && params.options.mode == "search") {
$scope.setRouteOptions(params.options);
}
};
$scope.getRouteOptions = function() {
var args = [],
query = $scope._routeSearch;
return {
mode: 'search',
args: args,
query: query
};
};
$scope._routeSearch = null;
var onNewCalled = false;
$scope.setRouteOptions = function(options) {
var opts = options || {},
fields = $scope._searchFields || [],
search = opts.search,
record = {};
var changed = !angular.equals($scope._routeSearch, search);
$scope._routeSearch = search;
if (!onNewCalled && _.isEmpty(search)) {
onNewCalled = true;
scopes.form.$broadcast('on:new');
}
if (!search || _.isEmpty(search) || !changed) {
return $scope.updateRoute();
}
_.each(fields, function(field) {
var value = search[field.name];
if (value === undefined) {
return;
}
if (field.target) {
if (value) {
record[field.name] = {id: +value};
}
} else {
record[field.name] = value;
}
});
if (search.objects) {
scopes.toolbar.editRecord({
objectSelect: search.objects
});
}
scopes.form.editSearch(record, fields);
function _doSearch() {
var promise = $scope.doSearch();
if (promise && promise.then && $scope._showSingle) {
promise.then(function () {
var items = scopes.grid.getItems();
if (items && items.length === 1) {
scopes.grid.selection = [0];
scopes.grid.onEdit();
}
});
}
}
var promise = scopes.toolbar._viewPromise;
if (promise && promise.then) {
promise.then(function() {
$scope.$timeout(_doSearch);
});
} else {
_doSearch();
}
};
var scopes = {};
$scope._register = function(key, scope) {
scopes[key] = scope;
};
$scope.doSearch = function() {
var params = _.extend({}, scopes.form.record),
empty = _.chain(params).values().compact().isEmpty().value();
if (empty)
return $scope.doClear();
var selected = (scopes.toolbar.record || {}).objectSelect;
_.extend(params,{
__name: view.name,
__selected: _.isEmpty(selected) ? null : selected.split(/,\s*/)
});
var promise = $http.post('ws/search', {
limit: view.limit || 80,
data: params
});
return promise.then(function(response){
var res = response.data,
records = res.data || [];
// slickgrid expects unique `id` so generate them and store original one
_.each(records, function(rec, i){
rec._id = rec.id;
rec.id = i + 1;
});
scopes.grid.setItems(records);
if (scopes.form.$events.onLoad) {
scopes.form.record._count = records.length;
scopes.form.record._countByModels = _.countBy(records, function(rec) {
return rec._model;
});
scopes.form.$events.onLoad();
}
if (_.isEmpty(records)) {
axelor.notify.info(_t("No records found."));
}
});
};
$scope.doClear = function(all) {
scopes.form.edit(null);
scopes.form.$broadcast('on:new');
scopes.grid.setItems([]);
if (all) {
scopes.toolbar.edit(null);
scopes.toolbar.doReset();
}
};
$scope.doAction = function() {
var action = scopes.toolbar.getMenuAction();
if (!action) {
return;
}
var grid = scopes.grid,
index = _.first(grid.selection),
record = grid.getItem(index);
action = action.action;
record = _.extend({
_action: action
}, record);
record.id = record._id;
MenuService.action(action).success(function(result){
if (!result.data) {
return;
}
var view = result.data[0].view;
var tab = view;
tab.action = _.uniqueId('$act');
tab.viewType = 'form';
tab.context = _.extend({}, tab.context, {
_ref : record
});
$scope.openTab(tab);
});
};
}
ui.controller('SearchFormCtrl', SearchFormCtrl);
SearchFormCtrl.$inject = ['$scope', '$element', 'ViewService'];
function SearchFormCtrl($scope, $element, ViewService) {
ui.FormViewCtrl.call(this, $scope, $element);
$scope._register('form', $scope);
$scope.setEditable();
// prevent requesting defaults
$scope.defaultValues = {};
$scope.$watch('_searchView', function searchSchemaWatch(schema) {
if (!schema) return;
var form = {
title: 'Search',
type: 'form',
cols: 1,
items: [{
type: 'panel',
title: schema.title,
items: schema.searchFields
}]
};
var meta = { fields: schema.searchFields };
ViewService.process(meta, schema.searchForm);
function process(item) {
if (item.items || item.pages) {
return _.each(item.items || item.pages, process);
}
switch (item.widget) {
case 'ManyToOne':
case 'OneToOne':
case 'SuggestBox':
item.canNew = false;
item.canEdit = false;
break;
case 'OneToMany':
case 'ManyToMany':
case 'MasterDetail':
item.hidden = true;
}
}
if (schema.searchForm && schema.searchForm.items) {
_.each(schema.searchForm.items, process);
}
$scope.fields = meta.fields;
$scope.schema = schema.searchForm || form;
$scope.schema.loaded = true;
});
var model = null;
var getContext = $scope.getContext;
$scope.getContext = function() {
var view = $scope._searchView || {};
if (model === null && view.selects) {
model = (_.first(view.selects) || {}).model;
}
var ctx = getContext.apply(this, arguments) || {};
ctx._model = model;
return ctx;
};
$scope.editSearch = function (record, fields) {
$scope.editRecord(record);
setTimeout(function () {
_.each(fields, function (field) {
if (!field.target || !$scope.record) return;
var item = $element.find('[x-field=' + field.name + ']');
var itemScope = item.data('$scope');
var value = itemScope.getValue();
if (value && itemScope && !itemScope.text && itemScope.select) {
itemScope.select(value);
}
});
});
};
}
ui.controller('SearchGridCtrl', SearchGridCtrl);
SearchGridCtrl.$inject = ['$scope', '$element', 'ViewService', '$interpolate'];
function SearchGridCtrl($scope, $element, ViewService, $interpolate) {
ui.GridViewCtrl.call(this, $scope, $element);
$scope._register('grid', $scope);
var viewTitles = {};
$scope.$watch('_searchView', function searchSchemaWatch(schema) {
if (!schema) return;
var view = {
title: 'Search',
type: 'grid',
editIcon: true,
items: []
};
var objItem = _.findWhere(schema.resultFields, {name: 'object'});
if (!objItem) {
view.items.push(objItem = {});
}
objItem = _.extend(objItem, { name : '_modelTitle', title: _t('Object') });
view.items = view.items.concat(schema.resultFields);
if (+(objItem.width) === 0) {
objItem.hidden = true;
}
var meta = { fields: schema.resultFields };
ViewService.process(meta);
_.each(schema.selects, function (select) {
viewTitles[select.model] = select.viewTitle;
});
_.each(view.items, function (item) {
if (item.width) {
objItem.width = objItem.width || 220;
}
});
$scope.fields = meta.fields;
$scope.schema = view;
$scope.schema.loaded = true;
});
$scope.onEdit = function(force) {
var index = _.first(this.selection),
records = this.getItems(),
record = this.getItem(index),
ids, domain, views;
ids = _.chain(records).filter(function(rec){
return rec._model == record._model;
}).pluck('_id').value();
domain = "self.id IN (" + ids.join(',') + ")";
views = _.map(['form', 'grid'], function(type){
var view = { type : type };
var name = record["_" + type];
if (name) view.name = name;
return view;
});
if (force === undefined) {
force = $scope._forceEdit;
}
var title = viewTitles[record._model];
if (title) {
title = $interpolate(title)(record);
}
var tab = {
action: _.uniqueId('$act'),
model: record._model,
title: title || record._modelTitle,
forceTitle: true,
domain: domain,
recordId: record._id,
forceEdit: force,
viewType: 'form',
views: views
};
this.openTab(tab);
};
$scope.onSort = function(event, args) {
var grid = args.grid;
var data = grid.getData();
var sortCols = args.sortCols;
var types = {};
_.each($scope.fields, function (field) {
types[field.name] = field.type;
});
data.sort(function(dataRow1, dataRow2) {
for (var i = 0, l = sortCols.length; i < l; i++) {
var name = sortCols[i].sortCol.field;
var sign = sortCols[i].sortAsc ? 1 : -1;
var value1 = dataRow1[name], value2 = dataRow2[name];
switch (types[name]) {
case "integer":
case "long":
value1 = value1 || 0;
value2 = value2 || 0;
break;
default:
value1 = value1 || "";
value2 = value2 || "";
}
var result = (value1 == value2 ? 0 : (value1 > value2 ? 1 : -1)) * sign;
if (result) {
return result;
}
}
return 0;
});
grid.invalidate();
grid.render();
};
}
ui.controller('SearchToolbarCtrl', SearchToolbarCtrl);
SearchToolbarCtrl.$inject = ['$scope', '$element', '$http'];
function SearchToolbarCtrl($scope, $element, $http) {
ui.FormViewCtrl.call(this, $scope, $element);
$scope._register('toolbar', $scope);
$scope.setEditable();
var menus = {};
function fetch(key, parent, request, response) {
if (menus[key]) {
return response(menus[key]);
}
var promise = $http.get('ws/search/menu', {
params: {
parent: parent
}
});
promise.then(function(res){
var data = res.data.data;
data = _.map(data, function(item){
return {
value: item.name,
action: item.action,
label: item.title
};
});
menus[key] = data;
response(data);
});
}
$scope.fetchRootMenus = function(request, response) {
fetch('menuRoot', null, request, response);
};
$scope.fetchSubMenus = function(request, response) {
fetch('menuSub', $scope.record.menuRoot, request, response);
};
$scope.fetchItemMenus = function(request, response) {
fetch('menuItem', $scope.record.menuSub, request, response);
};
$scope.resetSelector = function(a, b) {
_.each(arguments, function(name){
if (_.isString(name)) {
$scope.record[name] = null;
menus[name] = null;
}
});
};
$scope.getMenuAction = function() {
return _.find(menus.menuItem, function(item){
return item.value === $scope.record.menuItem;
});
};
$scope.$watch('_searchView', function searchSchemaWatch(schema) {
if (!schema) {
return;
}
var selected = [];
$scope.fields = {
'objectSelect' : {
type : 'string',
placeholder: _t('Search Objects'),
multiple : true,
selectionList : _.map(schema.selects, function(x) {
if (x.selected) {
selected.push(x.model);
}
return {
value : x.model,
title : x.title
};
})
},
'menuRoot' : {
type : 'string',
placeholder: _t('Action Category'),
widget: 'select-query',
attrs: {
query: 'fetchRootMenus',
'ng-change': 'resetSelector("menuSub", "menuItem")'
}
},
'menuSub' : {
placeholder: _t('Action Sub-Category'),
widget: 'select-query',
attrs: {
query: 'fetchSubMenus',
'ng-change': 'resetSelector($event, "menuItem")'
}
},
'menuItem' : {
placeholder: _t('Action'),
widget: 'select-query',
attrs: {
query: 'fetchItemMenus'
}
}
};
var items1 = [{
name : 'objectSelect',
showTitle : false,
colSpan: 8
}, {
type : 'button',
title : _t('Search'),
colSpan: 2,
attrs: {
'ng-click': 'doSearch()'
}
}, {
type : 'button',
title : _t('Clear'),
colSpan: 2,
attrs: {
'ng-click': 'doClear(true)'
}
}];
var items2 = [{
name : 'menuRoot',
showTitle : false,
colSpan: 3
}, {
name : 'menuSub',
showTitle : false,
colSpan: 3
}, {
name : 'menuItem',
showTitle : false,
colSpan: 4
}, {
type : 'button',
title : _t('Go'),
colSpan: 2,
attrs: {
'ng-click': 'doAction()'
}
}];
var item1 = {
type: "panel",
colSpan: $scope._hideActions ? 12 : 6,
items: items1
};
var item2 = {
type: "panel",
colSpan: 6,
items: items2
};
schema = {
type : 'form',
items: [{
type: 'panel',
items: $scope._hideActions ? [item1] : [item1, item2]
}]
};
$scope.schema = schema;
$scope.schema.loaded = true;
$scope.doReset = function () {
var record = $scope.record || {};
if (selected.length > 0 && _.isEmpty(record.objectSelect)) {
record.objectSelect = selected.join(', ');
$scope.edit(record);
}
};
$scope.$timeout($scope.doReset);
});
}
angular.module('axelor.ui').directive('uiViewSearch', function(){
return {
controller: SearchViewCtrl,
link: function(scope, element, attrs, ctrl) {
element.on('keypress', '.search-view-form form:first', function(event){
if (event.keyCode == 13 && $(event.target).is('input')){
scope.doSearch();
}
});
var grid = element.children('.search-view-grid');
scope.$onAdjust(function(){
if (!element.is(':visible'))
return;
grid.height(element.height() - grid.position().top);
});
}
};
});
// ActionSelector (TODO: re-use search view toolbar)
ActionSelectorCtrl.$inject = ['$scope', '$element', '$attrs', '$http', 'MenuService'];
function ActionSelectorCtrl($scope, $element, $attrs, $http, MenuService) {
ui.FormViewCtrl.call(this, $scope, $element);
var menus = {},
category = $attrs.category;
function fetch(key, request, response, params) {
if (menus[key]) {
return response(menus[key]);
}
var promise = $http.get('ws/search/menu', {
params: params
});
promise.then(function(res){
var data = res.data.data;
data = _.map(data, function(item){
return {
value: item.name,
action: item.action,
label: item.title
};
});
menus[key] = data;
response(data);
});
}
$scope.fetchRootMenus = function(request, response) {
fetch('$menuRoot', request, response, {
parent: '',
category: category
});
};
$scope.fetchSubMenus = function(request, response) {
if (!$scope.record.$menuRoot) return;
fetch('$menuSub', request, response, {
parent: $scope.record.$menuRoot
});
};
$scope.fetchItemMenus = function(request, response) {
if (!$scope.record.$menuSub) return;
fetch('$menuItem', request, response, {
parent: $scope.record.$menuSub
});
};
$scope.resetSelector = function(a, b) {
_.each(arguments, function(name){
if (_.isString(name)) {
$scope.record[name] = null;
menus[name] = null;
}
});
};
$scope.getMenuAction = function() {
return _.find(menus.$menuItem, function(item){
return item.value === $scope.record.$menuItem;
});
};
$scope.doAction = function() {
var action = $scope.getMenuAction();
if (!action) {
return;
}
var context = $scope.$parent.getContext(),
record;
action = action.action;
record = {
id : context.id,
_action: action,
_model: context._model
};
MenuService.action(action).success(function(result){
if (!result.data) {
return;
}
var view = result.data[0].view;
var tab = view;
tab.action = _.uniqueId('$act');
tab.viewType = 'form';
tab.context = _.extend({}, tab.context, {
_ref : record
});
$scope.openTab(tab);
});
};
$scope.fields = {
'$menuRoot' : {
type : 'string',
placeholder: _t('Action Category'),
widget: 'select-query',
attrs: {
query: 'fetchRootMenus',
'ng-change': 'resetSelector("$menuSub", "$menuItem")'
}
},
'$menuSub' : {
placeholder: _t('Action Sub-Category'),
type: 'select-query',
attrs: {
query: 'fetchSubMenus',
'ng-change': 'resetSelector($event, "$menuItem")'
}
},
'$menuItem' : {
placeholder: _t('Action'),
widget: 'select-query',
attrs: {
query: 'fetchItemMenus'
}
}
};
$scope.schema = {
cols : 4,
colWidths : '30%,30%,30%,10%',
type : 'form',
items : [ {
name : '$menuRoot',
showTitle : false
}, {
name : '$menuSub',
showTitle : false
}, {
name : '$menuItem',
showTitle : false
}, {
type : 'button',
title : _t('Go'),
attrs: {
'ng-click': 'doAction()'
}
} ]
};
}
angular.module('axelor.ui').directive('uiActionSelector', function(){
return {
scope: true,
controller: ActionSelectorCtrl,
template: '<div ui-view-form x-handler="this"></div>'
};
});
})();