Files
ERP/sophal/js/axelor.data.js

1068 lines
28 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 extend = angular.extend,
isArray = angular.isArray,
isObject = angular.isObject,
forEach = angular.forEach,
push = [].push;
var ds = angular.module('axelor.ds');
ds.factory('DataSource', ['$injector', '$rootScope', '$http', '$q', '$exceptionHandler', function($injector, $rootScope, $http, $q, $exceptionHandler) {
function DataSource(model, options) {
var opts = extend({
limit : DataSource.DEFAULT_LIMIT,
domain : null,
context : null
}, options);
this._model = model;
this._domain = opts.domain;
this._context = opts.context;
this._filter = null;
this._sortBy = null;
this._lastDomain = null;
this._lastContext = null;
this._lastAction = null;
this._showArchived = opts.archived;
if (opts.archived === undefined && _.has(opts.params || {}, 'showArchived')) {
this._showArchived = opts.params.showArchived;
}
this._data = [];
this._page = {
index : 0,
from : 0,
to : 0,
size : 0,
total : 0,
limit : opts.limit
};
this._listeners = {};
}
DataSource.DEFAULT_LIMIT = 40;
DataSource.prototype = {
constructor: DataSource,
_http: $http,
_request: function(action, id) {
var url = 'ws/rest/' + this._model;
if (id) url += '/' + id;
if (action) url += '/' + action;
return {
get: function(data, config) {
return $http.get(url, data, config);
},
post: function(data, config) {
return $http.post(url, data, config);
},
send: function (config) {
return $http(_.extend({}, config, {
url: url
}));
}
};
},
_new: function(model, options) {
return new DataSource(model, options);
},
on: function(name, listener) {
var listeners = this._listeners[name];
if(!listeners){
this._listeners[name] = listeners = [];
}
listeners.push(listener);
return function() {
var index = listeners.indexOf(listener);
if (index >= 0)
listeners.splice(index, 1);
return listener;
};
},
trigger: function(name) {
var listeners = this._listeners[name] || [],
event = {
name : name,
target: this
},
listenerArgs = [event];
listenerArgs = listenerArgs.concat([].slice.call(arguments, 1));
forEach(listeners, function(listener) {
try {
listener.apply(null, listenerArgs);
} catch (e) {
$exceptionHandler(e);
}
});
},
/**
* Check whether two objects are equal.
*
* It compares relational field values by it's id if version property
* is missing (assuming, they are not modified).
*
*/
equals: function(a, b) {
if (a === b) return true;
if (a === null || b === null) return false;
if (a !== a && b !== b) return true; // NaN === NaN
function isWindow(obj) {
return obj && obj.document && obj.location && obj.alert && obj.setInterval;
}
function isScope(obj) {
return obj && obj.$evalAsync && obj.$watch;
}
function compact(obj) {
if (!obj || !_.isObject(obj)) return obj;
if (isScope(obj) || isWindow(obj)) return obj;
if (_.isArray(obj)) return _.map(obj, compact).sort();
if (_.isDate(obj)) return obj;
if (obj.id > 0 && !obj.$dirty && !_.has(obj, "version")) {
return obj.id;
}
var res = {};
_.each(obj, function(v, k) {
if (k.substring(0, 2) === '__') return;
if (k === 'selected') return; // selected is transient field
res[k] = compact(v);
});
res.id = res.id || res.$id; // make sure to use dummy id
return res;
}
return angular.equals(compact(a), compact(b));
},
/**
* Return the difference between two records.
*
* The relational fields are compared by it's id if version
* property is missing (assuming, the are not modified).
*
* @param {Object} a - source record
* @param {Object} b - original record
*
* @returns {Object}
*/
diff: function(a, b) {
if (a === b) return a;
if (a === null || b === null) return a;
if (!a && !b) return a;
if (!a.id || a.id < 1) return a;
var result = _.pick(a, 'id', 'version'),
that = this;
_.each(a, function (value, key) {
if (!that.equals(value, b[key])) {
result[key] = value;
}
});
return result;
},
rpc: function(method, options) {
var params = _.extend({
model: this._model,
domain: this._domain,
context: this._lastContext
}, options);
var promise = $http.post('ws/action/' + method, {
model : this._model,
data : params
});
promise.success = function(fn){
promise.then(function(response){
fn(response.data);
});
return promise;
};
promise.error = function(fn){
promise.then(null, fn);
return promise;
};
return promise;
},
// if search count is less than current page boundary, adjust the page and do search again
fixPage: function () {
var page = this._page;
if (page.from != 0 && page.total != 0 && page.from >= page.total) {
page.from = page.total - (page.total % page.limit);
page.from = page.from === page.total ? page.from - page.limit : page.from;
page.from = Math.max(0, page.from);
page.to = Math.min(page.from + page.limit, page.total);
return this.search();
}
},
search: function(options) {
var opts = _.extend({
store: true
}, options);
var limit = opts.limit === undefined ? this._page.limit : opts.limit;
var offset = opts.offset === undefined ? this._page.from : opts.offset;
var domain = opts.domain === undefined ? (this._lastDomain || this._domain) : opts.domain;
var context = opts.context === undefined ? (this._lastContext || this._context) : opts.context;
var action = opts.action === undefined ? this._lastAction : opts.action;
var archived = opts.archived === undefined ? this._showArchived : opts.archived;
var fields = _.isEmpty(opts.fields) ? null : opts.fields;
var filter = opts.filter || this._filter;
var sortBy = opts.sortBy || this._sortBy;
if (opts.store) {
this._filter = filter;
this._sortBy = sortBy;
this._lastDomain = domain;
this._lastContext = context;
this._lastAction = action;
if (opts.archived !== undefined) {
this._showArchived = opts.archived;
}
}
offset = offset || 0;
context = _.extend({}, this._context, context);
//sophal
if(context._model != undefined){
if(context._model =='com.axelor.apps.suppliermanagement.db.PurchaseOrderSupplierLine'){
var limit = null ;
}
}
//sophal
var query = extend({
_domain: domain,
_domainContext: context,
_domainAction: action || undefined,
_archived: archived
}, filter);
var that = this,
page = this._page,
records = this._data,
params = {
fields: fields,
sortBy: sortBy,
data: query,
limit: limit,
offset: offset,
parent: opts.parent
};
var promise = this._request('search').post(params);
promise = promise.then(function(response){
var res = response.data;
var length = (res.data||[]).length;
res.offset = offset;
res.data = _.unique(res.data, function (a) { return a.id; });
if (opts.store) {
that._accept(res);
page.index = -1;
} else {
records = res.data || [];
page = that._pageInfo(res);
}
if (length !== page.size) {
page.total -= (length - page.size);
}
});
promise.success = function(fn){
promise.then(function(res){
fn(records, page);
});
return promise;
};
promise.error = function(fn){
promise.then(null, fn);
return promise;
};
return promise;
},
at: function(index) {
return this._data[index];
},
get: function(id) {
for (var i = 0; i < this._data.length; i++) {
if (this._data[i].id === id) {
return this._data[i];
}
}
},
/**
* Read the object with the given id.
*
* If options are provided then POST request is used otherwise GET is used.
* @param id record id
* @param options request options
* @returns promise
*/
read: function(id, options) {
var promise, record;
if (options) {
promise = this._request('fetch', id).post({
fields: options.fields,
related: options.related
});
} else {
promise = this._request(null, id).get();
}
promise.then(function(response){
var res = response.data;
record = res.data;
if (isArray(record)) {
record = record[0];
}
});
promise.success = function(fn){
promise.then(function(response){
fn(record);
});
return promise;
};
promise.error = function(fn) {
promise.then(null, fn);
return promise;
};
return promise;
},
// find followers of the given record
followers: function (id) {
var promise = this._request('followers', id).get();
promise.success = function(fn) {
promise.then(function(res) {
fn(res.data);
});
return promise;
};
promise.error = function(fn) {
promise.then(null, fn);
return promise;
};
return promise;
},
// find messages for the given record
messages: function (options) {
var opts = _.extend({}, options);
var promise = this._request('messages').send({
method: 'GET',
params: opts,
silent: opts.count ? true : undefined,
transformRequest: opts.count ? [] : undefined
});
promise.success = function(fn) {
promise.then(function(response) {
fn(response.data);
});
return promise;
};
promise.error = function(fn) {
promise.then(null, fn);
return promise;
};
return promise;
},
messageCount: function (options) {
var opts = _.extend({}, options, {
count: true
});
return this.messages(opts);
},
messagePost: function (id, text, options) {
var opts = _.extend({}, options);
var data = _.extend({
body : text,
subject: opts.subject,
type: opts.type,
parent: opts.parent,
files: opts.files,
recipients: opts.recipients
});
var promise = this._request('message', id).post({
data: data
});
promise.success = function(fn) {
promise.then(function(response){
fn(response.data);
});
return promise;
};
promise.error = function(fn) {
promise.then(null, fn);
return promise;
};
return promise;
},
messageFollow: function (id, options) {
var opts = _.extend({}, options);
var promise = this._request('follow', id).post({
data: opts.email
});
promise.success = function(fn) {
promise.then(function(response){
fn(response.data);
});
return promise;
};
promise.error = function(fn) {
promise.then(null, fn);
return promise;
};
return promise;
},
messageUnfollow: function (id, options) {
var opts = _.extend({}, options);
var promise = this._request('unfollow', id).post({
records: opts.records
});
promise.success = function(fn) {
promise.then(function(response){
fn(response.data);
});
return promise;
};
promise.error = function(fn) {
promise.then(null, fn);
return promise;
};
return promise;
},
isPermitted: function(action, values) {
var data = _.extend({}, {
id: values && values.id,
action: action
});
var promise = this._request("perms").get({
params: data
});
promise.success = function(fn) {
promise.then(function(response){
fn(response.data);
});
return promise;
};
promise.error = function(fn) {
promise.then(null, fn);
return promise;
};
return promise;
},
verify: function(values) {
var promise = this._request("verify").post({
data: values
});
promise.success = function(fn) {
promise.then(function(response){
fn(response.data);
});
return promise;
};
promise.error = function(fn) {
promise.then(null, fn);
return promise;
};
return promise;
},
upload: function(values, field, file) {
var that = this,
page = this._page,
record = null,
deferred = $q.defer(),
promise = deferred.promise;
var indicator = $injector.get('httpIndicator');
promise = promise.then(indicator.response, indicator.responseError);
var xhr = new XMLHttpRequest();
var data = new FormData();
var progress_cb = angular.noop;
promise.then(function(response) {
var res = response.data;
res.data = res.data[0];
record = that._accept(res);
});
promise.progress = function(fn) {
progress_cb = fn;
return promise;
};
promise.success = function(fn) {
promise.then(function(response) {
fn(record, page);
});
return promise;
};
promise.error = function(fn) {
promise.then(null, function(response){
fn(response.data);
});
return promise;
};
var request = {
data: values
};
data.append("file", file);
data.append("field", field);
data.append("request", angular.toJson(request));
xhr.upload.addEventListener("progress", function(e) {
var complete = Math.round(e.loaded * 100 / e.total);
progress_cb(complete);
}, false);
_.each($http.defaults.transformRequest, function(tr) {
tr(data);
});
xhr.onerror = deferred.reject;
xhr.onabort = deferred.reject;
xhr.onload = function () {
var data = angular.fromJson(xhr.response || xhr.responseText);
var response = {
data: data,
status: xhr.status
};
if (xhr.status == 200) {
deferred.resolve(response);
} else {
deferred.reject(response);
}
$rootScope.$applyAsync();
};
xhr.open("POST", "ws/rest/" + this._model + "/upload", true);
xhr.send(data);
return promise;
},
save: function(values) {
var that = this,
page = this._page,
record, promise;
if (values && values.$upload) {
var upload = values.$upload;
return this.upload(values, upload.field, upload.file);
}
promise = this._request().post({
data: values
});
promise.then(function(response){
var res = response.data;
res.data = res.data[0];
record = that._accept(res);
that.trigger('on:save', [values]);
$rootScope.$broadcast('ds:saved', that);
});
promise.success = function(fn) {
promise.then(function(response){
fn(record, page);
});
return promise;
};
promise.error = function(fn) {
promise.then(null, fn);
return promise;
};
return promise;
},
saveAll: function(items, fields) {
var that = this,
page = this._page,
records = this._data,
promise = this._request().post({
fields: fields,
records: items
});
promise.then(function(response){
var res = response.data;
_.each(res.data || [], function(item){
var found = _.find(records, function(rec){
if (rec.id === item.id) {
angular.copy(item, rec);
return true;
}
});
if (!found) {
records.push(item);
page.total += 1;
page.size += 1;
}
});
var i = 0;
while(i < records.length) {
var rec = records[i];
if (rec.id === 0) {
records.splice(i, 1);
break;
}
i ++;
}
that.trigger('change', records, page);
that.trigger('on:save', items);
$rootScope.$broadcast('ds:saved', that);
});
promise.success = function(fn) {
promise.then(function(response){
fn(records, page);
});
return promise;
};
promise.error = function(fn) {
promise.then(null, fn);
return promise;
};
return promise;
},
updateMass: function (values, ids) {
var domain = this._lastDomain;
var context = this._lastContext;
var filter = this._filter;
var items = _.compact(ids);
if (items.length > 0) {
if (domain) {
domain = domain + " AND self.id IN (:__ids__)";
} else {
domain = "self.id IN (:__ids__)";
}
context = _.extend({
__ids__: items
}, context);
}
var query = extend({
_domain: domain,
_domainContext: context,
_archived: this._showArchived
}, filter);
var promise = this._request('updateMass').post({
records: [values],
sortBy: this._sortBy,
data: query
});
promise.success = function(fn) {
promise.then(function(response){
var res = response.data;
fn(res);
});
return promise;
};
promise.error = function(fn) {
promise.then(null, fn);
return promise;
};
return promise;
},
remove: function(record) {
var that = this,
page = this._page,
records = this._data,
promise = this._request('remove', record.id).post({
data: record
});
promise = promise.then(function(reponse){
var index = -1;
for(var i = 0 ; i < records.length ; i++) {
if (records[i].id == record.id) {
index = i;
break;
}
}
if (index > -1) {
records.splice(index, 1);
page.total -= 1;
page.size -= 1;
}
that.trigger('change', records, page);
that.trigger('on:remove', [record]);
$rootScope.$broadcast('ds:removed', that);
});
promise.success = function(fn) {
promise.then(function(response){
fn(records, page);
});
return promise;
};
promise.error = function(fn) {
promise.then(null, fn);
return promise;
};
return promise;
},
removeAll: function(selection) {
var that = this,
page = this._page,
records = this._data,
promise;
selection = _.map(selection, function(record){
return { "id" : record.id, "version": record.version };
});
promise = this._request('removeAll').post({
records: selection
});
promise = promise.then(function(response){
var res = response.data;
function remove(id) {
var rec = _.find(records, function(record, i) {
return record.id == id;
});
var index = _.indexOf(records, rec);
if (index > -1) {
records.splice(index, 1);
page.total -= 1;
page.size -= 1;
}
}
_.each(res.data, function(record) {
remove(record.id);
});
that.trigger('change', records, page);
that.trigger('on:remove', selection);
$rootScope.$broadcast('ds:removed', that);
});
promise.success = function(fn) {
promise.then(function(response){
fn(records, page);
});
return promise;
};
promise.error = function(fn) {
promise.then(null, fn);
return promise;
};
return promise;
},
copy: function(id) {
var promise = this._request('copy', id).get();
promise.success = function(fn) {
promise.then(function(response){
var res = response.data,
record = res.data;
if (isArray(record))
record = record[0];
fn(record);
});
return promise;
};
promise.error = function(fn) {
promise.then(null, fn);
return promise;
};
return promise;
},
details: function(id, name) {
var promise = this._request('details', id).get({
params: {
name: name
}
});
promise.success = function(fn){
promise.then(function(response){
var res = response.data,
record = res.data;
if (isArray(record))
record = record[0];
fn(record);
});
return promise;
};
promise.error = function(fn) {
promise.then(null, fn);
return promise;
};
return promise;
},
/**
* Get current page info.
*
*/
page: function() {
return this._page;
},
next: function(fields) {
var page = this._page;
return this.search({
offset: page.from + page.limit,
fields: fields
});
},
prev: function(fields) {
var page = this._page;
return this.search({
offset: Math.max(0, page.from - page.limit),
fields: fields
});
},
nextItem: function(success) {
var self = this,
page = this._page,
index = page.index + 1,
record = this.at(index);
if (index < page.size) {
page.index = index;
return success(record);
}
this.next().success(function(){
page.index = 0;
record = self.at(0);
success(record);
});
},
prevItem: function(success) {
var self = this,
page = this._page,
index = page.index - 1,
record = this.at(index);
if (index > -1) {
page.index = index;
return success(record);
}
this.prev().success(function(){
page.index = page.size - 1;
record = self.at(page.index);
success(record);
});
},
canNext: function() {
var page = this._page;
return page.to < page.total;
},
canPrev: function() {
var page = this._page;
return page.from > 0;
},
_pageInfo: function(res) {
var records = res.data || [];
var page = _.extend({}, this._page);
page.from = res.offset === undefined ? page.from : res.offset;
page.to = page.from + records.length;
page.total = res.total === undefined ? page.total : res.total;
page.size = records.length;
return page;
},
_accept: function(res) {
var page = this._page,
records = this._data,
data = res.data,
accepted = null;
if (isArray(data)) {
records.length = 0;
forEach(data, function(record){
records.push(record);
});
_.extend(page, this._pageInfo(res));
accepted = records;
} else if (isObject(data)) {
var record = data;
var index = -1;
for(var i = 0 ; i < records.length ; i++) {
if (records[i].id === data.id) {
index = i;
break;
}
}
if (index > -1) {
var old = records.splice(index, 1, data)[0];
_.each(old, function (value, key) {
if (key.indexOf('.') > -1 && !data.hasOwnProperty(key)) {
data[key] = value;
}
});
} else {
records.push(record);
page.total += 1;
page.size += 1;
page.index = page.size - 1;
}
accepted = record;
}
this.trigger('change', records, page);
return accepted;
},
export_: function(fields) {
var domain = this._lastDomain || this._domain;
var context = this._lastContext;
var filter = this._filter;
var action = this._lastAction;
var query = extend({
_domain: domain,
_domainContext: context,
_domainAction: action || undefined,
_archived: this._showArchived
}, filter);
var params = {
fields: fields,
data: query,
sortBy: this._sortBy
};
var promise = this._request('export').post(params);
promise.success = function(fn){
promise.then(function(response){
var res = response.data,
records = res.data;
fn(records);
});
return promise;
};
promise.error = function(fn) {
promise.then(null, fn);
return promise;
};
return promise;
}
};
return {
create: create
};
function create(model, options) {
return new DataSource(model, options);
}
}]);
})();