mirror of
https://github.com/NaN-tic/sao-old.git
synced 2023-12-14 02:12:52 +01:00
648be43220
A field can be readonly by the domain inversion in the validate method. So it should be validated again in case its domain inversion change and reset it a non-readonly. So only field that are always readonly can be skipped for the validation. issue6448 review38341002
2445 lines
94 KiB
JavaScript
2445 lines
94 KiB
JavaScript
/* This file is part of Tryton. The COPYRIGHT file at the top level of
|
|
this repository contains the full copyright notices and license terms. */
|
|
(function() {
|
|
'use strict';
|
|
|
|
Sao.Model = Sao.class_(Object, {
|
|
init: function(name, attributes) {
|
|
attributes = attributes || {};
|
|
this.name = name;
|
|
this.session = Sao.Session.current_session;
|
|
this.fields = {};
|
|
},
|
|
add_fields: function(descriptions) {
|
|
var added = [];
|
|
for (var name in descriptions) {
|
|
if (descriptions.hasOwnProperty(name) &&
|
|
(!(name in this.fields))) {
|
|
var desc = descriptions[name];
|
|
var Field = Sao.field.get(desc.type);
|
|
this.fields[name] = new Field(desc);
|
|
added.push(name);
|
|
}
|
|
}
|
|
return added;
|
|
},
|
|
execute: function(method, params, context) {
|
|
if (context === undefined) {
|
|
context = {};
|
|
}
|
|
var args = {
|
|
'method': 'model.' + this.name + '.' + method,
|
|
'params': params.concat(context)
|
|
};
|
|
return Sao.rpc(args, this.session);
|
|
},
|
|
find: function(condition, offset, limit, order, context) {
|
|
if (!offset) offset = 0;
|
|
var self = this;
|
|
var prm = this.execute('search',
|
|
[condition, offset, limit, order], context);
|
|
var instanciate = function(ids) {
|
|
return Sao.Group(self, context, ids.map(function(id) {
|
|
return new Sao.Record(self, id);
|
|
}));
|
|
};
|
|
return prm.pipe(instanciate);
|
|
},
|
|
delete_: function(records) {
|
|
if (jQuery.isEmptyObject(records)) {
|
|
return jQuery.when();
|
|
}
|
|
var record = records[0];
|
|
var root_group = record.group.root_group();
|
|
console.assert(records.every(function(r) {
|
|
return r.model.name == record.model.name;
|
|
}), 'records not from the same model');
|
|
console.assert(records.every(function(r) {
|
|
return r.group.root_group() == record.group.root_group();
|
|
}), 'records not from the same root group');
|
|
records = records.filter(function(record) {
|
|
return record.id >= 0;
|
|
});
|
|
var context = {};
|
|
context._timestamp = {};
|
|
records.forEach(function(record) {
|
|
jQuery.extend(context._timestamp, record.get_timestamp());
|
|
});
|
|
var record_ids = records.map(function(record) {
|
|
return record.id;
|
|
});
|
|
return root_group.on_write_ids(record_ids).then(function(reload_ids) {
|
|
reload_ids = reload_ids.filter(function(e) {
|
|
return !~record_ids.indexOf(e);
|
|
});
|
|
return this.execute('delete', [record_ids], context)
|
|
.then(function() {
|
|
root_group.reload(reload_ids);
|
|
});
|
|
}.bind(this));
|
|
},
|
|
copy: function(records, context) {
|
|
if (jQuery.isEmptyObject(records)) {
|
|
return jQuery.when();
|
|
}
|
|
var record_ids = records.map(function(record) {
|
|
return record.id;
|
|
});
|
|
return this.execute('copy', [record_ids, {}], context);
|
|
}
|
|
});
|
|
|
|
Sao.Group = function(model, context, array) {
|
|
array.prm = jQuery.when();
|
|
array.model = model;
|
|
array._context = context;
|
|
array.on_write = [];
|
|
array.parent = undefined;
|
|
array.screens = [];
|
|
array.parent_name = '';
|
|
array.children = [];
|
|
array.child_name = '';
|
|
array.parent_datetime_field = undefined;
|
|
array.record_removed = [];
|
|
array.record_deleted = [];
|
|
array.__readonly = false;
|
|
array.skip_model_access = false;
|
|
array.forEach(function(e, i, a) {
|
|
e.group = a;
|
|
});
|
|
array.get_readonly = function() {
|
|
// Must skip res.user for Preference windows
|
|
var access = Sao.common.MODELACCESS.get(this.model.name);
|
|
if (this.context._datetime ||
|
|
(!(access.write || access.create) &&
|
|
!this.skip_model_access)) {
|
|
return true;
|
|
}
|
|
return this.__readonly;
|
|
};
|
|
array.set_readonly = function(value) {
|
|
this.__readonly = value;
|
|
};
|
|
array.load = function(ids, modified) {
|
|
var new_records = [];
|
|
var i, len;
|
|
for (i = 0, len = ids.length; i < len; i++) {
|
|
var id = ids[i];
|
|
var new_record = this.get(id);
|
|
if (!new_record) {
|
|
new_record = new Sao.Record(this.model, id);
|
|
new_record.group = this;
|
|
this.push(new_record);
|
|
}
|
|
new_records.push(new_record);
|
|
}
|
|
// Remove previously removed or deleted records
|
|
var record_removed = [];
|
|
var record;
|
|
for (i = 0, len = this.record_removed.length; i < len; i++) {
|
|
record = this.record_removed[i];
|
|
if (!~ids.indexOf(record.id)) {
|
|
record_removed.push(record);
|
|
}
|
|
}
|
|
this.record_removed = record_removed;
|
|
var record_deleted = [];
|
|
for (i = 0, len = this.record_deleted.length; i < len; i++) {
|
|
record = this.record_deleted[i];
|
|
if (!~ids.indexOf(record.id)) {
|
|
record_deleted.push(record);
|
|
}
|
|
}
|
|
this.record_deleted = record_deleted;
|
|
if (new_records.length && modified) {
|
|
this.changed();
|
|
}
|
|
};
|
|
array.get = function(id) {
|
|
// TODO optimize
|
|
for (var i = 0, len = this.length; i < len; i++) {
|
|
var record = this[i];
|
|
if (record.id == id) {
|
|
return record;
|
|
}
|
|
}
|
|
};
|
|
array.new_ = function(default_, id, rec_name) {
|
|
var record = new Sao.Record(this.model, id);
|
|
record.group = this;
|
|
if (default_) {
|
|
record.default_get(rec_name);
|
|
}
|
|
return record;
|
|
};
|
|
array.add = function(record, position, changed) {
|
|
if ((position === undefined) || (position == -1)) {
|
|
position = this.length;
|
|
}
|
|
if (changed === undefined) {
|
|
changed = true;
|
|
}
|
|
if (record.group != this) {
|
|
record.group = this;
|
|
}
|
|
this.splice(position, 0, record);
|
|
for (var record_rm in this.record_removed) {
|
|
if (record_rm.id == record.id) {
|
|
this.record_removed.splice(
|
|
this.record_removed.indexOf(record_rm), 1);
|
|
}
|
|
}
|
|
for (var record_del in this.record_deleted) {
|
|
if (record_del.id == record.id) {
|
|
this.record_deleted.splice(
|
|
this.record_deleted.indexOf(record_del), 1);
|
|
}
|
|
}
|
|
record._changed.id = true;
|
|
if (changed) {
|
|
this.changed();
|
|
// Set parent field to trigger on_change
|
|
if (this.parent && this.model.fields[this.parent_name]) {
|
|
var field = this.model.fields[this.parent_name];
|
|
if ((field instanceof Sao.field.Many2One) ||
|
|
field instanceof Sao.field.Reference) {
|
|
var value = [this.parent.id, ''];
|
|
if (field instanceof Sao.field.Reference) {
|
|
value = [this.parent.model.name, value];
|
|
}
|
|
field.set_client(record, value);
|
|
}
|
|
}
|
|
}
|
|
return record;
|
|
};
|
|
array.remove = function(record, remove, modified, force_remove) {
|
|
if (modified === undefined) {
|
|
modified = true;
|
|
}
|
|
var idx = this.indexOf(record);
|
|
if (record.id >= 0) {
|
|
if (remove) {
|
|
if (~this.record_deleted.indexOf(record)) {
|
|
this.record_deleted.splice(
|
|
this.record_deleted.indexOf(record), 1);
|
|
}
|
|
this.record_removed.push(record);
|
|
} else {
|
|
if (~this.record_removed.indexOf(record)) {
|
|
this.record_removed.splice(
|
|
this.record_removed.indexOf(record), 1);
|
|
}
|
|
this.record_deleted.push(record);
|
|
}
|
|
}
|
|
if (record.group.parent) {
|
|
record.group.parent._changed.id = true;
|
|
}
|
|
if (modified) {
|
|
record._changed.id = true;
|
|
}
|
|
if ((record.id < 0) || force_remove) {
|
|
this._remove(record);
|
|
}
|
|
record.group.changed();
|
|
record.group.root_group().screens.forEach(function(screen) {
|
|
screen.display();
|
|
});
|
|
};
|
|
array._remove = function(record) {
|
|
var idx = this.indexOf(record);
|
|
this.splice(idx, 1);
|
|
};
|
|
array.unremove = function(record) {
|
|
this.record_removed.splice(this.record_removed.indexOf(record), 1);
|
|
this.record_deleted.splice(this.record_deleted.indexOf(record), 1);
|
|
record.group.changed();
|
|
record.group.root_group().screens.forEach(function(screen) {
|
|
screen.display();
|
|
});
|
|
};
|
|
array.changed = function() {
|
|
if (!this.parent) {
|
|
return jQuery.when();
|
|
}
|
|
this.parent._changed[this.child_name] = true;
|
|
var prm = jQuery.Deferred();
|
|
var changed_prm = this.parent.model.fields[this.child_name]
|
|
.changed(this.parent);
|
|
// One2Many.changed could return undefined
|
|
if (changed_prm) {
|
|
changed_prm.then(function() {
|
|
this.parent.validate(null, true).done(function() {
|
|
this.parent.group.changed().done(prm.resolve);
|
|
}.bind(this));
|
|
}.bind(this));
|
|
} else {
|
|
prm.resolve();
|
|
}
|
|
return prm;
|
|
};
|
|
array.root_group = function() {
|
|
var root = this;
|
|
var parent = this.parent;
|
|
while (parent) {
|
|
root = parent.group;
|
|
parent = parent.parent;
|
|
}
|
|
return root;
|
|
};
|
|
array.save = function() {
|
|
var deferreds = [];
|
|
this.forEach(function(record) {
|
|
deferreds.push(record.save());
|
|
});
|
|
if (!jQuery.isEmptyObject(this.record_deleted)) {
|
|
this.record_deleted.forEach(function(record) {
|
|
this._remove(record);
|
|
}.bind(this));
|
|
deferreds.push(this.model.delete_(this.record_deleted));
|
|
this.record_deleted.splice(0, this.record_deleted.length);
|
|
}
|
|
return jQuery.when.apply(jQuery, deferreds);
|
|
};
|
|
array.written = function(ids) {
|
|
if (typeof(ids) == 'number') {
|
|
ids = [ids];
|
|
}
|
|
return this.on_write_ids(ids).then(function(to_reload) {
|
|
to_reload = to_reload.filter(function(e) {
|
|
return !~ids.indexOf(e);
|
|
});
|
|
this.root_group().reload(to_reload);
|
|
}.bind(this));
|
|
};
|
|
array.reload = function(ids) {
|
|
this.children.forEach(function(child) {
|
|
child.reload(ids);
|
|
});
|
|
ids.forEach(function(id) {
|
|
var record = this.get(id);
|
|
if (record && jQuery.isEmptyObject(record._changed)) {
|
|
record.cancel();
|
|
}
|
|
}.bind(this));
|
|
};
|
|
array.on_write_ids = function(ids) {
|
|
var deferreds = [];
|
|
var result = [];
|
|
this.on_write.forEach(function(fnct) {
|
|
var prm = this.model.execute(fnct, [ids], this._context)
|
|
.then(function(res) {
|
|
jQuery.extend(result, res);
|
|
});
|
|
deferreds.push(prm);
|
|
}.bind(this));
|
|
return jQuery.when.apply(jQuery, deferreds).then(function() {
|
|
return result.filter(function(e, i, a) {
|
|
return i == a.indexOf(e);
|
|
});
|
|
});
|
|
};
|
|
array.set_parent = function(parent) {
|
|
this.parent = parent;
|
|
if (parent && parent.model.name == this.model.name) {
|
|
this.parent.group.children.push(this);
|
|
}
|
|
};
|
|
array.add_fields = function(fields) {
|
|
var added = this.model.add_fields(fields);
|
|
if (jQuery.isEmptyObject(this)) {
|
|
return;
|
|
}
|
|
var new_ = [];
|
|
this.forEach(function(record) {
|
|
if (record.id < 0) {
|
|
new_.push(record);
|
|
}
|
|
});
|
|
if (new_.length && added.length) {
|
|
this.model.execute('default_get', [added, this.context()])
|
|
.then(function(values) {
|
|
new_.forEach(function(record) {
|
|
record.set_default(values);
|
|
});
|
|
});
|
|
}
|
|
};
|
|
array.destroy = function() {
|
|
if (this.parent) {
|
|
var i = this.parent.group.children.indexOf(this);
|
|
if (~i) {
|
|
this.parent.group.children.splice(i, 1);
|
|
}
|
|
}
|
|
this.parent = null;
|
|
};
|
|
array.domain = function() {
|
|
var domain = [];
|
|
this.screens.forEach(function(screen) {
|
|
if (screen.attributes.domain) {
|
|
domain.push(screen.attributes.domain);
|
|
}
|
|
});
|
|
if (this.parent && this.child_name) {
|
|
var field = this.parent.model.fields[this.child_name];
|
|
return [domain, field.get_domain(this.parent)];
|
|
} else {
|
|
return domain;
|
|
}
|
|
};
|
|
array.context = function() {
|
|
var context = jQuery.extend({}, this._context);
|
|
if (this.parent) {
|
|
jQuery.extend(context, this.parent.get_context());
|
|
if (this.child_name in this.parent.model.fields) {
|
|
var field = this.parent.model.fields[this.child_name];
|
|
jQuery.extend(context, field.get_context(this.parent));
|
|
}
|
|
}
|
|
jQuery.extend(context, this._context);
|
|
if (this.parent_datetime_field) {
|
|
context._datetime = this.parent.get_eval()
|
|
[this.parent_datetime_field];
|
|
}
|
|
return context;
|
|
};
|
|
array.set_context = function(context) {
|
|
this._context = jQuery.extend({}, context);
|
|
};
|
|
array.clean4inversion = function(domain) {
|
|
if (jQuery.isEmptyObject(domain)) {
|
|
return [];
|
|
}
|
|
var inversion = new Sao.common.DomainInversion();
|
|
var head = domain[0];
|
|
var tail = domain.slice(1);
|
|
if (~['AND', 'OR'].indexOf(head)) {
|
|
} else if (inversion.is_leaf(head)) {
|
|
var field = head[0];
|
|
if ((field in this.model.fields) &&
|
|
(this.model.fields[field].description.readonly)) {
|
|
head = [];
|
|
}
|
|
} else {
|
|
head = this.clean4inversion(head);
|
|
}
|
|
return [head].concat(this.clean4inversion(tail));
|
|
};
|
|
array.domain4inversion = function() {
|
|
var domain = this.domain();
|
|
if (!this.__domain4inversion ||
|
|
!Sao.common.compare(this.__domain4inversion[0], domain)) {
|
|
this.__domain4inversion = [domain, this.clean4inversion(domain)];
|
|
}
|
|
return this.__domain4inversion[1];
|
|
};
|
|
array.get_by_path = function(path) {
|
|
path = jQuery.extend([], path);
|
|
var record = null;
|
|
var group = this;
|
|
|
|
var browse_child = function() {
|
|
if (jQuery.isEmptyObject(path)) {
|
|
return record;
|
|
}
|
|
var child_name = path[0][0];
|
|
var id = path[0][1];
|
|
path.splice(0, 1);
|
|
record = group.get(id);
|
|
if (!record) {
|
|
return null;
|
|
}
|
|
if (!child_name) {
|
|
return browse_child();
|
|
}
|
|
return record.load(child_name).then(function() {
|
|
group = record._values[child_name];
|
|
if (!group) {
|
|
return null;
|
|
}
|
|
return browse_child();
|
|
});
|
|
};
|
|
return jQuery.when().then(browse_child);
|
|
};
|
|
array.sort = function(ids) {
|
|
var id2record = {};
|
|
this.forEach(function(record) {
|
|
id2record[record.id] = record;
|
|
});
|
|
ids.forEach(function(ordered_record, i){
|
|
this[i] = id2record[ordered_record.id];
|
|
}.bind(this));
|
|
};
|
|
return array;
|
|
};
|
|
|
|
Sao.Record = Sao.class_(Object, {
|
|
id_counter: -1,
|
|
init: function(model, id) {
|
|
this.model = model;
|
|
this.group = Sao.Group(model, {}, []);
|
|
this.id = id || Sao.Record.prototype.id_counter--;
|
|
this._values = {};
|
|
this._changed = {};
|
|
this._loaded = {};
|
|
this.fields = {};
|
|
this._timestamp = null;
|
|
this.attachment_count = -1;
|
|
this.unread_note = -1;
|
|
this.button_clicks = {};
|
|
this.state_attrs = {};
|
|
this.autocompletion = {};
|
|
this.exception = false;
|
|
},
|
|
has_changed: function() {
|
|
return !jQuery.isEmptyObject(this._changed);
|
|
},
|
|
save: function(force_reload) {
|
|
if (force_reload === undefined) {
|
|
force_reload = false;
|
|
}
|
|
var context = this.get_context();
|
|
var prm = jQuery.when();
|
|
var values = this.get();
|
|
if ((this.id < 0) || !jQuery.isEmptyObject(values)) {
|
|
if (this.id < 0) {
|
|
prm = this.model.execute('create', [[values]], context);
|
|
var created = function(ids) {
|
|
this.id = ids[0];
|
|
};
|
|
prm.done(created.bind(this));
|
|
} else {
|
|
if (!jQuery.isEmptyObject(values)) {
|
|
context._timestamp = this.get_timestamp();
|
|
prm = this.model.execute('write', [[this.id], values],
|
|
context);
|
|
}
|
|
}
|
|
prm = prm.done(function() {
|
|
this.cancel();
|
|
if (force_reload) {
|
|
return this.reload();
|
|
}
|
|
}.bind(this));
|
|
if (this.group) {
|
|
prm = prm.done(function() {
|
|
return this.group.written(this.id);
|
|
}.bind(this));
|
|
}
|
|
}
|
|
if (this.group.parent) {
|
|
delete this.group.parent._changed[this.group.child_name];
|
|
prm = prm.done(function() {
|
|
return this.group.parent.save(force_reload);
|
|
}.bind(this));
|
|
}
|
|
return prm;
|
|
},
|
|
reload: function(fields) {
|
|
if (this.id < 0) {
|
|
return jQuery.when();
|
|
}
|
|
if (!fields) {
|
|
return this.load('*');
|
|
} else {
|
|
var prms = fields.map(function(field) {
|
|
return this.load(field);
|
|
}.bind(this));
|
|
return jQuery.when.apply(jQuery, prms);
|
|
}
|
|
},
|
|
load: function(name) {
|
|
var fname;
|
|
var prm;
|
|
if ((this.id < 0) || (name in this._loaded)) {
|
|
return jQuery.when();
|
|
}
|
|
if (this.group.prm.state() == 'pending') {
|
|
prm = jQuery.Deferred();
|
|
this.group.prm.then(function() {
|
|
this.load(name).then(prm.resolve, prm.reject);
|
|
}.bind(this));
|
|
return prm;
|
|
}
|
|
var id2record = {};
|
|
id2record[this.id] = this;
|
|
var loading;
|
|
if (name == '*') {
|
|
loading = 'eager';
|
|
for (fname in this.model.fields) {
|
|
if (!this.model.fields.hasOwnProperty(fname)) {
|
|
continue;
|
|
}
|
|
var field_loading = (
|
|
this.model.fields[fname].description.loading ||
|
|
'eager');
|
|
if (field_loading != 'eager') {
|
|
loading = 'lazy';
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
loading = (this.model.fields[name].description.loading ||
|
|
'eager');
|
|
}
|
|
var fnames = [];
|
|
if (loading == 'eager') {
|
|
for (fname in this.model.fields) {
|
|
if (!this.model.fields.hasOwnProperty(fname)) {
|
|
continue;
|
|
}
|
|
if ((this.model.fields[fname].description.loading ||
|
|
'eager') == 'eager') {
|
|
fnames.push(fname);
|
|
}
|
|
}
|
|
} else {
|
|
fnames = Object.keys(this.model.fields);
|
|
}
|
|
fnames = fnames.filter(function(e, i, a) {
|
|
return !(e in this._loaded);
|
|
}.bind(this));
|
|
var fnames_to_fetch = fnames.slice();
|
|
var rec_named_fields = ['many2one', 'one2one', 'reference'];
|
|
for (var i in fnames) {
|
|
fname = fnames[i];
|
|
var fdescription = this.model.fields[fname].description;
|
|
if (~rec_named_fields.indexOf(fdescription.type))
|
|
fnames_to_fetch.push(fname + '.rec_name');
|
|
}
|
|
if (!~fnames.indexOf('rec_name')) {
|
|
fnames_to_fetch.push('rec_name');
|
|
}
|
|
fnames_to_fetch.push('_timestamp');
|
|
|
|
var context = jQuery.extend({}, this.get_context());
|
|
if (loading == 'eager') {
|
|
var limit = parseInt(Sao.config.limit / fnames_to_fetch.length,
|
|
10);
|
|
|
|
var filter_group = function(record) {
|
|
return !(name in record._loaded) && (record.id >= 0);
|
|
};
|
|
var filter_parent_group = function(record) {
|
|
return (filter_group(record) &&
|
|
(id2record[record.id] === undefined) &&
|
|
((record.group === this.group) ||
|
|
// Don't compute context for same group
|
|
(JSON.stringify(record.get_context()) ===
|
|
JSON.stringify(context))));
|
|
}.bind(this);
|
|
var group, filter;
|
|
if (this.group.parent &&
|
|
(this.group.parent.model.name == this.model.name)) {
|
|
group = [];
|
|
group = group.concat.apply(
|
|
group, this.group.parent.group.children);
|
|
filter = filter_parent_group;
|
|
} else {
|
|
group = this.group;
|
|
filter = filter_group;
|
|
}
|
|
var idx = group.indexOf(this);
|
|
if (~idx) {
|
|
var length = group.length;
|
|
var n = 1;
|
|
while ((Object.keys(id2record).length < limit) &&
|
|
((idx - n >= 0) || (idx + n < length)) &&
|
|
(n < 2 * limit)) {
|
|
var record;
|
|
if (idx - n >= 0) {
|
|
record = group[idx - n];
|
|
if (filter(record)) {
|
|
id2record[record.id] = record;
|
|
}
|
|
}
|
|
if (idx + n < length) {
|
|
record = group[idx + n];
|
|
if (filter(record)) {
|
|
id2record[record.id] = record;
|
|
}
|
|
}
|
|
n++;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (fname in this.model.fields) {
|
|
if (!this.model.fields.hasOwnProperty(fname)) {
|
|
continue;
|
|
}
|
|
if ((this.model.fields[fname].description.type == 'binary') &&
|
|
~fnames_to_fetch.indexOf(fname, fnames_to_fetch)) {
|
|
context[this.model.name + '.' + fname] = 'size';
|
|
}
|
|
}
|
|
prm = this.model.execute('read', [Object.keys(id2record).map(
|
|
function (e) { return parseInt(e, 10); }),
|
|
fnames_to_fetch], context);
|
|
var succeed = function(values, exception) {
|
|
if (exception === undefined) exception = false;
|
|
var id2value = {};
|
|
var promises = [];
|
|
values.forEach(function(e, i, a) {
|
|
id2value[e.id] = e;
|
|
});
|
|
for (var id in id2record) {
|
|
if (!id2record.hasOwnProperty(id)) {
|
|
continue;
|
|
}
|
|
var record = id2record[id];
|
|
if (!record.exception) {
|
|
record.exception = exception;
|
|
}
|
|
var value = id2value[id];
|
|
if (record && value) {
|
|
for (var key in this._changed) {
|
|
if (!this._changed.hasOwnProperty(key)) {
|
|
continue;
|
|
}
|
|
delete value[key];
|
|
}
|
|
promises.push(record.set(value));
|
|
}
|
|
}
|
|
return jQuery.when.apply(jQuery, promises);
|
|
}.bind(this);
|
|
var failed = function() {
|
|
var failed_values = [];
|
|
var default_values;
|
|
for (var id in id2record) {
|
|
default_values = {
|
|
id: id
|
|
};
|
|
for (var i in fnames_to_fetch) {
|
|
default_values[fnames_to_fetch[i]] = null;
|
|
}
|
|
failed_values.push(default_values);
|
|
}
|
|
return succeed(failed_values, true);
|
|
};
|
|
this.group.prm = prm.then(succeed, failed);
|
|
return this.group.prm;
|
|
},
|
|
set: function(values, validate) {
|
|
if (validate === undefined) {
|
|
validate = true;
|
|
}
|
|
var name, value;
|
|
var rec_named_fields = ['many2one', 'one2one', 'reference'];
|
|
var later = {};
|
|
var fieldnames = [];
|
|
for (name in values) {
|
|
if (!values.hasOwnProperty(name)) {
|
|
continue;
|
|
}
|
|
value = values[name];
|
|
if (name == '_timestamp') {
|
|
// Always keep the older timestamp
|
|
if (!this._timestamp) {
|
|
this._timestamp = value;
|
|
}
|
|
continue;
|
|
}
|
|
if (!(name in this.model.fields)) {
|
|
if (name == 'rec_name') {
|
|
this._values[name] = value;
|
|
}
|
|
continue;
|
|
}
|
|
if (this.model.fields[name] instanceof Sao.field.One2Many) {
|
|
later[name] = value;
|
|
}
|
|
if ((this.model.fields[name] instanceof Sao.field.Many2One) ||
|
|
(this.model.fields[name] instanceof Sao.field.Reference)) {
|
|
var field_rec_name = name + '.rec_name';
|
|
if (values.hasOwnProperty(field_rec_name)) {
|
|
this._values[field_rec_name] = values[field_rec_name];
|
|
}
|
|
else if (this._values.hasOwnProperty(field_rec_name)) {
|
|
delete this._values[field_rec_name];
|
|
}
|
|
}
|
|
this.model.fields[name].set(this, value);
|
|
this._loaded[name] = true;
|
|
fieldnames.push(name);
|
|
}
|
|
for (name in later) {
|
|
value = later[name];
|
|
this.model.fields[name].set(this, value);
|
|
this._loaded[name] = true;
|
|
}
|
|
if (validate) {
|
|
return this.validate(fieldnames, true);
|
|
}
|
|
return jQuery.when();
|
|
},
|
|
get: function() {
|
|
var value = {};
|
|
for (var name in this.model.fields) {
|
|
if (!this.model.fields.hasOwnProperty(name)) {
|
|
continue;
|
|
}
|
|
var field = this.model.fields[name];
|
|
if (field.description.readonly &&
|
|
!(field instanceof Sao.field.One2Many)) {
|
|
continue;
|
|
}
|
|
if ((this._changed[name] === undefined) && this.id >= 0) {
|
|
continue;
|
|
}
|
|
value[name] = field.get(this);
|
|
}
|
|
return value;
|
|
},
|
|
invalid_fields: function() {
|
|
var fields = {};
|
|
for (var fname in this.model.fields) {
|
|
var field = this.model.fields[fname];
|
|
var invalid = field.get_state_attrs(this).invalid;
|
|
if (invalid) {
|
|
fields[fname] = invalid;
|
|
}
|
|
}
|
|
return fields;
|
|
},
|
|
get_context: function() {
|
|
return this.group.context();
|
|
},
|
|
field_get: function(name) {
|
|
return this.model.fields[name].get(this);
|
|
},
|
|
field_set: function(name, value) {
|
|
this.model.fields[name].set(this, value);
|
|
},
|
|
field_get_client: function(name) {
|
|
return this.model.fields[name].get_client(this);
|
|
},
|
|
field_set_client: function(name, value, force_change) {
|
|
this.model.fields[name].set_client(this, value, force_change);
|
|
},
|
|
default_get: function(rec_name) {
|
|
if (!jQuery.isEmptyObject(this.model.fields)) {
|
|
var context = this.get_context();
|
|
if (context.default_rec_name === undefined) {
|
|
context.default_rec_name = rec_name;
|
|
}
|
|
var prm = this.model.execute('default_get',
|
|
[Object.keys(this.model.fields)], context);
|
|
return prm.then(function(values) {
|
|
if (this.group.parent &&
|
|
this.group.parent_name in this.group.model.fields) {
|
|
var parent_field =
|
|
this.group.model.fields[this.group.parent_name];
|
|
if (parent_field instanceof Sao.field.Reference) {
|
|
values[this.group.parent_name] = [
|
|
this.group.parent.model.name,
|
|
this.group.parent.id];
|
|
} else if (parent_field.description.relation ==
|
|
this.group.parent.model.name) {
|
|
values[this.group.parent_name] =
|
|
this.group.parent.id;
|
|
}
|
|
}
|
|
return this.set_default(values);
|
|
}.bind(this));
|
|
}
|
|
return jQuery.when();
|
|
},
|
|
set_default: function(values, validate) {
|
|
if (validate === undefined) {
|
|
validate = true;
|
|
}
|
|
var promises = [];
|
|
var fieldnames = [];
|
|
for (var fname in values) {
|
|
if (!values.hasOwnProperty(fname)) {
|
|
continue;
|
|
}
|
|
var value = values[fname];
|
|
if (!(fname in this.model.fields)) {
|
|
continue;
|
|
}
|
|
if ((this.model.fields[fname] instanceof Sao.field.Many2One) ||
|
|
(this.model.fields[fname] instanceof Sao.field.Reference)) {
|
|
var field_rec_name = fname + '.rec_name';
|
|
if (values.hasOwnProperty(field_rec_name)) {
|
|
this._values[field_rec_name] = values[field_rec_name];
|
|
} else if (this._values.hasOwnProperty(field_rec_name)) {
|
|
delete this._values[field_rec_name];
|
|
}
|
|
}
|
|
promises.push(this.model.fields[fname].set_default(this, value));
|
|
this._loaded[fname] = true;
|
|
fieldnames.push(fname);
|
|
}
|
|
return jQuery.when.apply(jQuery, promises).then(function() {
|
|
return this.on_change(fieldnames).then(function() {
|
|
return this.on_change_with(fieldnames).then(function() {
|
|
var callback = function() {
|
|
return this.group.root_group().screens
|
|
.forEach(function(screen) {
|
|
return screen.display();
|
|
});
|
|
}.bind(this);
|
|
if (validate) {
|
|
return this.validate(null, true)
|
|
.then(callback);
|
|
} else {
|
|
return callback();
|
|
}
|
|
}.bind(this));
|
|
}.bind(this));
|
|
}.bind(this));
|
|
},
|
|
get_timestamp: function() {
|
|
var timestamps = {};
|
|
timestamps[this.model.name + ',' + this.id] = this._timestamp;
|
|
for (var fname in this.model.fields) {
|
|
if (!this.model.fields.hasOwnProperty(fname)) {
|
|
continue;
|
|
}
|
|
if (!(fname in this._loaded)) {
|
|
continue;
|
|
}
|
|
jQuery.extend(timestamps,
|
|
this.model.fields[fname].get_timestamp(this));
|
|
}
|
|
return timestamps;
|
|
},
|
|
get_eval: function() {
|
|
var value = {};
|
|
for (var key in this.model.fields) {
|
|
if (!this.model.fields.hasOwnProperty(key) && this.id >= 0)
|
|
continue;
|
|
value[key] = this.model.fields[key].get_eval(this);
|
|
}
|
|
return value;
|
|
},
|
|
get_on_change_value: function(skip) {
|
|
var value = {};
|
|
for (var key in this.model.fields) {
|
|
if (skip && ~skip.indexOf(key)) {
|
|
continue;
|
|
}
|
|
if ((this.id >= 0) &&
|
|
(!this._loaded[key] || !this._changed[key])) {
|
|
continue;
|
|
}
|
|
value[key] = this.model.fields[key].get_on_change_value(this);
|
|
}
|
|
value.id = this.id;
|
|
return value;
|
|
},
|
|
_get_on_change_args: function(args) {
|
|
var result = {};
|
|
var values = Sao.common.EvalEnvironment(this, 'on_change');
|
|
args.forEach(function(arg) {
|
|
var scope = values;
|
|
arg.split('.').forEach(function(e) {
|
|
if (scope !== undefined) {
|
|
scope = scope[e];
|
|
}
|
|
});
|
|
result[arg] = scope;
|
|
});
|
|
result.id = this.id;
|
|
return result;
|
|
},
|
|
on_change: function(fieldnames) {
|
|
var values = {};
|
|
fieldnames.forEach(function(fieldname) {
|
|
var on_change = this.model.fields[fieldname]
|
|
.description.on_change;
|
|
if (!jQuery.isEmptyObject(on_change)) {
|
|
values = jQuery.extend(values,
|
|
this._get_on_change_args(on_change));
|
|
}
|
|
}.bind(this));
|
|
if (!jQuery.isEmptyObject(values)) {
|
|
var prm = this.model.execute('on_change',
|
|
[values, fieldnames], this.get_context());
|
|
return prm.then(function(changes) {
|
|
changes.forEach(this.set_on_change.bind(this));
|
|
}.bind(this));
|
|
} else {
|
|
return jQuery.when();
|
|
}
|
|
},
|
|
on_change_with: function(field_names) {
|
|
var fieldnames = {};
|
|
var values = {};
|
|
var later = {};
|
|
var fieldname, on_change_with;
|
|
for (fieldname in this.model.fields) {
|
|
if (!this.model.fields.hasOwnProperty(fieldname)) {
|
|
continue;
|
|
}
|
|
on_change_with = this.model.fields[fieldname]
|
|
.description.on_change_with;
|
|
if (jQuery.isEmptyObject(on_change_with)) {
|
|
continue;
|
|
}
|
|
for (var i = 0; i < field_names.length; i++) {
|
|
if (~on_change_with.indexOf(field_names[i])) {
|
|
break;
|
|
}
|
|
}
|
|
if (i >= field_names.length) {
|
|
continue;
|
|
}
|
|
if (!jQuery.isEmptyObject(Sao.common.intersect(
|
|
Object.keys(fieldnames).sort(),
|
|
on_change_with.sort()))) {
|
|
later[fieldname] = true;
|
|
continue;
|
|
}
|
|
fieldnames[fieldname] = true;
|
|
values = jQuery.extend(values,
|
|
this._get_on_change_args(on_change_with));
|
|
if ((this.model.fields[fieldname] instanceof
|
|
Sao.field.Many2One) ||
|
|
(this.model.fields[fieldname] instanceof
|
|
Sao.field.Reference)) {
|
|
delete this._values[fieldname + '.rec_name'];
|
|
}
|
|
}
|
|
var prms = [];
|
|
var prm;
|
|
if (!jQuery.isEmptyObject(fieldnames)) {
|
|
prm = this.model.execute('on_change_with',
|
|
[values, Object.keys(fieldnames)], this.get_context());
|
|
prms.push(prm.then(this.set_on_change.bind(this)));
|
|
}
|
|
var set_on_change = function(fieldname) {
|
|
return function(result) {
|
|
this.model.fields[fieldname].set_on_change(this, result);
|
|
};
|
|
};
|
|
for (fieldname in later) {
|
|
if (!later.hasOwnProperty(fieldname)) {
|
|
continue;
|
|
}
|
|
on_change_with = this.model.fields[fieldname]
|
|
.description.on_change_with;
|
|
values = this._get_on_change_args(on_change_with);
|
|
prm = this.model.execute('on_change_with_' + fieldname,
|
|
[values], this.get_context());
|
|
prms.push(prm.then(set_on_change(fieldname).bind(this)));
|
|
}
|
|
return jQuery.when.apply(jQuery, prms);
|
|
},
|
|
set_on_change: function(values) {
|
|
var fieldname, value;
|
|
for (fieldname in values) {
|
|
if (!values.hasOwnProperty(fieldname)) {
|
|
continue;
|
|
}
|
|
value = values[fieldname];
|
|
if (!(fieldname in this.model.fields)) {
|
|
continue;
|
|
}
|
|
if ((this.model.fields[fieldname] instanceof
|
|
Sao.field.Many2One) ||
|
|
(this.model.fields[fieldname] instanceof
|
|
Sao.field.Reference)) {
|
|
var field_rec_name = fieldname + '.rec_name';
|
|
if (values.hasOwnProperty(field_rec_name)) {
|
|
this._values[field_rec_name] = values[field_rec_name];
|
|
} else if (this._values.hasOwnProperty(field_rec_name)) {
|
|
delete this._values[field_rec_name];
|
|
}
|
|
}
|
|
this.model.fields[fieldname].set_on_change(this, value);
|
|
}
|
|
},
|
|
autocomplete_with: function(fieldname) {
|
|
var promises = [];
|
|
for (var fname in this.model.fields) {
|
|
var field = this.model.fields[fname];
|
|
var autocomplete = field.description.autocomplete || [];
|
|
if (!~autocomplete.indexOf(fieldname)) {
|
|
continue;
|
|
}
|
|
promises.push(this.do_autocomplete(fname));
|
|
}
|
|
return jQuery.when.apply(jQuery, promises);
|
|
},
|
|
do_autocomplete: function(fieldname) {
|
|
this.autocompletion[fieldname] = [];
|
|
var field = this.model.fields[fieldname];
|
|
var autocomplete = field.description.autocomplete;
|
|
var values = this._get_on_change_args(autocomplete);
|
|
var prm = this.model.execute(
|
|
'autocomplete_' + fieldname, [values], this.get_context());
|
|
return prm.then(function(result) {
|
|
this.autocompletion[fieldname] = result;
|
|
}.bind(this));
|
|
},
|
|
expr_eval: function(expr) {
|
|
if (typeof(expr) != 'string') return expr;
|
|
var ctx = jQuery.extend({}, this.model.session.context);
|
|
ctx.context = jQuery.extend({}, ctx);
|
|
jQuery.extend(ctx.context, this.get_context());
|
|
jQuery.extend(ctx, this.get_eval());
|
|
ctx.active_model = this.model.name;
|
|
ctx.active_id = this.id;
|
|
ctx._user = this.model.session.user_id;
|
|
if (this.group.parent && this.group.parent_name) {
|
|
var parent_env = Sao.common.EvalEnvironment(this.group.parent);
|
|
ctx['_parent_' + this.group.parent_name] = parent_env;
|
|
}
|
|
return new Sao.PYSON.Decoder(ctx).decode(expr);
|
|
},
|
|
rec_name: function() {
|
|
var prm = this.model.execute('read', [[this.id], ['rec_name']],
|
|
this.get_context());
|
|
return prm.then(function(values) {
|
|
return values[0].rec_name;
|
|
});
|
|
},
|
|
validate: function(fields, softvalidation, pre_validate, sync) {
|
|
var validate_fields = function() {
|
|
var result = true;
|
|
var exclude_fields = [];
|
|
this.group.screens.forEach(function(screen) {
|
|
if (screen.exclude_field) {
|
|
exclude_fields.push(screen.exclude_field);
|
|
}
|
|
});
|
|
for (var fname in this.model.fields) {
|
|
// Skip not loaded fields if sync and record is not new
|
|
if (sync && this.id >= 0 && !(fname in this._loaded)) {
|
|
continue;
|
|
}
|
|
if (!this.model.fields.hasOwnProperty(fname)) {
|
|
continue;
|
|
}
|
|
var field = this.model.fields[fname];
|
|
if (fields && !~fields.indexOf(fname)) {
|
|
continue;
|
|
}
|
|
if (field.description.readonly) {
|
|
continue;
|
|
}
|
|
if (~exclude_fields.indexOf(fname)) {
|
|
continue;
|
|
}
|
|
if (!field.validate(this, softvalidation, pre_validate)) {
|
|
result = false;
|
|
}
|
|
}
|
|
return result;
|
|
}.bind(this);
|
|
if (sync) {
|
|
return validate_fields();
|
|
} else {
|
|
return this._check_load(fields).then(validate_fields);
|
|
}
|
|
},
|
|
pre_validate: function() {
|
|
if (jQuery.isEmptyObject(this._changed)) {
|
|
return jQuery.Deferred().resolve(true);
|
|
}
|
|
var values = this._get_on_change_args(Object.keys(this._changed));
|
|
return this.model.execute('pre_validate',
|
|
[values], this.get_context())
|
|
.then(function() {
|
|
return true;
|
|
}, function() {
|
|
return false;
|
|
});
|
|
},
|
|
cancel: function() {
|
|
this._loaded = {};
|
|
this._changed = {};
|
|
this._timestamp = null;
|
|
this.button_clicks = {};
|
|
},
|
|
_check_load: function(fields) {
|
|
if (!this.get_loaded(fields)) {
|
|
return this.reload(fields);
|
|
}
|
|
return jQuery.when();
|
|
},
|
|
get_loaded: function(fields) {
|
|
if (!jQuery.isEmptyObject(fields)) {
|
|
var result = true;
|
|
fields.forEach(function(field) {
|
|
if (!(field in this._loaded) && !(field in this._changed)) {
|
|
result = false;
|
|
}
|
|
}.bind(this));
|
|
return result;
|
|
}
|
|
return Sao.common.compare(Object.keys(this.model.fields).sort(),
|
|
Object.keys(this._loaded).sort());
|
|
},
|
|
root_parent: function root_parent() {
|
|
var parent = this;
|
|
while (parent.group.parent) {
|
|
parent = parent.group.parent;
|
|
}
|
|
return parent;
|
|
},
|
|
get_path: function(group) {
|
|
var path = [];
|
|
var i = this;
|
|
var child_name = '';
|
|
while (i) {
|
|
path.push([child_name, i.id]);
|
|
if (i.group === group) {
|
|
break;
|
|
}
|
|
child_name = i.group.child_name;
|
|
i = i.group.parent;
|
|
}
|
|
path.reverse();
|
|
return path;
|
|
},
|
|
deleted: function() {
|
|
return Boolean(~this.group.record_deleted.indexOf(this));
|
|
},
|
|
removed: function() {
|
|
return Boolean(~this.group.record_removed.indexOf(this));
|
|
},
|
|
readonly: function() {
|
|
return this.deleted() || this.removed() || this.exception;
|
|
},
|
|
set_field_context: function() {
|
|
for (var name in this.model.fields) {
|
|
if (!this.model.fields.hasOwnProperty(name)) {
|
|
continue;
|
|
}
|
|
var field = this.model.fields[name];
|
|
var value = this._values[name];
|
|
if (!value || !value.set_context) {
|
|
continue;
|
|
}
|
|
var context = field.description.context;
|
|
if (context) {
|
|
value.set_context(this.expr_eval(context));
|
|
}
|
|
}
|
|
},
|
|
get_attachment_count: function(reload) {
|
|
var prm = jQuery.Deferred();
|
|
if (this.id < 0) {
|
|
prm.resolve(0);
|
|
return prm;
|
|
}
|
|
if ((this.attachment_count < 0) || reload) {
|
|
prm = Sao.rpc({
|
|
method: 'model.ir.attachment.search_count',
|
|
params: [
|
|
[['resource', '=', this.model.name + ',' + this.id]],
|
|
this.get_context()]
|
|
}, this.model.session).then(function(count) {
|
|
this.attachment_count = count;
|
|
return count;
|
|
}.bind(this));
|
|
} else {
|
|
prm.resolve(this.attachment_count);
|
|
}
|
|
return prm;
|
|
},
|
|
get_unread_note: function(reload) {
|
|
var prm = jQuery.Deferred();
|
|
if (this.id < 0) {
|
|
prm.resolve(0);
|
|
return prm;
|
|
}
|
|
if ((this.unread_note < 0) || reload) {
|
|
prm = Sao.rpc({
|
|
method: 'model.ir.note.search_count',
|
|
params: [
|
|
[['resource', '=', this.model.name + ',' + this.id],
|
|
['unread', '=', true]],
|
|
this.get_context()]
|
|
}, this.model.session).then(function(count) {
|
|
this.unread_note = count;
|
|
return count;
|
|
}.bind(this));
|
|
} else {
|
|
prm.resolve(this.unread_note);
|
|
}
|
|
return prm;
|
|
},
|
|
get_button_clicks: function(name) {
|
|
if (this.id < 0) {
|
|
return jQuery.when();
|
|
}
|
|
var clicks = this.button_clicks[name];
|
|
if (clicks !== undefined) {
|
|
return jQuery.when(clicks);
|
|
}
|
|
return Sao.rpc({
|
|
'method': 'model.ir.model.button.click.get_click',
|
|
'params': [this.model.name, name, this.id, {}],
|
|
}, this.model.session).then(function(clicks) {
|
|
this.button_clicks[name] = clicks;
|
|
return clicks;
|
|
}.bind(this));
|
|
}
|
|
});
|
|
|
|
|
|
Sao.field = {};
|
|
|
|
Sao.field.get = function(type) {
|
|
switch (type) {
|
|
case 'char':
|
|
return Sao.field.Char;
|
|
case 'selection':
|
|
return Sao.field.Selection;
|
|
case 'datetime':
|
|
return Sao.field.DateTime;
|
|
case 'date':
|
|
return Sao.field.Date;
|
|
case 'time':
|
|
return Sao.field.Time;
|
|
case 'timedelta':
|
|
return Sao.field.TimeDelta;
|
|
case 'float':
|
|
return Sao.field.Float;
|
|
case 'numeric':
|
|
return Sao.field.Numeric;
|
|
case 'integer':
|
|
return Sao.field.Integer;
|
|
case 'boolean':
|
|
return Sao.field.Boolean;
|
|
case 'many2one':
|
|
return Sao.field.Many2One;
|
|
case 'one2one':
|
|
return Sao.field.One2One;
|
|
case 'one2many':
|
|
return Sao.field.One2Many;
|
|
case 'many2many':
|
|
return Sao.field.Many2Many;
|
|
case 'reference':
|
|
return Sao.field.Reference;
|
|
case 'binary':
|
|
return Sao.field.Binary;
|
|
case 'dict':
|
|
return Sao.field.Dict;
|
|
default:
|
|
return Sao.field.Char;
|
|
}
|
|
};
|
|
|
|
Sao.field.Field = Sao.class_(Object, {
|
|
_default: null,
|
|
init: function(description) {
|
|
this.description = description;
|
|
this.name = description.name;
|
|
},
|
|
set: function(record, value) {
|
|
record._values[this.name] = value;
|
|
},
|
|
get: function(record) {
|
|
var value = record._values[this.name];
|
|
if (value === undefined) {
|
|
value = this._default;
|
|
}
|
|
return value;
|
|
},
|
|
set_client: function(record, value, force_change) {
|
|
var previous_value = this.get(record);
|
|
this.set(record, value);
|
|
// Use stringify to compare object instance like Number for Decimal
|
|
if (JSON.stringify(previous_value) !=
|
|
JSON.stringify(this.get(record))) {
|
|
record._changed[this.name] = true;
|
|
this.changed(record).done(function() {
|
|
record.validate(null, true).then(function() {
|
|
record.group.changed().done(function() {
|
|
var root_group = record.group.root_group();
|
|
root_group.screens.forEach(function(screen) {
|
|
screen.display();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
} else if (force_change) {
|
|
record._changed[this.name] = true;
|
|
this.changed(record).done(function() {
|
|
record.validate(null, true).then(function() {
|
|
var root_group = record.group.root_group();
|
|
root_group.screens.forEach(function(screen) {
|
|
screen.display();
|
|
});
|
|
});
|
|
});
|
|
}
|
|
},
|
|
get_client: function(record) {
|
|
return this.get(record);
|
|
},
|
|
set_default: function(record, value) {
|
|
record._values[this.name] = value;
|
|
record._changed[this.name] = true;
|
|
},
|
|
set_on_change: function(record, value) {
|
|
record._values[this.name] = value;
|
|
record._changed[this.name] = true;
|
|
},
|
|
changed: function(record) {
|
|
var prms = [];
|
|
prms.push(record.on_change([this.name]));
|
|
prms.push(record.on_change_with([this.name]));
|
|
prms.push(record.autocomplete_with(this.name));
|
|
record.set_field_context();
|
|
return jQuery.when.apply(jQuery, prms);
|
|
},
|
|
get_timestamp: function(record) {
|
|
return {};
|
|
},
|
|
get_context: function(record) {
|
|
var context = jQuery.extend({}, record.get_context());
|
|
if (record.group.parent) {
|
|
jQuery.extend(context, record.group.parent.get_context());
|
|
}
|
|
jQuery.extend(context,
|
|
record.expr_eval(this.description.context || {}));
|
|
return context;
|
|
},
|
|
get_domains: function(record, pre_validate) {
|
|
var inversion = new Sao.common.DomainInversion();
|
|
var screen_domain = inversion.domain_inversion(
|
|
[record.group.domain4inversion(), pre_validate || []],
|
|
this.name, Sao.common.EvalEnvironment(record));
|
|
if ((typeof screen_domain == 'boolean') && !screen_domain) {
|
|
screen_domain = [['id', '=', null]];
|
|
} else if ((typeof screen_domain == 'boolean') && screen_domain) {
|
|
screen_domain = [];
|
|
}
|
|
var attr_domain = record.expr_eval(this.description.domain || []);
|
|
return [screen_domain, attr_domain];
|
|
},
|
|
get_domain: function(record) {
|
|
var domains = this.get_domains(record);
|
|
var screen_domain = domains[0];
|
|
var attr_domain = domains[1];
|
|
var inversion = new Sao.common.DomainInversion();
|
|
return inversion.concat(
|
|
[inversion.localize_domain(screen_domain), attr_domain]);
|
|
},
|
|
validation_domains: function(record, pre_validate) {
|
|
var inversion = new Sao.common.DomainInversion();
|
|
return inversion.concat(this.get_domains(record, pre_validate));
|
|
},
|
|
get_eval: function(record) {
|
|
return this.get(record);
|
|
},
|
|
get_on_change_value: function(record) {
|
|
return this.get_eval(record);
|
|
},
|
|
set_state: function(record, states) {
|
|
if (states === undefined) {
|
|
states = ['readonly', 'required', 'invisible'];
|
|
}
|
|
var state_changes = record.expr_eval(
|
|
this.description.states || {});
|
|
states.forEach(function(state) {
|
|
if ((state == 'readonly') && this.description.readonly) {
|
|
return;
|
|
}
|
|
if (state_changes[state] !== undefined) {
|
|
this.get_state_attrs(record)[state] = state_changes[state];
|
|
} else if (this.description[state] !== undefined) {
|
|
this.get_state_attrs(record)[state] =
|
|
this.description[state];
|
|
}
|
|
}.bind(this));
|
|
if (record.group.get_readonly() ||
|
|
this.get_state_attrs(record).domain_readonly) {
|
|
this.get_state_attrs(record).readonly = true;
|
|
}
|
|
},
|
|
get_state_attrs: function(record) {
|
|
if (!(this.name in record.state_attrs)) {
|
|
record.state_attrs[this.name] = jQuery.extend(
|
|
{}, this.description);
|
|
}
|
|
if (record.group.get_readonly() || record.readonly()) {
|
|
record.state_attrs[this.name].readonly = true;
|
|
}
|
|
return record.state_attrs[this.name];
|
|
},
|
|
check_required: function(record) {
|
|
var state_attrs = this.get_state_attrs(record);
|
|
if (state_attrs.required == 1) {
|
|
if (!this.get(record) && (state_attrs.readonly != 1)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
},
|
|
validate: function(record, softvalidation, pre_validate) {
|
|
if (this.description.readonly) {
|
|
return true;
|
|
}
|
|
var invalid = false;
|
|
this.get_state_attrs(record).domain_readonly = false;
|
|
var inversion = new Sao.common.DomainInversion();
|
|
var domain = inversion.simplify(this.validation_domains(record,
|
|
pre_validate));
|
|
if (!softvalidation) {
|
|
if (!this.check_required(record)) {
|
|
invalid = 'required';
|
|
}
|
|
}
|
|
if (typeof domain == 'boolean') {
|
|
if (!domain) {
|
|
invalid = 'domain';
|
|
}
|
|
} else if (Sao.common.compare(domain, [['id', '=', null]])) {
|
|
invalid = 'domain';
|
|
} else {
|
|
var uniques = inversion.unique_value(domain);
|
|
var unique = uniques[0];
|
|
var leftpart = uniques[1];
|
|
var value = uniques[2];
|
|
if (unique) {
|
|
// If the inverted domain is so constraint that only one
|
|
// value is possible we should use it. But we must also pay
|
|
// attention to the fact that the original domain might be
|
|
// a 'OR' domain and thus not preventing the modification
|
|
// of fields.
|
|
if (value === false) {
|
|
// XXX to remove once server domains are fixed
|
|
value = null;
|
|
}
|
|
var setdefault = true;
|
|
var original_domain;
|
|
if (!jQuery.isEmptyObject(record.group.domain())) {
|
|
original_domain = inversion.merge(record.group.domain());
|
|
} else {
|
|
original_domain = inversion.merge(domain);
|
|
}
|
|
var domain_readonly = original_domain[0] == 'AND';
|
|
if (leftpart.contains('.')) {
|
|
var recordpart = leftpart.split('.', 1)[0];
|
|
var localpart = leftpart.split('.', 1)[1];
|
|
var constraintfields = [];
|
|
if (domain_readonly) {
|
|
inversion.localize_domain(
|
|
original_domain.slice(1))
|
|
.forEach(function(leaf) {
|
|
constraintfields.push(leaf);
|
|
});
|
|
}
|
|
if ((localpart != 'id') ||
|
|
!~constraintfields.indexOf(recordpart)) {
|
|
setdefault = false;
|
|
}
|
|
}
|
|
if (setdefault && !pre_validate) {
|
|
this.set_client(record, value);
|
|
this.get_state_attrs(record).domain_readonly =
|
|
domain_readonly;
|
|
}
|
|
}
|
|
if (!inversion.eval_domain(domain,
|
|
Sao.common.EvalEnvironment(record))) {
|
|
invalid = domain;
|
|
}
|
|
}
|
|
this.get_state_attrs(record).invalid = invalid;
|
|
return !invalid;
|
|
}
|
|
});
|
|
|
|
Sao.field.Char = Sao.class_(Sao.field.Field, {
|
|
_default: '',
|
|
get: function(record) {
|
|
return Sao.field.Char._super.get.call(this, record) || this._default;
|
|
}
|
|
});
|
|
|
|
Sao.field.Selection = Sao.class_(Sao.field.Field, {
|
|
_default: null
|
|
});
|
|
|
|
Sao.field.DateTime = Sao.class_(Sao.field.Field, {
|
|
_default: null,
|
|
time_format: function(record) {
|
|
return record.expr_eval(this.description.format);
|
|
},
|
|
set_client: function(record, value, force_change) {
|
|
var current_value;
|
|
if (value) {
|
|
if (value.isTime) {
|
|
current_value = this.get(record);
|
|
if (current_value) {
|
|
value = Sao.DateTime.combine(current_value, value);
|
|
} else {
|
|
value = null;
|
|
}
|
|
} else if (value.isDate) {
|
|
current_value = this.get(record);
|
|
if (current_value) {
|
|
value = Sao.DateTime.combine(value, current_value);
|
|
}
|
|
}
|
|
}
|
|
Sao.field.DateTime._super.set_client.call(this, record, value,
|
|
force_change);
|
|
},
|
|
date_format: function(record) {
|
|
var context = this.get_context(record);
|
|
return Sao.common.date_format(context.date_format);
|
|
}
|
|
});
|
|
|
|
Sao.field.Date = Sao.class_(Sao.field.Field, {
|
|
_default: null,
|
|
set_client: function(record, value, force_change) {
|
|
if (value && !value.isDate) {
|
|
value.isDate = true;
|
|
value.isDateTime = false;
|
|
}
|
|
Sao.field.Date._super.set_client.call(this, record, value,
|
|
force_change);
|
|
},
|
|
date_format: function(record) {
|
|
var context = this.get_context(record);
|
|
return Sao.common.date_format(context.date_format);
|
|
}
|
|
});
|
|
|
|
Sao.field.Time = Sao.class_(Sao.field.Field, {
|
|
_default: null,
|
|
time_format: function(record) {
|
|
return record.expr_eval(this.description.format);
|
|
},
|
|
set_client: function(record, value, force_change) {
|
|
if (value && (value.isDate || value.isDateTime)) {
|
|
value = Sao.Time(value.hour(), value.minute(),
|
|
value.second(), value.millisecond());
|
|
}
|
|
Sao.field.Time._super.set_client.call(this, record, value,
|
|
force_change);
|
|
}
|
|
});
|
|
|
|
Sao.field.TimeDelta = Sao.class_(Sao.field.Field, {
|
|
_default: null,
|
|
converter: function(record) {
|
|
// TODO allow local context converter
|
|
return record.model.session.context[this.description.converter];
|
|
},
|
|
set_client: function(record, value, force_change) {
|
|
if (typeof(value) == 'string') {
|
|
value = Sao.common.timedelta.parse(value, this.converter(record));
|
|
}
|
|
Sao.field.TimeDelta._super.set_client.call(
|
|
this, record, value, force_change);
|
|
},
|
|
get_client: function(record) {
|
|
var value = Sao.field.TimeDelta._super.get_client.call(
|
|
this, record);
|
|
return Sao.common.timedelta.format(value, this.converter(record));
|
|
}
|
|
});
|
|
|
|
Sao.field.Float = Sao.class_(Sao.field.Field, {
|
|
_default: null,
|
|
digits: function(record, factor) {
|
|
if (factor === undefined) {
|
|
factor = 1;
|
|
}
|
|
var digits = record.expr_eval(this.description.digits);
|
|
if (!digits || !digits.every(function(e) {
|
|
return e !== null;
|
|
})) {
|
|
return;
|
|
}
|
|
var shift = Math.round(Math.log(Math.abs(factor)) / Math.LN10);
|
|
return [digits[0] + shift, digits[1] - shift];
|
|
},
|
|
check_required: function(record) {
|
|
var state_attrs = this.get_state_attrs(record);
|
|
if (state_attrs.required == 1) {
|
|
if ((this.get(record) === null) &&
|
|
(state_attrs.readonly != 1)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
},
|
|
convert: function(value) {
|
|
if (!value) {
|
|
return null;
|
|
}
|
|
value = Number(value);
|
|
if (isNaN(value)) {
|
|
value = this._default;
|
|
}
|
|
return value;
|
|
},
|
|
apply_factor: function(value, factor) {
|
|
if (value !== null) {
|
|
value /= factor;
|
|
}
|
|
return value;
|
|
},
|
|
set_client: function(record, value, force_change, factor) {
|
|
if (factor === undefined) {
|
|
factor = 1;
|
|
}
|
|
value = this.apply_factor(this.convert(value), factor);
|
|
Sao.field.Float._super.set_client.call(this, record, value,
|
|
force_change);
|
|
},
|
|
get_client: function(record, factor) {
|
|
if (factor === undefined) {
|
|
factor = 1;
|
|
}
|
|
var value = this.get(record);
|
|
if (value !== null) {
|
|
var digits = this.digits(record, factor);
|
|
if (digits) {
|
|
return (value * factor).toFixed(digits[1]);
|
|
} else {
|
|
return '' + (value * factor);
|
|
}
|
|
} else {
|
|
return '';
|
|
}
|
|
}
|
|
});
|
|
|
|
Sao.field.Numeric = Sao.class_(Sao.field.Float, {
|
|
convert: function(value) {
|
|
if (!value) {
|
|
return null;
|
|
}
|
|
value = new Sao.Decimal(value);
|
|
if (isNaN(value.valueOf())) {
|
|
value = this._default;
|
|
}
|
|
return value;
|
|
},
|
|
apply_factor: function(value, factor) {
|
|
value = Sao.field.Numeric._super.apply_factor(value, factor);
|
|
if (value !== null) {
|
|
value = new Sao.Decimal(value);
|
|
}
|
|
return value;
|
|
}
|
|
});
|
|
|
|
Sao.field.Integer = Sao.class_(Sao.field.Float, {
|
|
convert: function(value) {
|
|
value = parseInt(value, 10);
|
|
if (isNaN(value)) {
|
|
value = this._default;
|
|
}
|
|
return value;
|
|
}
|
|
});
|
|
|
|
Sao.field.Boolean = Sao.class_(Sao.field.Field, {
|
|
_default: false,
|
|
set_client: function(record, value, force_change) {
|
|
value = Boolean(value);
|
|
Sao.field.Boolean._super.set_client.call(this, record, value,
|
|
force_change);
|
|
},
|
|
get: function(record) {
|
|
return Boolean(record._values[this.name]);
|
|
},
|
|
get_client: function(record) {
|
|
return Boolean(record._values[this.name]);
|
|
}
|
|
});
|
|
|
|
Sao.field.Many2One = Sao.class_(Sao.field.Field, {
|
|
_default: null,
|
|
check_required: function(record) {
|
|
var state_attrs = this.get_state_attrs(record);
|
|
if (state_attrs.required == 1) {
|
|
if ((this.get(record) === null) &&
|
|
(state_attrs.readonly != 1)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
},
|
|
get_client: function(record) {
|
|
var rec_name = record._values[this.name + '.rec_name'];
|
|
if (rec_name === undefined) {
|
|
this.set(record, this.get(record));
|
|
rec_name = record._values[this.name + '.rec_name'] || '';
|
|
}
|
|
return rec_name;
|
|
},
|
|
set: function(record, value) {
|
|
var rec_name = record._values[this.name + '.rec_name'] || '';
|
|
var store_rec_name = function(rec_name) {
|
|
record._values[this.name + '.rec_name'] = rec_name[0].rec_name;
|
|
};
|
|
if (!rec_name && (value >= 0) && (value !== null)) {
|
|
var model_name = record.model.fields[this.name].description
|
|
.relation;
|
|
Sao.rpc({
|
|
'method': 'model.' + model_name + '.read',
|
|
'params': [[value], ['rec_name'], record.get_context()]
|
|
}, record.model.session).done(store_rec_name.bind(this)).done(
|
|
function() {
|
|
record.group.root_group().screens.forEach(
|
|
function(screen) {
|
|
screen.display();
|
|
});
|
|
});
|
|
} else {
|
|
store_rec_name.call(this, [{'rec_name': rec_name}]);
|
|
}
|
|
record._values[this.name] = value;
|
|
},
|
|
set_client: function(record, value, force_change) {
|
|
var rec_name;
|
|
if (value instanceof Array) {
|
|
rec_name = value[1];
|
|
value = value[0];
|
|
} else {
|
|
if (value == this.get(record)) {
|
|
rec_name = record._values[this.name + '.rec_name'] || '';
|
|
} else {
|
|
rec_name = '';
|
|
}
|
|
}
|
|
record._values[this.name + '.rec_name'] = rec_name;
|
|
Sao.field.Many2One._super.set_client.call(this, record, value,
|
|
force_change);
|
|
},
|
|
get_context: function(record) {
|
|
var context = Sao.field.Many2One._super.get_context.call(
|
|
this, record);
|
|
if (this.description.datetime_field) {
|
|
context._datetime = record.get_eval()[
|
|
this.description.datetime_field];
|
|
}
|
|
return context;
|
|
},
|
|
validation_domains: function(record, pre_validate) {
|
|
return this.get_domains(record, pre_validate)[0];
|
|
},
|
|
get_domain: function(record) {
|
|
var domains = this.get_domains(record);
|
|
var screen_domain = domains[0];
|
|
var attr_domain = domains[1];
|
|
var inversion = new Sao.common.DomainInversion();
|
|
return inversion.concat([inversion.localize_domain(
|
|
inversion.inverse_leaf(screen_domain), this.name),
|
|
attr_domain]);
|
|
},
|
|
get_on_change_value: function(record) {
|
|
if ((record.group.parent_name == this.name) &&
|
|
record.group.parent) {
|
|
return record.group.parent.get_on_change_value(
|
|
[record.group.child_name]);
|
|
}
|
|
return Sao.field.Many2One._super.get_on_change_value.call(
|
|
this, record);
|
|
}
|
|
});
|
|
|
|
Sao.field.One2One = Sao.class_(Sao.field.Many2One, {
|
|
});
|
|
|
|
Sao.field.One2Many = Sao.class_(Sao.field.Field, {
|
|
init: function(description) {
|
|
Sao.field.One2Many._super.init.call(this, description);
|
|
},
|
|
_default: null,
|
|
_set_value: function(record, value, default_) {
|
|
this._set_default_value(record);
|
|
var group = record._values[this.name];
|
|
var prm = jQuery.when();
|
|
if (jQuery.isEmptyObject(value)) {
|
|
value = [];
|
|
}
|
|
var mode;
|
|
if (jQuery.isEmptyObject(value) ||
|
|
!isNaN(parseInt(value[0], 10))) {
|
|
mode = 'list ids';
|
|
} else {
|
|
mode = 'list values';
|
|
}
|
|
if (mode == 'list values') {
|
|
var context = this.get_context(record);
|
|
var field_names = {};
|
|
value.forEach(function(val) {
|
|
for (var fieldname in val) {
|
|
if (!val.hasOwnProperty(fieldname)) {
|
|
continue;
|
|
}
|
|
if (!(fieldname in group.model.fields) &&
|
|
(!~fieldname.indexOf('.'))) {
|
|
field_names[fieldname] = true;
|
|
}
|
|
}
|
|
});
|
|
if (!jQuery.isEmptyObject(field_names)) {
|
|
var args = {
|
|
'method': 'model.' + this.description.relation +
|
|
'.fields_get',
|
|
'params': [Object.keys(field_names), context]
|
|
};
|
|
prm = Sao.rpc(args, record.model.session);
|
|
}
|
|
}
|
|
var set_value = function(fields) {
|
|
var promises = [];
|
|
if (!jQuery.isEmptyObject(fields)) {
|
|
group.add_fields(fields);
|
|
}
|
|
record._values[this.name] = group;
|
|
if (mode == 'list ids') {
|
|
for (var i = 0, len = group.length; i < len; i++) {
|
|
var old_record = group[i];
|
|
if (!~value.indexOf(old_record.id)) {
|
|
group.remove(old_record, true);
|
|
}
|
|
}
|
|
group.load(value);
|
|
} else {
|
|
value.forEach(function(vals) {
|
|
var new_record = group.new_(false);
|
|
if (default_) {
|
|
// Don't validate as parent will validate
|
|
promises.push(new_record.set_default(vals, false));
|
|
group.add(new_record);
|
|
} else {
|
|
promises.push(new_record.set(vals));
|
|
group.push(new_record);
|
|
}
|
|
});
|
|
}
|
|
return jQuery.when.apply(jQuery, promises);
|
|
};
|
|
return prm.then(set_value);
|
|
},
|
|
set: function(record, value, _default) {
|
|
if (_default === undefined) {
|
|
_default = false;
|
|
}
|
|
var group = record._values[this.name];
|
|
var model;
|
|
if (group !== undefined) {
|
|
model = group.model;
|
|
group.destroy();
|
|
} else if (record.model.name == this.description.relation) {
|
|
model = record.model;
|
|
} else {
|
|
model = new Sao.Model(this.description.relation);
|
|
}
|
|
record._values[this.name] = undefined;
|
|
this._set_default_value(record, model);
|
|
return this._set_value(record, value, _default);
|
|
},
|
|
get: function(record) {
|
|
var group = record._values[this.name];
|
|
if (group === undefined) {
|
|
return [];
|
|
}
|
|
var record_removed = group.record_removed;
|
|
var record_deleted = group.record_deleted;
|
|
var result = [];
|
|
var parent_name = this.description.relation_field || '';
|
|
var to_add = [];
|
|
var to_create = [];
|
|
var to_write = [];
|
|
for (var i = 0, len = group.length; i < len; i++) {
|
|
var record2 = group[i];
|
|
if (~record_removed.indexOf(record2) ||
|
|
~record_deleted.indexOf(record2)) {
|
|
continue;
|
|
}
|
|
var values;
|
|
if (record2.id >= 0) {
|
|
values = record2.get();
|
|
delete values[parent_name];
|
|
if (record2.has_changed() &&
|
|
!jQuery.isEmptyObject(values)) {
|
|
to_write.push([record2.id]);
|
|
to_write.push(values);
|
|
}
|
|
to_add.push(record2.id);
|
|
} else {
|
|
values = record2.get();
|
|
delete values[parent_name];
|
|
to_create.push(values);
|
|
}
|
|
}
|
|
if (!jQuery.isEmptyObject(to_add)) {
|
|
result.push(['add', to_add]);
|
|
}
|
|
if (!jQuery.isEmptyObject(to_create)) {
|
|
result.push(['create', to_create]);
|
|
}
|
|
if (!jQuery.isEmptyObject(to_write)) {
|
|
result.push(['write'].concat(to_write));
|
|
}
|
|
if (!jQuery.isEmptyObject(record_removed)) {
|
|
result.push(['remove', record_removed.map(function(r) {
|
|
return r.id;
|
|
})]);
|
|
}
|
|
if (!jQuery.isEmptyObject(record_deleted)) {
|
|
result.push(['delete', record_deleted.map(function(r) {
|
|
return r.id;
|
|
})]);
|
|
}
|
|
return result;
|
|
},
|
|
set_client: function(record, value, force_change) {
|
|
// domain inversion try to set None as value
|
|
if (value === null) {
|
|
value = [];
|
|
}
|
|
// domain inversion could try to set id as value
|
|
if (typeof value == 'number') {
|
|
value = [value];
|
|
}
|
|
|
|
var previous_ids = this.get_eval(record);
|
|
this._set_value(record, value);
|
|
if (!Sao.common.compare(previous_ids.sort(), value.sort())) {
|
|
record._changed[this.name] = true;
|
|
this.changed(record).done(function() {
|
|
record.validate(null, true).then(function() {
|
|
record.group.changed().done(function() {
|
|
var root_group = record.group.root_group();
|
|
root_group.screens.forEach(function(screen) {
|
|
screen.display();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
} else if (force_change) {
|
|
record._changed[this.name] = true;
|
|
this.changed(record).done(function() {
|
|
record.validate(null, true).then(function() {
|
|
var root_group = record.group.root_group();
|
|
root_group.screens.forEach(function(screen) {
|
|
screen.display();
|
|
});
|
|
});
|
|
});
|
|
}
|
|
},
|
|
get_client: function(record) {
|
|
this._set_default_value(record);
|
|
return record._values[this.name];
|
|
},
|
|
set_default: function(record, value) {
|
|
record._changed[this.name] = true;
|
|
return this.set(record, value, true);
|
|
},
|
|
set_on_change: function(record, value) {
|
|
record._changed[this.name] = true;
|
|
this._set_default_value(record);
|
|
if (value instanceof Array) {
|
|
this._set_value(record, value);
|
|
return;
|
|
}
|
|
var prm = jQuery.when();
|
|
if (value.add || value.update) {
|
|
var context = this.get_context(record);
|
|
var fields = record._values[this.name].model.fields;
|
|
var field_names = {};
|
|
var adding_values = [];
|
|
if (value.add) {
|
|
for (var i=0; i < value.add.length; i++) {
|
|
adding_values.push(value.add[i][1]);
|
|
}
|
|
}
|
|
[adding_values, value.update].forEach(function(l) {
|
|
if (!jQuery.isEmptyObject(l)) {
|
|
l.forEach(function(v) {
|
|
Object.keys(v).forEach(function(f) {
|
|
if (!(f in fields) &&
|
|
(f != 'id')) {
|
|
field_names[f] = true;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
});
|
|
if (!jQuery.isEmptyObject(field_names)) {
|
|
var args = {
|
|
'method': 'model.' + this.description.relation +
|
|
'.fields_get',
|
|
'params': [Object.keys(field_names), context]
|
|
};
|
|
prm = Sao.rpc(args, record.model.session);
|
|
}
|
|
}
|
|
|
|
var to_remove = [];
|
|
var group = record._values[this.name];
|
|
group.forEach(function(record2) {
|
|
if (!record2.id) {
|
|
to_remove.push(record2);
|
|
}
|
|
});
|
|
if (value.remove) {
|
|
value.remove.forEach(function(record_id) {
|
|
var record2 = group.get(record_id);
|
|
if (record2) {
|
|
to_remove.push(record2);
|
|
}
|
|
}.bind(this));
|
|
}
|
|
to_remove.forEach(function(record2) {
|
|
group.remove(record2, false, true, false);
|
|
}.bind(this));
|
|
|
|
if (value.add || value.update) {
|
|
prm.then(function(fields) {
|
|
group.add_fields(fields);
|
|
if (value.add) {
|
|
value.add.forEach(function(vals) {
|
|
var index = vals[0];
|
|
var data = vals[1];
|
|
var new_record = group.new_(false);
|
|
group.add(new_record, index, false);
|
|
new_record.set_on_change(data);
|
|
});
|
|
}
|
|
if (value.update) {
|
|
value.update.forEach(function(vals) {
|
|
if (!vals.id) {
|
|
return;
|
|
}
|
|
var record2 = group.get(vals.id);
|
|
if (record2) {
|
|
record2.set_on_change(vals);
|
|
}
|
|
});
|
|
}
|
|
}.bind(this));
|
|
}
|
|
},
|
|
_set_default_value: function(record, model) {
|
|
if (record._values[this.name] !== undefined) {
|
|
return;
|
|
}
|
|
if (!model) {
|
|
model = new Sao.Model(this.description.relation);
|
|
}
|
|
if (record.model.name == this.description.relation) {
|
|
model = record.model;
|
|
}
|
|
var context = record.expr_eval(this.description.context || {});
|
|
var group = Sao.Group(model, context, []);
|
|
group.set_parent(record);
|
|
group.parent_name = this.description.relation_field;
|
|
group.child_name = this.name;
|
|
record._values[this.name] = group;
|
|
},
|
|
get_timestamp: function(record) {
|
|
var group = record._values[this.name];
|
|
if (group === undefined) {
|
|
return {};
|
|
}
|
|
|
|
var timestamps = {};
|
|
var record2;
|
|
for (var i = 0, len = group.length; i < len; i++) {
|
|
record2 = group[i];
|
|
jQuery.extend(timestamps, record2.get_timestamp());
|
|
}
|
|
return timestamps;
|
|
},
|
|
get_eval: function(record) {
|
|
var result = [];
|
|
var group = record._values[this.name];
|
|
if (group === undefined) return result;
|
|
|
|
var record_removed = group.record_removed;
|
|
var record_deleted = group.record_deleted;
|
|
for (var i = 0, len = record._values[this.name].length; i < len;
|
|
i++) {
|
|
var record2 = group[i];
|
|
if (~record_removed.indexOf(record2) ||
|
|
~record_deleted.indexOf(record2))
|
|
continue;
|
|
result.push(record2.id);
|
|
}
|
|
return result;
|
|
},
|
|
get_on_change_value: function(record) {
|
|
var result = [];
|
|
var group = record._values[this.name];
|
|
if (group === undefined) return result;
|
|
for (var i = 0, len = record._values[this.name].length; i < len;
|
|
i++) {
|
|
var record2 = group[i];
|
|
if (!record2.deleted() || !record2.removed())
|
|
result.push(record2.get_on_change_value(
|
|
[this.description.relation_field || '']));
|
|
}
|
|
return result;
|
|
},
|
|
get_removed_ids: function(record) {
|
|
return record._values[this.name].record_removed.map(function(r) {
|
|
return r.id;
|
|
});
|
|
},
|
|
get_domain: function(record) {
|
|
var domains = this.get_domains(record);
|
|
var attr_domain = domains[1];
|
|
// Forget screen_domain because it only means at least one record
|
|
// and not all records
|
|
return attr_domain;
|
|
},
|
|
validation_domains: function(record, pre_validate) {
|
|
return this.get_domains(record, pre_validate)[0];
|
|
},
|
|
validate: function(record, softvalidation, pre_validate) {
|
|
var invalid = false;
|
|
var inversion = new Sao.common.DomainInversion();
|
|
var ldomain = inversion.localize_domain(inversion.domain_inversion(
|
|
record.group.clean4inversion(pre_validate || []), this.name,
|
|
Sao.common.EvalEnvironment(record)), this.name);
|
|
if (typeof ldomain == 'boolean') {
|
|
if (ldomain) {
|
|
ldomain = [];
|
|
} else {
|
|
ldomain = [['id', '=', null]];
|
|
}
|
|
}
|
|
for (var i = 0, len = (record._values[this.name] || []).length;
|
|
i < len; i++) {
|
|
var record2 = record._values[this.name][i];
|
|
if (!record2.get_loaded() && (record2.id >= 0) &&
|
|
!pre_validate) {
|
|
continue;
|
|
}
|
|
if (!record2.validate(null, softvalidation, ldomain, true)) {
|
|
invalid = 'children';
|
|
}
|
|
}
|
|
var test = Sao.field.One2Many._super.validate.call(this, record,
|
|
softvalidation, pre_validate);
|
|
if (test && invalid) {
|
|
this.get_state_attrs(record).invalid = invalid;
|
|
return false;
|
|
}
|
|
return test;
|
|
},
|
|
set_state: function(record, states) {
|
|
this._set_default_value(record);
|
|
Sao.field.One2Many._super.set_state.call(this, record, states);
|
|
}
|
|
});
|
|
|
|
Sao.field.Many2Many = Sao.class_(Sao.field.One2Many, {
|
|
get_on_change_value: function(record) {
|
|
return this.get_eval(record);
|
|
}
|
|
});
|
|
|
|
Sao.field.Reference = Sao.class_(Sao.field.Field, {
|
|
_default: null,
|
|
get_client: function(record) {
|
|
if (record._values[this.name]) {
|
|
var model = record._values[this.name][0];
|
|
var name = record._values[this.name + '.rec_name'] || '';
|
|
return [model, name];
|
|
} else {
|
|
return null;
|
|
}
|
|
},
|
|
get: function(record) {
|
|
if (record._values[this.name] &&
|
|
record._values[this.name][0] &&
|
|
record._values[this.name][1] >= -1) {
|
|
return record._values[this.name].join(',');
|
|
}
|
|
return null;
|
|
},
|
|
set_client: function(record, value, force_change) {
|
|
if (value) {
|
|
if (typeof(value) == 'string') {
|
|
value = value.split(',');
|
|
}
|
|
var ref_model = value[0];
|
|
var ref_id = value[1];
|
|
var rec_name;
|
|
if (ref_id instanceof Array) {
|
|
rec_name = ref_id[1];
|
|
ref_id = ref_id[0];
|
|
} else {
|
|
if (ref_id && !isNaN(parseInt(ref_id, 10))) {
|
|
ref_id = parseInt(ref_id, 10);
|
|
}
|
|
if ([ref_model, ref_id].join(',') == this.get(record)) {
|
|
rec_name = record._values[this.name + '.rec_name'] || '';
|
|
} else {
|
|
rec_name = '';
|
|
}
|
|
}
|
|
record._values[this.name + '.rec_name'] = rec_name;
|
|
value = [ref_model, ref_id];
|
|
}
|
|
Sao.field.Reference._super.set_client.call(
|
|
this, record, value, force_change);
|
|
},
|
|
set: function(record, value) {
|
|
if (!value) {
|
|
record._values[this.name] = this._default;
|
|
return;
|
|
}
|
|
var ref_model, ref_id;
|
|
if (typeof(value) == 'string') {
|
|
ref_model = value.split(',')[0];
|
|
ref_id = value.split(',')[1];
|
|
if (!ref_id) {
|
|
ref_id = null;
|
|
} else if (!isNaN(parseInt(ref_id, 10))) {
|
|
ref_id = parseInt(ref_id, 10);
|
|
}
|
|
} else {
|
|
ref_model = value[0];
|
|
ref_id = value[1];
|
|
}
|
|
var rec_name = record._values[this.name + '.rec_name'] || '';
|
|
var store_rec_name = function(rec_name) {
|
|
record._values[this.name + '.rec_name'] = rec_name;
|
|
}.bind(this);
|
|
if (ref_model && ref_id !== null && ref_id >= 0) {
|
|
if (!rec_name && ref_id >= 0) {
|
|
Sao.rpc({
|
|
'method': 'model.' + ref_model + '.read',
|
|
'params': [[ref_id], ['rec_name'], record.get_context()]
|
|
}, record.model.session).done(function(result) {
|
|
store_rec_name(result[0].rec_name);
|
|
});
|
|
}
|
|
} else if (ref_model) {
|
|
rec_name = '';
|
|
} else {
|
|
rec_name = ref_id;
|
|
}
|
|
record._values[this.name] = [ref_model, ref_id];
|
|
store_rec_name(rec_name);
|
|
},
|
|
get_on_change_value: function(record) {
|
|
if ((record.group.parent_name == this.name) &&
|
|
record.group.parent) {
|
|
return [record.group.parent.model.name,
|
|
record.group.parent.get_on_change_value(
|
|
[record.group.child_name])];
|
|
}
|
|
return Sao.field.Reference._super.get_on_change_value.call(
|
|
this, record);
|
|
},
|
|
get_context: function(record) {
|
|
var context = Sao.field.Reference._super.get_context.call(
|
|
this, record);
|
|
if (this.description.datetime_field) {
|
|
context._datetime = record.get_eval()[
|
|
this.description.datetime_field];
|
|
}
|
|
return context;
|
|
},
|
|
validation_domains: function(record, pre_validate) {
|
|
return this.get_domains(record, pre_validate)[0];
|
|
},
|
|
get_domain: function(record) {
|
|
var model = null;
|
|
if (record._values[this.name]) {
|
|
model = record._values[this.name][0];
|
|
}
|
|
var domains = this.get_domains(record);
|
|
var screen_domain = domains[0];
|
|
var attr_domain = domains[1];
|
|
var inversion = new Sao.common.DomainInversion();
|
|
return inversion.concat([inversion.localize_domain(
|
|
inversion.filter_leaf(screen_domain, this.name, model),
|
|
true), attr_domain]);
|
|
}
|
|
});
|
|
|
|
Sao.field.Binary = Sao.class_(Sao.field.Field, {
|
|
_default: null,
|
|
get_size: function(record) {
|
|
var data = record._values[this.name] || 0;
|
|
if (data instanceof Uint8Array) {
|
|
return data.length;
|
|
}
|
|
return data;
|
|
},
|
|
get_data: function(record) {
|
|
var data = record._values[this.name] || [];
|
|
var prm = jQuery.when(data);
|
|
if (!(data instanceof Uint8Array)) {
|
|
if (record.id < 0) {
|
|
return prm;
|
|
}
|
|
var context = record.get_context();
|
|
prm = record.model.execute('read', [[record.id], [this.name]],
|
|
context).then(function(data) {
|
|
return data[0][this.name];
|
|
}.bind(this));
|
|
}
|
|
return prm;
|
|
}
|
|
});
|
|
|
|
Sao.field.Dict = Sao.class_(Sao.field.Field, {
|
|
_default: {},
|
|
set: function(record, value) {
|
|
if (value) {
|
|
// Order keys to allow comparison with stringify
|
|
var keys = [];
|
|
for (var key in value) {
|
|
keys.push(key);
|
|
}
|
|
keys.sort();
|
|
var new_value = {};
|
|
for (var index in keys) {
|
|
key = keys[index];
|
|
new_value[key] = value[key];
|
|
}
|
|
value = new_value;
|
|
}
|
|
Sao.field.Dict._super.set.call(this, record, value);
|
|
},
|
|
get: function(record) {
|
|
return (Sao.field.Dict._super.get.call(this, record) ||
|
|
this._default);
|
|
},
|
|
get_client: function(record) {
|
|
return (Sao.field.Dict._super.get_client.call(this, record) ||
|
|
this._default);
|
|
},
|
|
validation_domains: function(record, pre_validate) {
|
|
return this.get_domains(record, pre_validate)[0];
|
|
},
|
|
get_domain: function(record) {
|
|
var inversion = new Sao.common.DomainInversion();
|
|
var domains = this.get_domains(record);
|
|
var screen_domain = domains[0];
|
|
var attr_domain = domains[1];
|
|
return inversion.concat([inversion.localize_domain(
|
|
inversion.inverse_leaf(screen_domain)),
|
|
attr_domain]);
|
|
},
|
|
date_format: function(record) {
|
|
var context = this.get_context(record);
|
|
return Sao.common.date_format(context.date_format);
|
|
},
|
|
time_format: function(record) {
|
|
return '%X';
|
|
}
|
|
});
|
|
}());
|